1
0
Fork 0
mirror of https://github.com/LadybirdBrowser/ladybird.git synced 2025-06-08 05:27:14 +09:00

LibWeb: Protect SkiaBackendContext with a mutex

The Skia Ganesh backend we currently use doesn't support painting from
multiple threads, which could happen before this change when the main
thread used Skia to paint on the HTML canvas while the rendering thread
was working on display list rasterization.

Fixes https://github.com/LadybirdBrowser/ladybird/issues/4172
This commit is contained in:
Aliaksandr Kalenik 2025-04-01 20:18:15 +02:00 committed by Alexander Kalenik
parent 24527b6ae3
commit fd147e6be0
Notes: github-actions[bot] 2025-04-01 21:40:01 +00:00
7 changed files with 98 additions and 29 deletions

View file

@ -1,6 +1,6 @@
/*
* Copyright (c) 2024, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
* Copyright (c) 2024-2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
* Copyright (c) 2024, Lucien Fiorini <lucienfiorini@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
@ -33,9 +33,13 @@ struct PainterSkia::Impl {
{
}
SkCanvas* canvas() const
template<typename Callback>
void with_canvas(Callback&& callback)
{
return &painting_surface->canvas();
painting_surface->lock_context();
auto& canvas = painting_surface->canvas();
callback(canvas);
painting_surface->unlock_context();
}
};
@ -122,14 +126,18 @@ void PainterSkia::clear_rect(Gfx::FloatRect const& rect, Gfx::Color color)
SkPaint paint;
paint.setColor(to_skia_color(color));
paint.setBlendMode(SkBlendMode::kClear);
impl().canvas()->drawRect(to_skia_rect(rect), paint);
impl().with_canvas([&](auto& canvas) {
canvas.drawRect(to_skia_rect(rect), paint);
});
}
void PainterSkia::fill_rect(Gfx::FloatRect const& rect, Color color)
{
SkPaint paint;
paint.setColor(to_skia_color(color));
impl().canvas()->drawRect(to_skia_rect(rect), paint);
impl().with_canvas([&](auto& canvas) {
canvas.drawRect(to_skia_rect(rect), paint);
});
}
void PainterSkia::draw_bitmap(Gfx::FloatRect const& dst_rect, Gfx::ImmutableBitmap const& src_bitmap, Gfx::IntRect const& src_rect, Gfx::ScalingMode scaling_mode, ReadonlySpan<Gfx::Filter> filters, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator)
@ -139,13 +147,15 @@ void PainterSkia::draw_bitmap(Gfx::FloatRect const& dst_rect, Gfx::ImmutableBitm
paint.setAlpha(static_cast<u8>(global_alpha * 255));
paint.setBlender(to_skia_blender(compositing_and_blending_operator));
impl().canvas()->drawImageRect(
src_bitmap.sk_image(),
to_skia_rect(src_rect),
to_skia_rect(dst_rect),
to_skia_sampling_options(scaling_mode),
&paint,
SkCanvas::kStrict_SrcRectConstraint);
impl().with_canvas([&](auto& canvas) {
canvas.drawImageRect(
src_bitmap.sk_image(),
to_skia_rect(src_rect),
to_skia_rect(dst_rect),
to_skia_sampling_options(scaling_mode),
&paint,
SkCanvas::kStrict_SrcRectConstraint);
});
}
void PainterSkia::set_transform(Gfx::AffineTransform const& transform)
@ -155,7 +165,9 @@ void PainterSkia::set_transform(Gfx::AffineTransform const& transform)
transform.b(), transform.d(), transform.f(),
0, 0, 1);
impl().canvas()->setMatrix(matrix);
impl().with_canvas([&](auto& canvas) {
canvas.setMatrix(matrix);
});
}
void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::Color color, float thickness)
@ -170,7 +182,9 @@ void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::Color color, float thi
paint.setStrokeWidth(thickness);
paint.setColor(to_skia_color(color));
auto sk_path = to_skia_path(path);
impl().canvas()->drawPath(sk_path, paint);
impl().with_canvas([&](auto& canvas) {
canvas.drawPath(sk_path, paint);
});
}
void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::Color color, float thickness, float blur_radius, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator)
@ -187,7 +201,9 @@ void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::Color color, float thi
paint.setColor(to_skia_color(color));
paint.setBlender(to_skia_blender(compositing_and_blending_operator));
auto sk_path = to_skia_path(path);
impl().canvas()->drawPath(sk_path, paint);
impl().with_canvas([&](auto& canvas) {
canvas.drawPath(sk_path, paint);
});
}
void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::PaintStyle const& paint_style, ReadonlySpan<Gfx::Filter> filters, float thickness, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator)
@ -204,7 +220,9 @@ void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::PaintStyle const& pain
paint.setStyle(SkPaint::Style::kStroke_Style);
paint.setStrokeWidth(thickness);
paint.setBlender(to_skia_blender(compositing_and_blending_operator));
impl().canvas()->drawPath(sk_path, paint);
impl().with_canvas([&](auto& canvas) {
canvas.drawPath(sk_path, paint);
});
}
void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::PaintStyle const& paint_style, ReadonlySpan<Gfx::Filter> filters, float thickness, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator, Gfx::Path::CapStyle const& cap_style, Gfx::Path::JoinStyle const& join_style)
@ -223,7 +241,9 @@ void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::PaintStyle const& pain
paint.setStrokeCap(to_skia_cap(cap_style));
paint.setStrokeJoin(to_skia_join(join_style));
paint.setBlender(to_skia_blender(compositing_and_blending_operator));
impl().canvas()->drawPath(sk_path, paint);
impl().with_canvas([&](auto& canvas) {
canvas.drawPath(sk_path, paint);
});
}
void PainterSkia::fill_path(Gfx::Path const& path, Gfx::Color color, Gfx::WindingRule winding_rule)
@ -233,7 +253,9 @@ void PainterSkia::fill_path(Gfx::Path const& path, Gfx::Color color, Gfx::Windin
paint.setColor(to_skia_color(color));
auto sk_path = to_skia_path(path);
sk_path.setFillType(to_skia_path_fill_type(winding_rule));
impl().canvas()->drawPath(sk_path, paint);
impl().with_canvas([&](auto& canvas) {
canvas.drawPath(sk_path, paint);
});
}
void PainterSkia::fill_path(Gfx::Path const& path, Gfx::Color color, Gfx::WindingRule winding_rule, float blur_radius, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator)
@ -245,7 +267,9 @@ void PainterSkia::fill_path(Gfx::Path const& path, Gfx::Color color, Gfx::Windin
paint.setBlender(to_skia_blender(compositing_and_blending_operator));
auto sk_path = to_skia_path(path);
sk_path.setFillType(to_skia_path_fill_type(winding_rule));
impl().canvas()->drawPath(sk_path, paint);
impl().with_canvas([&](auto& canvas) {
canvas.drawPath(sk_path, paint);
});
}
void PainterSkia::fill_path(Gfx::Path const& path, Gfx::PaintStyle const& paint_style, ReadonlySpan<Gfx::Filter> filters, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator, Gfx::WindingRule winding_rule)
@ -257,24 +281,32 @@ void PainterSkia::fill_path(Gfx::Path const& path, Gfx::PaintStyle const& paint_
float alpha = paint.getAlphaf();
paint.setAlphaf(alpha * global_alpha);
paint.setBlender(to_skia_blender(compositing_and_blending_operator));
impl().canvas()->drawPath(sk_path, paint);
impl().with_canvas([&](auto& canvas) {
canvas.drawPath(sk_path, paint);
});
}
void PainterSkia::save()
{
impl().canvas()->save();
impl().with_canvas([&](auto& canvas) {
canvas.save();
});
}
void PainterSkia::restore()
{
impl().canvas()->restore();
impl().with_canvas([&](auto& canvas) {
canvas.restore();
});
}
void PainterSkia::clip(Gfx::Path const& path, Gfx::WindingRule winding_rule)
{
auto sk_path = to_skia_path(path);
sk_path.setFillType(to_skia_path_fill_type(winding_rule));
impl().canvas()->clipPath(sk_path, SkClipOp::kIntersect, true);
impl().with_canvas([&](auto& canvas) {
canvas.clipPath(sk_path, SkClipOp::kIntersect, true);
});
}
}

View file

@ -21,6 +21,7 @@
namespace Gfx {
struct PaintingSurface::Impl {
RefPtr<SkiaBackendContext> context;
IntSize size;
sk_sp<SkSurface> surface;
RefPtr<Bitmap> bitmap;
@ -36,12 +37,12 @@ NonnullRefPtr<PaintingSurface> PaintingSurface::create_with_size(RefPtr<SkiaBack
auto bitmap = Bitmap::create(color_type, alpha_type, size).value();
auto surface = SkSurfaces::WrapPixels(image_info, bitmap->begin(), bitmap->pitch());
VERIFY(surface);
return adopt_ref(*new PaintingSurface(make<Impl>(size, surface, bitmap)));
return adopt_ref(*new PaintingSurface(make<Impl>(context, size, surface, bitmap)));
}
auto surface = SkSurfaces::RenderTarget(context->sk_context(), skgpu::Budgeted::kNo, image_info);
VERIFY(surface);
return adopt_ref(*new PaintingSurface(make<Impl>(size, surface, nullptr)));
return adopt_ref(*new PaintingSurface(make<Impl>(context, size, surface, nullptr)));
}
NonnullRefPtr<PaintingSurface> PaintingSurface::wrap_bitmap(Bitmap& bitmap)
@ -51,7 +52,7 @@ NonnullRefPtr<PaintingSurface> PaintingSurface::wrap_bitmap(Bitmap& bitmap)
auto size = bitmap.size();
auto image_info = SkImageInfo::Make(bitmap.width(), bitmap.height(), color_type, alpha_type, SkColorSpace::MakeSRGB());
auto surface = SkSurfaces::WrapPixels(image_info, bitmap.begin(), bitmap.pitch());
return adopt_ref(*new PaintingSurface(make<Impl>(size, surface, bitmap)));
return adopt_ref(*new PaintingSurface(make<Impl>(RefPtr<SkiaBackendContext> {}, size, surface, bitmap)));
}
#ifdef AK_OS_MACOS
@ -75,7 +76,7 @@ NonnullRefPtr<PaintingSurface> PaintingSurface::wrap_iosurface(Core::IOSurfaceHa
VERIFY_NOT_REACHED();
}
auto surface = SkSurfaces::WrapBackendRenderTarget(context->sk_context(), backend_render_target, sk_origin, kBGRA_8888_SkColorType, nullptr, nullptr);
return adopt_ref(*new PaintingSurface(make<Impl>(size, surface, nullptr)));
return adopt_ref(*new PaintingSurface(make<Impl>(context, size, surface, nullptr)));
}
#endif
@ -141,4 +142,18 @@ void PaintingSurface::flush()
on_flush(*this);
}
void PaintingSurface::lock_context() const
{
auto& context = m_impl->context;
if (context)
context->lock();
}
void PaintingSurface::unlock_context() const
{
auto& context = m_impl->context;
if (context)
context->unlock();
}
}

View file

@ -57,6 +57,9 @@ public:
~PaintingSurface();
void lock_context() const;
void unlock_context() const;
private:
struct Impl;

View file

@ -8,6 +8,7 @@
#include <AK/Noncopyable.h>
#include <AK/RefCounted.h>
#include <LibThreading/Mutex.h>
#ifdef USE_VULKAN
# include <LibGfx/VulkanContext.h>
@ -44,6 +45,12 @@ public:
virtual GrDirectContext* sk_context() const = 0;
virtual MetalContext& metal_context() = 0;
void lock() { m_mutex.lock(); }
void unlock() { m_mutex.unlock(); }
private:
Threading::Mutex m_mutex;
};
}

View file

@ -36,6 +36,17 @@ static bool command_is_clip_or_mask(Command const& command)
}
void DisplayListPlayer::execute(DisplayList& display_list, RefPtr<Gfx::PaintingSurface> surface)
{
if (surface) {
surface->lock_context();
}
execute_impl(display_list, surface);
if (surface) {
surface->unlock_context();
}
}
void DisplayListPlayer::execute_impl(DisplayList& display_list, RefPtr<Gfx::PaintingSurface> surface)
{
if (surface)
m_surfaces.append(*surface);

View file

@ -30,6 +30,7 @@ public:
protected:
Gfx::PaintingSurface& surface() const { return m_surfaces.last(); }
void execute_impl(DisplayList&, RefPtr<Gfx::PaintingSurface>);
private:
virtual void flush() = 0;

View file

@ -972,7 +972,7 @@ void DisplayListPlayerSkia::add_mask(AddMask const& command)
auto mask_surface = Gfx::PaintingSurface::create_with_size(m_context, rect.size(), Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied);
execute(*command.display_list, mask_surface);
execute_impl(*command.display_list, mask_surface);
SkMatrix mask_matrix;
mask_matrix.setTranslate(rect.x(), rect.y());
@ -985,7 +985,7 @@ void DisplayListPlayerSkia::paint_nested_display_list(PaintNestedDisplayList con
{
auto& canvas = surface().canvas();
canvas.translate(command.rect.x(), command.rect.y());
execute(*command.display_list, {});
execute_impl(*command.display_list, {});
}
void DisplayListPlayerSkia::paint_scrollbar(PaintScrollBar const& command)