From af8b41e1cb0d3969fe26418537251595cf6bf358 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Mon, 15 Jun 2026 19:20:22 +0200 Subject: [PATCH] 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. --- Libraries/LibWeb/Forward.h | 1 + .../LibWeb/Painting/CanvasSurfaceRegistry.h | 57 +++++++++++++++++++ Libraries/LibWeb/Painting/DisplayList.cpp | 5 +- Libraries/LibWeb/Painting/DisplayList.h | 4 +- .../LibWeb/Painting/DisplayListCommand.cpp | 5 ++ .../LibWeb/Painting/DisplayListCommand.h | 13 +++++ .../LibWeb/Painting/DisplayListPlayerSkia.cpp | 23 ++++++++ .../LibWeb/Painting/DisplayListRecorder.cpp | 11 ++++ .../LibWeb/Painting/DisplayListRecorder.h | 1 + .../LibWeb/Painting/DisplayListResourceIds.h | 1 + Services/Compositor/CompositorState.cpp | 2 +- Services/Compositor/CompositorState.h | 6 ++ Services/Compositor/ContextState.cpp | 6 +- Services/Compositor/ContextState.h | 3 +- 14 files changed, 132 insertions(+), 6 deletions(-) create mode 100644 Libraries/LibWeb/Painting/CanvasSurfaceRegistry.h diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index 27b5d6ec5a..9cf2487ed8 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -46,6 +46,7 @@ namespace Web::Painting { class AccumulatedVisualContextTree; class BackingStore; class ChromeWidget; +class CanvasSurfaceRegistry; class DevicePixelConverter; class DisplayList; class DisplayListPlayerSkia; diff --git a/Libraries/LibWeb/Painting/CanvasSurfaceRegistry.h b/Libraries/LibWeb/Painting/CanvasSurfaceRegistry.h new file mode 100644 index 0000000000..3510cd62af --- /dev/null +++ b/Libraries/LibWeb/Painting/CanvasSurfaceRegistry.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2026, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +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 surface) + { + auto id = allocate_canvas_id(); + set_canvas_surface(id, move(surface)); + return id; + } + + void set_canvas_surface(CanvasId id, NonnullRefPtr 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> m_surfaces; +}; + +} diff --git a/Libraries/LibWeb/Painting/DisplayList.cpp b/Libraries/LibWeb/Painting/DisplayList.cpp index 18f790c28c..1c4fc3dd64 100644 --- a/Libraries/LibWeb/Painting/DisplayList.cpp +++ b/Libraries/LibWeb/Painting/DisplayList.cpp @@ -110,14 +110,17 @@ void DisplayListPlayer::execute( AccumulatedVisualContextTree const& visual_context_tree, DisplayListResourceStorage const& resource_storage, ScrollStateSnapshot const& scroll_state_snapshot, - RefPtr surface) + RefPtr 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; diff --git a/Libraries/LibWeb/Painting/DisplayList.h b/Libraries/LibWeb/Painting/DisplayList.h index a2766fb49e..829ac47b10 100644 --- a/Libraries/LibWeb/Painting/DisplayList.h +++ b/Libraries/LibWeb/Painting/DisplayList.h @@ -33,7 +33,7 @@ class WEB_API DisplayListPlayer { public: virtual ~DisplayListPlayer() = default; - void execute(DisplayList const&, AccumulatedVisualContextTree const&, DisplayListResourceStorage const&, ScrollStateSnapshot const&, RefPtr); + void execute(DisplayList const&, AccumulatedVisualContextTree const&, DisplayListResourceStorage const&, ScrollStateSnapshot const&, RefPtr, 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(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 m_surface; ReadonlyBytes m_current_command_payload; }; diff --git a/Libraries/LibWeb/Painting/DisplayListCommand.cpp b/Libraries/LibWeb/Painting/DisplayListCommand.cpp index 998acc58e2..b73cd8ab45 100644 --- a/Libraries/LibWeb/Painting/DisplayListCommand.cpp +++ b/Libraries/LibWeb/Painting/DisplayListCommand.cpp @@ -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); diff --git a/Libraries/LibWeb/Painting/DisplayListCommand.h b/Libraries/LibWeb/Painting/DisplayListCommand.h index b34fc745cf..fecbfeb43b 100644 --- a/Libraries/LibWeb/Painting/DisplayListCommand.h +++ b/Libraries/LibWeb/Painting/DisplayListCommand.h @@ -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; diff --git a/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp b/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp index 053b340453..b14316a6d3 100644 --- a/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp +++ b/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include 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>(); + 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); diff --git a/Libraries/LibWeb/Painting/DisplayListRecorder.cpp b/Libraries/LibWeb/Painting/DisplayListRecorder.cpp index 72db557b9f..26ad449f14 100644 --- a/Libraries/LibWeb/Painting/DisplayListRecorder.cpp +++ b/Libraries/LibWeb/Painting/DisplayListRecorder.cpp @@ -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 frame, Gfx::ScalingMode scaling_mode) { if (dst_rect.is_empty()) diff --git a/Libraries/LibWeb/Painting/DisplayListRecorder.h b/Libraries/LibWeb/Painting/DisplayListRecorder.h index 394cc0581b..56bf75e303 100644 --- a/Libraries/LibWeb/Painting/DisplayListRecorder.h +++ b/Libraries/LibWeb/Painting/DisplayListRecorder.h @@ -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, 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); diff --git a/Libraries/LibWeb/Painting/DisplayListResourceIds.h b/Libraries/LibWeb/Painting/DisplayListResourceIds.h index 262eb75705..859ea82688 100644 --- a/Libraries/LibWeb/Painting/DisplayListResourceIds.h +++ b/Libraries/LibWeb/Painting/DisplayListResourceIds.h @@ -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() { diff --git a/Services/Compositor/CompositorState.cpp b/Services/Compositor/CompositorState.cpp index d033a12770..bf105f476e 100644 --- a/Services/Compositor/CompositorState.cpp +++ b/Services/Compositor/CompositorState.cpp @@ -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(page_id, web_content_client, m_async_scrolling_enabled); + return make(page_id, web_content_client, m_canvas_surface_registry, m_async_scrolling_enabled); }); resize_backing_stores_if_needed(context_id, context); } diff --git a/Services/Compositor/CompositorState.h b/Services/Compositor/CompositorState.h index 2ad04e891b..35197143a8 100644 --- a/Services/Compositor/CompositorState.h +++ b/Services/Compositor/CompositorState.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -69,6 +70,10 @@ public: ContextOwnerCheckResult check_context_owner(Web::Compositor::CompositorContextId, CompositorStateWebContentClient&); void destroy_contexts_for_web_content_client(CompositorStateWebContentClient&); + RefPtr 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 page_id, CompositorStateWebContentClient&); void destroy_context(Web::Compositor::CompositorContextId); @@ -143,6 +148,7 @@ private: HashMap> m_contexts; DoublyLinkedList m_pending_async_presents; RefPtr m_skia_backend_context; + Web::Painting::CanvasSurfaceRegistry m_canvas_surface_registry; OwnPtr m_display_list_player; HashMap, OwnPtr> m_vsync_schedulers_by_display; RefPtr m_gpu_completion_timer; diff --git a/Services/Compositor/ContextState.cpp b/Services/Compositor/ContextState.cpp index 8e1b5f7a97..f791b2f437 100644 --- a/Services/Compositor/ContextState.cpp +++ b/Services/Compositor/ContextState.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -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 page_id, CompositorStateWebContentClient& web_content_client, bool async_scrolling_enabled) +ContextState::ContextState(Optional 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); } diff --git a/Services/Compositor/ContextState.h b/Services/Compositor/ContextState.h index 31c68203f0..33f6500384 100644 --- a/Services/Compositor/ContextState.h +++ b/Services/Compositor/ContextState.h @@ -85,7 +85,7 @@ public: i32 bitmap_id { 0 }; }; - ContextState(Optional page_id, CompositorStateWebContentClient&, bool async_scrolling_enabled); + ContextState(Optional 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 m_page_id; bool const m_async_scrolling_enabled { true };