LibGfx+LibWeb+Compositor+WebContent: Commit 2D canvas flushes atomically
2D canvas contexts started publishing partial frames after canvas rasterization moved into the Compositor. WebContent still splits large recorded command lists after 64 commands, but every split batch was sent through the same compositor path as the end-of-frame flush. The compositor replayed each batch into the DrawCanvas source surface, so a pending present could sample a canvas after clear and before the rest of the next frame had been drawn. Canvas-heavy pages such as slither.com then flickered between partial and complete frames. Carry an explicit commit bit with 2D canvas command updates. Non-commit batches now update a hidden working canvas in the Compositor, while the display-list-visible surface keeps the last committed canvas contents. The end-of-frame canvas preparation sends the commit boundary, including the empty-commit case needed when the auto-flush consumed all recorded commands before prepare_for_compositing() runs.
This commit is contained in:
parent
cb88229c3f
commit
a08734ecbc
13 changed files with 114 additions and 33 deletions
|
|
@ -9,7 +9,11 @@
|
|||
#include <LibGfx/SharedImageBuffer.h>
|
||||
#include <LibGfx/SkiaUtils.h>
|
||||
|
||||
#include <core/SkCanvas.h>
|
||||
#include <core/SkColorSpace.h>
|
||||
#include <core/SkImage.h>
|
||||
#include <core/SkPaint.h>
|
||||
#include <core/SkRect.h>
|
||||
#include <core/SkSurface.h>
|
||||
#include <gpu/ganesh/GrBackendSurface.h>
|
||||
#include <gpu/ganesh/GrDirectContext.h>
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ public:
|
|||
virtual bool create_context(Gfx::IntSize, bool alpha) = 0;
|
||||
virtual Optional<Painting::CanvasId> 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<Gfx::Bitmap> read_back_pixels(Gfx::IntRect const&) = 0;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -241,7 +241,7 @@ WebIDL::ExceptionOr<void> 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<Gfx::Bitmap> 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<Painting::CanvasId> 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;
|
||||
|
|
|
|||
|
|
@ -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<float> 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<RemoteCanvas2DTransport> 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 };
|
||||
|
|
|
|||
|
|
@ -45,11 +45,27 @@ OwnPtr<Gfx::CanvasCommandPlayer> 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<Canvas2DContext>();
|
||||
VERIFY(player);
|
||||
return **player;
|
||||
destination.copy_from_surface(source);
|
||||
}
|
||||
|
||||
static NonnullRefPtr<Gfx::PaintingSurface> 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<Canvas2DContext>();
|
||||
VERIFY(canvas_context);
|
||||
return *canvas_context;
|
||||
}
|
||||
|
||||
HostWebGLContext& CanvasHost::as_webgl(Context& context)
|
||||
|
|
@ -61,12 +77,17 @@ HostWebGLContext& CanvasHost::as_webgl(Context& context)
|
|||
|
||||
Optional<Web::Painting::CanvasId> 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<Gfx::DecodedImageFrame> 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);
|
||||
|
|
|
|||
|
|
@ -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<Gfx::DecodedImageFrame> const&);
|
||||
ErrorOr<ByteBuffer> 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<Gfx::CanvasCommandPlayer>;
|
||||
struct Canvas2DContext {
|
||||
NonnullOwnPtr<Gfx::CanvasCommandPlayer> command_player;
|
||||
NonnullRefPtr<Gfx::PaintingSurface> presented_surface;
|
||||
bool has_uncommitted_commands { false };
|
||||
};
|
||||
using WebGLContext = NonnullOwnPtr<HostWebGLContext>;
|
||||
using Context = Variant<Canvas2DContext, WebGLContext>;
|
||||
|
||||
Context* context(Web::Painting::CanvasId);
|
||||
OwnPtr<Gfx::CanvasCommandPlayer> 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<Gfx::SkiaBackendContext> m_skia_backend_context;
|
||||
Web::Painting::CanvasSurfaceRegistry& m_canvas_surface_registry;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ private:
|
|||
virtual void update_video_frame(Web::Compositor::CompositorContextId, Web::Painting::VideoFrameResourceId, NonnullRefPtr<Media::VideoFrame const>) 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;
|
||||
|
||||
|
|
|
|||
|
|
@ -97,12 +97,12 @@ Optional<Web::Painting::CanvasId> 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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ public:
|
|||
void update_video_frame(Web::Compositor::CompositorContextId, Web::Painting::VideoFrameResourceId, NonnullRefPtr<Media::VideoFrame const> const&);
|
||||
void clear_video_frame(Web::Compositor::CompositorContextId, Web::Painting::VideoFrameResourceId);
|
||||
Optional<Web::Painting::CanvasId> 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);
|
||||
|
|
|
|||
|
|
@ -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<Gfx::Bitmap> read_back_pixels(Gfx::IntRect const& rect) override
|
||||
|
|
|
|||
Loading…
Reference in a new issue