LibWeb+Compositor: Add canvas display-list surfaces

Canvas contexts need a compositor-owned surface path that can be shared
by 2D canvas and WebGL. Add CanvasId and a CanvasSurfaceRegistry, pass
the registry into display-list playback, and teach Skia playback how to
resolve and draw a registered canvas surface.

This only adds the shared display-list command and registry plumbing.
Existing canvas elements still publish their old compositor surfaces, so
the behavior change is left for the later canvas-host commits.
This commit is contained in:
Aliaksandr Kalenik 2026-06-15 19:20:22 +02:00 committed by Alexander Kalenik
parent 7dadfa2e52
commit af8b41e1cb
14 changed files with 132 additions and 6 deletions

View file

@ -46,6 +46,7 @@ namespace Web::Painting {
class AccumulatedVisualContextTree;
class BackingStore;
class ChromeWidget;
class CanvasSurfaceRegistry;
class DevicePixelConverter;
class DisplayList;
class DisplayListPlayerSkia;

View file

@ -0,0 +1,57 @@
/*
* Copyright (c) 2026, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/HashMap.h>
#include <AK/Noncopyable.h>
#include <AK/NonnullRefPtr.h>
#include <LibGfx/PaintingSurface.h>
#include <LibWeb/Painting/DisplayListResourceIds.h>
namespace Web::Painting {
class CanvasSurfaceRegistry {
AK_MAKE_NONCOPYABLE(CanvasSurfaceRegistry);
AK_MAKE_DEFAULT_MOVABLE(CanvasSurfaceRegistry);
public:
CanvasSurfaceRegistry() = default;
~CanvasSurfaceRegistry() = default;
CanvasId allocate_canvas_id()
{
return CanvasId { m_next_canvas_id++ };
}
CanvasId create_canvas_surface(NonnullRefPtr<Gfx::PaintingSurface> surface)
{
auto id = allocate_canvas_id();
set_canvas_surface(id, move(surface));
return id;
}
void set_canvas_surface(CanvasId id, NonnullRefPtr<Gfx::PaintingSurface> surface)
{
m_surfaces.set(id, move(surface));
}
void remove_canvas_surface(CanvasId id)
{
m_surfaces.remove(id);
}
Gfx::PaintingSurface const* canvas_surface(CanvasId id) const
{
return m_surfaces.get(id).value_or(nullptr);
}
private:
u64 m_next_canvas_id { 1 };
HashMap<CanvasId, NonnullRefPtr<Gfx::PaintingSurface>> m_surfaces;
};
}

View file

@ -110,14 +110,17 @@ void DisplayListPlayer::execute(
AccumulatedVisualContextTree const& visual_context_tree,
DisplayListResourceStorage const& resource_storage,
ScrollStateSnapshot const& scroll_state_snapshot,
RefPtr<Gfx::PaintingSurface> surface)
RefPtr<Gfx::PaintingSurface> surface,
CanvasSurfaceRegistry const* canvas_surface_registry)
{
VERIFY(display_list.compatible_visual_context_tree_version() == visual_context_tree.version());
m_surface = surface;
m_active_display_list = &display_list;
m_active_visual_context_tree = &visual_context_tree;
m_resource_storage = &resource_storage;
m_canvas_surface_registry = canvas_surface_registry;
execute_impl(display_list, scroll_state_snapshot);
m_canvas_surface_registry = nullptr;
m_resource_storage = nullptr;
m_active_visual_context_tree = nullptr;
m_active_display_list = nullptr;

View file

@ -33,7 +33,7 @@ class WEB_API DisplayListPlayer {
public:
virtual ~DisplayListPlayer() = default;
void execute(DisplayList const&, AccumulatedVisualContextTree const&, DisplayListResourceStorage const&, ScrollStateSnapshot const&, RefPtr<Gfx::PaintingSurface>);
void execute(DisplayList const&, AccumulatedVisualContextTree const&, DisplayListResourceStorage const&, ScrollStateSnapshot const&, RefPtr<Gfx::PaintingSurface>, CanvasSurfaceRegistry const* = nullptr);
virtual void flush(Gfx::PaintingSurface&) = 0;
protected:
@ -41,6 +41,7 @@ protected:
DisplayList const& active_display_list() const { return *m_active_display_list; }
AccumulatedVisualContextTree const& active_visual_context_tree() const { return *m_active_visual_context_tree; }
DisplayListResourceStorage const& resource_storage() const { return *m_resource_storage; }
CanvasSurfaceRegistry const* canvas_surface_registry() const { return m_canvas_surface_registry; }
ReadonlyBytes inline_data(DisplayListDataSpan span) const
{
VERIFY(static_cast<size_t>(span.offset) + span.size <= m_current_command_payload.size());
@ -73,6 +74,7 @@ private:
DisplayList const* m_active_display_list { nullptr };
AccumulatedVisualContextTree const* m_active_visual_context_tree { nullptr };
DisplayListResourceStorage const* m_resource_storage { nullptr };
CanvasSurfaceRegistry const* m_canvas_surface_registry { nullptr };
RefPtr<Gfx::PaintingSurface> m_surface;
ReadonlyBytes m_current_command_payload;
};

View file

@ -45,6 +45,11 @@ void DrawCompositorSurface::dump(StringBuilder& builder) const
builder.appendff(" dst_rect={}", dst_rect);
}
void DrawCanvas::dump(StringBuilder& builder) const
{
builder.appendff(" dst_rect={}", dst_rect);
}
void DrawVideoFrame::dump(StringBuilder& builder) const
{
builder.appendff(" dst_rect={}", dst_rect);

View file

@ -39,6 +39,7 @@ class DisplayList;
V(DrawScaledDecodedImageFrame, draw_scaled_decoded_image_frame) \
V(DrawRepeatedDecodedImageFrame, draw_repeated_decoded_image_frame) \
V(DrawCompositorSurface, draw_compositor_surface) \
V(DrawCanvas, draw_canvas) \
V(DrawVideoFrame, draw_video_frame) \
V(Save, save) \
V(SaveLayer, save_layer) \
@ -199,6 +200,18 @@ struct DrawCompositorSurface {
void dump(StringBuilder&) const;
};
struct DrawCanvas {
static constexpr StringView command_name = "DrawCanvas"sv;
static constexpr DisplayListCommandType command_type = DisplayListCommandType::DrawCanvas;
Gfx::IntRect dst_rect;
CanvasId canvas_id;
Gfx::ScalingMode scaling_mode;
[[nodiscard]] Gfx::IntRect bounding_rect() const { return dst_rect; }
void dump(StringBuilder&) const;
};
struct DrawVideoFrame {
static constexpr StringView command_name = "DrawVideoFrame"sv;
static constexpr DisplayListCommandType command_type = DisplayListCommandType::DrawVideoFrame;

View file

@ -38,6 +38,7 @@
#include <LibGfx/SkiaUtils.h>
#include <LibGfx/YUVData.h>
#include <LibMedia/VideoFrame.h>
#include <LibWeb/Painting/CanvasSurfaceRegistry.h>
#include <LibWeb/Painting/DisplayListPlayerSkia.h>
namespace Web::Painting {
@ -225,6 +226,28 @@ void DisplayListPlayerSkia::play_command(DrawCompositorSurface const& command)
canvas.drawImageRect(image.get(), src_rect, dst_rect, to_skia_sampling_options(command.scaling_mode), &paint, SkCanvas::kStrict_SrcRectConstraint);
}
void DisplayListPlayerSkia::play_command(DrawCanvas const& command)
{
auto const* registry = canvas_surface_registry();
if (!registry)
return;
auto* canvas_surface = registry->canvas_surface(command.canvas_id);
if (!canvas_surface)
return;
auto image = canvas_surface->sk_image_snapshot<sk_sp<SkImage>>();
if (!image)
return;
auto dst_rect = to_skia_rect(command.dst_rect);
SkRect src_rect = SkRect::MakeIWH(image->width(), image->height());
auto& canvas = surface().canvas();
SkPaint paint;
paint.setAntiAlias(true);
canvas.drawImageRect(image.get(), src_rect, dst_rect, to_skia_sampling_options(command.scaling_mode), &paint, SkCanvas::kStrict_SrcRectConstraint);
}
void DisplayListPlayerSkia::play_command(DrawVideoFrame const& command)
{
auto frame = resource_storage().video_frame(command.video_frame_id);

View file

@ -523,6 +523,17 @@ void DisplayListRecorder::draw_compositor_surface(Gfx::IntRect const& dst_rect,
});
}
void DisplayListRecorder::draw_canvas(Gfx::IntRect const& dst_rect, CanvasId canvas_id, Gfx::ScalingMode scaling_mode)
{
if (dst_rect.is_empty())
return;
append_command(DrawCanvas {
.dst_rect = dst_rect,
.canvas_id = canvas_id,
.scaling_mode = scaling_mode,
});
}
void DisplayListRecorder::draw_video_frame(Gfx::IntRect const& dst_rect, VideoFrameResourceId frame_id, RefPtr<Media::VideoFrame const> frame, Gfx::ScalingMode scaling_mode)
{
if (dst_rect.is_empty())

View file

@ -74,6 +74,7 @@ public:
void draw_scaled_decoded_image_frame(Gfx::IntRect const& dst_rect, Gfx::DecodedImageFrame frame, Gfx::ScalingMode scaling_mode = Gfx::ScalingMode::NearestNeighbor);
void draw_compositor_surface(Gfx::IntRect const& dst_rect, CompositorSurfaceId, Gfx::ScalingMode scaling_mode = Gfx::ScalingMode::NearestNeighbor);
void draw_canvas(Gfx::IntRect const& dst_rect, CanvasId, Gfx::ScalingMode scaling_mode = Gfx::ScalingMode::NearestNeighbor);
void draw_video_frame(Gfx::IntRect const& dst_rect, VideoFrameResourceId, RefPtr<Media::VideoFrame const>, Gfx::ScalingMode scaling_mode = Gfx::ScalingMode::NearestNeighbor);
void draw_repeated_decoded_image_frame(Gfx::IntRect dst_rect, Gfx::IntRect clip_rect, Gfx::DecodedImageFrame frame, Gfx::ScalingMode scaling_mode, bool repeat_x, bool repeat_y);

View file

@ -17,6 +17,7 @@ AK_TYPEDEF_DISTINCT_ORDERED_ID(u64, ImageFrameResourceId);
AK_TYPEDEF_DISTINCT_ORDERED_ID(u64, VideoFrameResourceId);
AK_TYPEDEF_DISTINCT_ORDERED_ID(u64, DisplayListResourceId);
AK_TYPEDEF_DISTINCT_ORDERED_ID(u64, CompositorSurfaceId);
AK_TYPEDEF_DISTINCT_ORDERED_ID(u64, CanvasId);
inline VideoFrameResourceId allocate_video_frame_resource_id()
{

View file

@ -70,7 +70,7 @@ void CompositorState::create_context(Web::Compositor::CompositorContextId contex
VERIFY(context_id == Web::Compositor::compositor_context_id_for_page(*page_id));
auto& context = *m_contexts.ensure(context_id, [&] {
return make<ContextState>(page_id, web_content_client, m_async_scrolling_enabled);
return make<ContextState>(page_id, web_content_client, m_canvas_surface_registry, m_async_scrolling_enabled);
});
resize_backing_stores_if_needed(context_id, context);
}

View file

@ -25,6 +25,7 @@
#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>
@ -69,6 +70,10 @@ public:
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);
@ -143,6 +148,7 @@ private:
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;

View file

@ -9,6 +9,7 @@
#include <Compositor/CompositorState.h>
#include <Compositor/ContextState.h>
#include <LibCore/Timer.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/Color.h>
#include <LibGfx/PainterSkia.h>
#include <LibGfx/PaintingSurface.h>
@ -79,8 +80,9 @@ static void clamp_visual_viewport_transform_to_viewport(Web::Painting::Transform
transform.matrix[1, 3] = clamp(transform.matrix[1, 3], min_y, 0.0f);
}
ContextState::ContextState(Optional<u64> page_id, CompositorStateWebContentClient& web_content_client, bool async_scrolling_enabled)
ContextState::ContextState(Optional<u64> page_id, CompositorStateWebContentClient& web_content_client, Web::Painting::CanvasSurfaceRegistry const& canvas_surface_registry, bool async_scrolling_enabled)
: m_web_content_client(web_content_client)
, m_canvas_surface_registry(canvas_surface_registry)
, m_page_id(page_id)
, m_async_scrolling_enabled(async_scrolling_enabled)
{
@ -877,7 +879,7 @@ Web::Painting::AccumulatedVisualContextTree const& ContextState::visual_context_
void ContextState::paint_current_display_list(Web::Painting::DisplayListPlayerSkia& display_list_player, Gfx::PaintingSurface& surface)
{
VERIFY(m_display_list);
display_list_player.execute(*m_display_list, visual_context_tree_for_compositing(), m_display_list_resource_storage, m_scroll_state_snapshot, surface);
display_list_player.execute(*m_display_list, visual_context_tree_for_compositing(), m_display_list_resource_storage, m_scroll_state_snapshot, surface, &m_canvas_surface_registry);
m_viewport_scrollbar_controller.paint(surface, display_list_player, m_scroll_state_snapshot);
}

View file

@ -85,7 +85,7 @@ public:
i32 bitmap_id { 0 };
};
ContextState(Optional<u64> page_id, CompositorStateWebContentClient&, bool async_scrolling_enabled);
ContextState(Optional<u64> page_id, CompositorStateWebContentClient&, Web::Painting::CanvasSurfaceRegistry const&, bool async_scrolling_enabled);
~ContextState();
static bool presentation_mode_presents_to_client(Web::Compositor::PresentationMode const&);
@ -179,6 +179,7 @@ private:
void paint_current_display_list(Web::Painting::DisplayListPlayerSkia&, Gfx::PaintingSurface&);
CompositorStateWebContentClient& m_web_content_client;
Web::Painting::CanvasSurfaceRegistry const& m_canvas_surface_registry;
Optional<u64> m_page_id;
bool const m_async_scrolling_enabled { true };