diff --git a/Libraries/LibGfx/PaintingSurface.cpp b/Libraries/LibGfx/PaintingSurface.cpp index 32ceda1cf5..220f683598 100644 --- a/Libraries/LibGfx/PaintingSurface.cpp +++ b/Libraries/LibGfx/PaintingSurface.cpp @@ -9,7 +9,11 @@ #include #include +#include #include +#include +#include +#include #include #include #include @@ -178,6 +182,25 @@ void PaintingSurface::write_from_bitmap(Bitmap const& bitmap) m_impl->surface->writePixels(pixmap, 0, 0); } +void PaintingSurface::copy_from_surface(PaintingSurface& source) +{ + source.flush(); + + auto image = source.m_impl->surface->makeImageSnapshot(); + if (!image) + return; + + SkPaint paint; + paint.setBlendMode(SkBlendMode::kSrc); + canvas().drawImageRect( + image.get(), + SkRect::MakeIWH(image->width(), image->height()), + SkRect::MakeIWH(size().width(), size().height()), + SkSamplingOptions {}, + &paint, + SkCanvas::kStrict_SrcRectConstraint); +} + IntSize PaintingSurface::size() const { return m_impl->size; diff --git a/Libraries/LibGfx/PaintingSurface.h b/Libraries/LibGfx/PaintingSurface.h index 1ce37d8d94..4948479cb4 100644 --- a/Libraries/LibGfx/PaintingSurface.h +++ b/Libraries/LibGfx/PaintingSurface.h @@ -58,6 +58,7 @@ public: void read_into_bitmap(Bitmap&, IntPoint source_position = {}) const; void write_from_bitmap(Bitmap const&); + void copy_from_surface(PaintingSurface&); void notify_content_will_change(); diff --git a/Libraries/LibWeb/HTML/Canvas/RemoteCanvas2DTransport.h b/Libraries/LibWeb/HTML/Canvas/RemoteCanvas2DTransport.h index ca68ed96ff..27ebc0969d 100644 --- a/Libraries/LibWeb/HTML/Canvas/RemoteCanvas2DTransport.h +++ b/Libraries/LibWeb/HTML/Canvas/RemoteCanvas2DTransport.h @@ -23,7 +23,7 @@ public: virtual bool create_context(Gfx::IntSize, bool alpha) = 0; virtual Optional canvas_id() const = 0; virtual void destroy_context() = 0; - virtual void update_commands(Gfx::CanvasCommandList const&) = 0; + virtual void update_commands(Gfx::CanvasCommandList const&, bool commit) = 0; virtual RefPtr read_back_pixels(Gfx::IntRect const&) = 0; }; diff --git a/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp b/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp index 489f1bf3e6..d463fbdfe9 100644 --- a/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp +++ b/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp @@ -241,7 +241,7 @@ WebIDL::ExceptionOr CanvasRenderingContext2D::draw_image_internal(CanvasIm .compositing_and_blending_operator = drawing_state().current_compositing_and_blending_operator, }); did_draw(destination_rect); - flush_recorded_commands(); + flush_recorded_commands(CommitCommands::No); // 7. If image is not origin-clean, then set the CanvasRenderingContext2D's origin-clean flag to false. if (image_is_not_origin_clean(image)) @@ -289,7 +289,7 @@ Gfx::CanvasCommandList* CanvasRenderingContext2D::canvas_command_list() if (!has_backing_storage()) return nullptr; if (m_commands.size() >= max_pending_canvas_commands) - flush_recorded_commands(); + flush_recorded_commands(CommitCommands::No); return &m_commands; } @@ -315,20 +315,30 @@ bool CanvasRenderingContext2D::ensure_remote_canvas_context() return true; } -void CanvasRenderingContext2D::flush_recorded_commands() +void CanvasRenderingContext2D::flush_recorded_commands(CommitCommands commit) { - if (m_commands.is_empty() || !m_transport) + if (!m_transport) return; + bool const should_commit = commit == CommitCommands::Yes; + if (m_commands.is_empty()) { + if (!should_commit || !m_has_uncommitted_remote_commands) + return; + m_transport->update_commands(m_commands, true); + m_has_uncommitted_remote_commands = false; + return; + } + auto commands = move(m_commands); - m_transport->update_commands(commands); + m_transport->update_commands(commands, should_commit); + m_has_uncommitted_remote_commands = !should_commit; } RefPtr CanvasRenderingContext2D::read_pixels(Gfx::IntRect const& rect) { if (!has_backing_storage()) return nullptr; - flush_recorded_commands(); + flush_recorded_commands(CommitCommands::No); return m_transport->read_back_pixels(rect); } @@ -342,7 +352,7 @@ void CanvasRenderingContext2D::set_size(Gfx::IntSize const& size) void CanvasRenderingContext2D::prepare_for_compositing() { - flush_recorded_commands(); + flush_recorded_commands(CommitCommands::Yes); } Optional CanvasRenderingContext2D::canvas_id() const @@ -413,6 +423,7 @@ void CanvasRenderingContext2D::ensure_backing_storage() void CanvasRenderingContext2D::discard_backing_storage() { m_commands = {}; + m_has_uncommitted_remote_commands = false; if (m_transport) { m_transport->destroy_context(); m_transport = nullptr; diff --git a/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h b/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h index 73347d5dee..736927b0dd 100644 --- a/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h +++ b/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h @@ -179,7 +179,11 @@ private: void paint_shadow_for_fill_internal(Gfx::Path const&, Gfx::WindingRule); void paint_shadow_for_stroke_internal(Gfx::Path const&, Gfx::Path::CapStyle, Gfx::Path::JoinStyle, Vector const&); - void flush_recorded_commands(); + enum class CommitCommands { + No, + Yes, + }; + void flush_recorded_commands(CommitCommands); bool ensure_remote_canvas_context(); @@ -189,6 +193,7 @@ private: Gfx::CanvasCommandList m_commands; RefPtr m_transport; + bool m_has_uncommitted_remote_commands { false }; // https://html.spec.whatwg.org/multipage/canvas.html#concept-canvas-origin-clean bool m_origin_clean { true }; diff --git a/Services/Compositor/CanvasHost.cpp b/Services/Compositor/CanvasHost.cpp index d945f034d8..8c338784ec 100644 --- a/Services/Compositor/CanvasHost.cpp +++ b/Services/Compositor/CanvasHost.cpp @@ -45,11 +45,27 @@ OwnPtr CanvasHost::create_2d_command_player(Gfx::IntSi return player; } -Gfx::CanvasCommandPlayer& CanvasHost::as_2d(Context& context) +static void copy_surface_contents(Gfx::PaintingSurface& source, Gfx::PaintingSurface& destination) { - auto* player = context.get_pointer(); - VERIFY(player); - return **player; + destination.copy_from_surface(source); +} + +static NonnullRefPtr create_presented_canvas_surface(Gfx::PaintingSurface& source) +{ + auto surface = Gfx::PaintingSurface::create_with_size( + source.size(), + Gfx::BitmapFormat::BGRA8888, + Gfx::AlphaType::Premultiplied, + source.skia_backend_context()); + copy_surface_contents(source, *surface); + return surface; +} + +CanvasHost::Canvas2DContext& CanvasHost::as_2d(Context& context) +{ + auto* canvas_context = context.get_pointer(); + VERIFY(canvas_context); + return *canvas_context; } HostWebGLContext& CanvasHost::as_webgl(Context& context) @@ -61,12 +77,17 @@ HostWebGLContext& CanvasHost::as_webgl(Context& context) Optional CanvasHost::create_2d_context(Gfx::IntSize size, bool alpha) { - auto context = create_2d_command_player(size, alpha); - if (!context) + auto command_player = create_2d_command_player(size, alpha); + if (!command_player) return {}; - auto canvas_id = m_canvas_surface_registry.create_canvas_surface(context->surface()); - m_contexts.set(canvas_id, context.release_nonnull()); + auto presented_surface = create_presented_canvas_surface(command_player->surface()); + auto canvas_id = m_canvas_surface_registry.create_canvas_surface(presented_surface); + Canvas2DContext context { + .command_player = command_player.release_nonnull(), + .presented_surface = move(presented_surface), + }; + m_contexts.set(canvas_id, move(context)); return canvas_id; } @@ -104,11 +125,26 @@ CanvasHost::Context* CanvasHost::context(Web::Painting::CanvasId canvas_id) return &it->value; } -void CanvasHost::execute_canvas_2d_commands(Web::Painting::CanvasId canvas_id, Gfx::CanvasCommandList const& commands) +void CanvasHost::present_canvas_2d_context(Web::Painting::CanvasId canvas_id, Canvas2DContext& context) +{ + copy_surface_contents(context.command_player->surface(), context.presented_surface); + m_canvas_surface_registry.set_canvas_surface(canvas_id, context.presented_surface); + context.has_uncommitted_commands = false; +} + +void CanvasHost::execute_canvas_2d_commands(Web::Painting::CanvasId canvas_id, Gfx::CanvasCommandList const& commands, bool commit) { auto* context = this->context(canvas_id); VERIFY(context); - as_2d(*context).play(commands); + + auto& canvas_context = as_2d(*context); + if (!commands.is_empty()) { + canvas_context.command_player->play(commands); + canvas_context.has_uncommitted_commands = true; + } + + if (commit && canvas_context.has_uncommitted_commands) + present_canvas_2d_context(canvas_id, canvas_context); } void CanvasHost::execute_webgl_commands(Web::Painting::CanvasId canvas_id, ByteBuffer const& commands, Vector const& bitmaps) @@ -174,8 +210,8 @@ Gfx::ShareableBitmap CanvasHost::read_back_pixels(Web::Painting::CanvasId canvas return {}; return context->visit( - [rect](Canvas2DContext& player) { - return read_back_surface(player->surface(), rect); + [rect](Canvas2DContext& canvas_context) { + return read_back_surface(canvas_context.command_player->surface(), rect); }, [rect](WebGLContext& webgl_context) { return webgl_context->read_back_drawing_buffer(rect); diff --git a/Services/Compositor/CanvasHost.h b/Services/Compositor/CanvasHost.h index f9a277c49c..a5f8359f4a 100644 --- a/Services/Compositor/CanvasHost.h +++ b/Services/Compositor/CanvasHost.h @@ -50,7 +50,7 @@ public: void destroy_context(Web::Painting::CanvasId); bool has_context(Web::Painting::CanvasId) const; - void execute_canvas_2d_commands(Web::Painting::CanvasId, Gfx::CanvasCommandList const&); + void execute_canvas_2d_commands(Web::Painting::CanvasId, Gfx::CanvasCommandList const&, bool commit); void execute_webgl_commands(Web::Painting::CanvasId, ByteBuffer const&, Vector const&); ErrorOr execute_webgl_sync_call(Web::Painting::CanvasId, ByteBuffer request); Web::WebGL::ReadPixelsResult webgl_read_pixels_robust_angle(Web::Painting::CanvasId, Web::WebGL::GLint x, Web::WebGL::GLint y, Web::WebGL::GLsizei width, Web::WebGL::GLsizei height, Web::WebGL::GLenum format, Web::WebGL::GLenum type, Web::WebGL::GLsizei buf_size, Core::AnonymousBuffer pixels); @@ -60,14 +60,19 @@ public: Gfx::ShareableBitmap read_back_pixels(Web::Painting::CanvasId, Gfx::IntRect); private: - using Canvas2DContext = NonnullOwnPtr; + struct Canvas2DContext { + NonnullOwnPtr command_player; + NonnullRefPtr presented_surface; + bool has_uncommitted_commands { false }; + }; using WebGLContext = NonnullOwnPtr; using Context = Variant; Context* context(Web::Painting::CanvasId); OwnPtr create_2d_command_player(Gfx::IntSize, bool alpha); - static Gfx::CanvasCommandPlayer& as_2d(Context&); + static Canvas2DContext& as_2d(Context&); static HostWebGLContext& as_webgl(Context&); + void present_canvas_2d_context(Web::Painting::CanvasId, Canvas2DContext&); RefPtr m_skia_backend_context; Web::Painting::CanvasSurfaceRegistry& m_canvas_surface_registry; diff --git a/Services/Compositor/CompositorWebContentServer.ipc b/Services/Compositor/CompositorWebContentServer.ipc index ddcb72a363..b59b952a84 100644 --- a/Services/Compositor/CompositorWebContentServer.ipc +++ b/Services/Compositor/CompositorWebContentServer.ipc @@ -32,7 +32,7 @@ endpoint CompositorWebContentServer clear_video_frame(Web::Compositor::CompositorContextId context_id, Web::Painting::VideoFrameResourceId frame_id) =| create_canvas_2d_context(Gfx::IntSize size, bool alpha) => (bool success, Web::Painting::CanvasId canvas_id) - update_canvas_2d_commands(Web::Painting::CanvasId canvas_id, Gfx::CanvasCommandList commands) =| + update_canvas_2d_commands(Web::Painting::CanvasId canvas_id, Gfx::CanvasCommandList commands, bool commit) =| destroy_canvas_context(Web::Painting::CanvasId canvas_id) =| get_canvas_pixels(Web::Painting::CanvasId canvas_id, Gfx::IntRect rect) => (Gfx::ShareableBitmap pixels) diff --git a/Services/Compositor/ConnectionFromWebContent.cpp b/Services/Compositor/ConnectionFromWebContent.cpp index 5455f504ad..dda5a4b345 100644 --- a/Services/Compositor/ConnectionFromWebContent.cpp +++ b/Services/Compositor/ConnectionFromWebContent.cpp @@ -120,9 +120,9 @@ Messages::CompositorWebContentServer::CreateCanvas2dContextResponse ConnectionFr return { true, *canvas_id }; } -void ConnectionFromWebContent::update_canvas_2d_commands(Web::Painting::CanvasId canvas_id, Gfx::CanvasCommandList commands) +void ConnectionFromWebContent::update_canvas_2d_commands(Web::Painting::CanvasId canvas_id, Gfx::CanvasCommandList commands, bool commit) { - m_canvas_host.execute_canvas_2d_commands(canvas_id, commands); + m_canvas_host.execute_canvas_2d_commands(canvas_id, commands, commit); } void ConnectionFromWebContent::destroy_canvas_context(Web::Painting::CanvasId canvas_id) diff --git a/Services/Compositor/ConnectionFromWebContent.h b/Services/Compositor/ConnectionFromWebContent.h index 6e5d2d8ca7..c4a50c2608 100644 --- a/Services/Compositor/ConnectionFromWebContent.h +++ b/Services/Compositor/ConnectionFromWebContent.h @@ -46,7 +46,7 @@ private: virtual void update_video_frame(Web::Compositor::CompositorContextId, Web::Painting::VideoFrameResourceId, NonnullRefPtr) override; virtual void clear_video_frame(Web::Compositor::CompositorContextId, Web::Painting::VideoFrameResourceId) override; virtual Messages::CompositorWebContentServer::CreateCanvas2dContextResponse create_canvas_2d_context(Gfx::IntSize, bool) override; - virtual void update_canvas_2d_commands(Web::Painting::CanvasId, Gfx::CanvasCommandList) override; + virtual void update_canvas_2d_commands(Web::Painting::CanvasId, Gfx::CanvasCommandList, bool commit) override; virtual void destroy_canvas_context(Web::Painting::CanvasId) override; virtual Messages::CompositorWebContentServer::GetCanvasPixelsResponse get_canvas_pixels(Web::Painting::CanvasId, Gfx::IntRect) override; diff --git a/Services/WebContent/CompositorConnection.cpp b/Services/WebContent/CompositorConnection.cpp index a681c16549..ee9f8993f2 100644 --- a/Services/WebContent/CompositorConnection.cpp +++ b/Services/WebContent/CompositorConnection.cpp @@ -97,12 +97,12 @@ Optional CompositorConnection::create_canvas_2d_context return response->canvas_id(); } -void CompositorConnection::update_canvas_2d_commands(Web::Painting::CanvasId canvas_id, Gfx::CanvasCommandList const& commands) +void CompositorConnection::update_canvas_2d_commands(Web::Painting::CanvasId canvas_id, Gfx::CanvasCommandList const& commands, bool commit) { if (!can_send_message_to_compositor()) return; - auto encoded_message = MUST(Messages::CompositorWebContentServer::UpdateCanvas2dCommands::static_encode(canvas_id, commands)); + auto encoded_message = MUST(Messages::CompositorWebContentServer::UpdateCanvas2dCommands::static_encode(canvas_id, commands, commit)); if (post_message(encoded_message).is_error()) did_lose_compositor(); } diff --git a/Services/WebContent/CompositorConnection.h b/Services/WebContent/CompositorConnection.h index 91eef82585..89545d179f 100644 --- a/Services/WebContent/CompositorConnection.h +++ b/Services/WebContent/CompositorConnection.h @@ -46,7 +46,7 @@ public: void update_video_frame(Web::Compositor::CompositorContextId, Web::Painting::VideoFrameResourceId, NonnullRefPtr const&); void clear_video_frame(Web::Compositor::CompositorContextId, Web::Painting::VideoFrameResourceId); Optional create_canvas_2d_context(Gfx::IntSize, bool alpha); - void update_canvas_2d_commands(Web::Painting::CanvasId, Gfx::CanvasCommandList const&); + void update_canvas_2d_commands(Web::Painting::CanvasId, Gfx::CanvasCommandList const&, bool commit); void destroy_canvas_context(Web::Painting::CanvasId); Gfx::ShareableBitmap get_canvas_pixels(Web::Painting::CanvasId, Gfx::IntRect); void invalidate_wheel_event_listener_state(Web::Compositor::CompositorContextId, u64 generation); diff --git a/Services/WebContent/WebContentCompositorHost.cpp b/Services/WebContent/WebContentCompositorHost.cpp index a7058f4139..301a4e7e36 100644 --- a/Services/WebContent/WebContentCompositorHost.cpp +++ b/Services/WebContent/WebContentCompositorHost.cpp @@ -128,11 +128,11 @@ private: m_canvas_id.clear(); } - virtual void update_commands(Gfx::CanvasCommandList const& commands) override + virtual void update_commands(Gfx::CanvasCommandList const& commands, bool commit) override { if (!m_canvas_id.has_value()) return; - m_connection->update_canvas_2d_commands(*m_canvas_id, commands); + m_connection->update_canvas_2d_commands(*m_canvas_id, commands, commit); } virtual RefPtr read_back_pixels(Gfx::IntRect const& rect) override