LibGfx+LibWeb: Reuse DisplayListPlayer and PaintingSurface when possible

Previously, we were reinstantiating the DisplayListPlayer and
PaintingSurface on every paint.
This commit is contained in:
Jelle Raaijmakers 2025-01-29 10:24:57 +01:00 committed by Alexander Kalenik
commit 342cb7addf
Notes: github-actions[bot] 2025-01-31 12:29:12 +00:00
12 changed files with 107 additions and 104 deletions

View file

@ -24,7 +24,6 @@ struct PaintingSurface::Impl {
IntSize size;
sk_sp<SkSurface> surface;
RefPtr<Bitmap> bitmap;
RefPtr<SkiaBackendContext> context;
};
NonnullRefPtr<PaintingSurface> PaintingSurface::create_with_size(RefPtr<SkiaBackendContext> context, IntSize size, BitmapFormat color_type, AlphaType alpha_type)
@ -37,12 +36,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, context)));
return adopt_ref(*new PaintingSurface(make<Impl>(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, context)));
return adopt_ref(*new PaintingSurface(make<Impl>(size, surface, nullptr)));
}
NonnullRefPtr<PaintingSurface> PaintingSurface::wrap_bitmap(Bitmap& bitmap)
@ -52,7 +51,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, nullptr)));
return adopt_ref(*new PaintingSurface(make<Impl>(size, surface, bitmap)));
}
#ifdef AK_OS_MACOS
@ -76,7 +75,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, context)));
return adopt_ref(*new PaintingSurface(make<Impl>(size, surface, nullptr)));
}
#endif
@ -136,11 +135,10 @@ sk_sp<SkImage> PaintingSurface::sk_image_snapshot() const
return m_impl->surface->makeImageSnapshot();
}
void PaintingSurface::flush() const
void PaintingSurface::flush()
{
if (auto context = m_impl->context) {
context->flush_and_submit(m_impl->surface.get());
}
if (on_flush)
on_flush(*this);
}
}

View file

@ -6,6 +6,7 @@
#pragma once
#include <AK/Function.h>
#include <AK/NonnullOwnPtr.h>
#include <AK/RefCounted.h>
#include <AK/RefPtr.h>
@ -29,6 +30,8 @@ public:
BottomLeft,
};
Function<void(PaintingSurface&)> on_flush;
static NonnullRefPtr<PaintingSurface> create_with_size(RefPtr<SkiaBackendContext> context, IntSize size, BitmapFormat color_type, AlphaType alpha_type);
static NonnullRefPtr<PaintingSurface> wrap_bitmap(Bitmap&);
@ -50,7 +53,7 @@ public:
template<typename T>
T sk_image_snapshot() const;
void flush() const;
void flush();
~PaintingSurface();

View file

@ -6,6 +6,7 @@
#include <AK/NonnullOwnPtr.h>
#include <AK/RefPtr.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/SkiaBackendContext.h>
#include <core/SkSurface.h>

View file

@ -170,7 +170,7 @@ public:
CSSPixelPoint viewport_scroll_offset() const { return m_viewport_scroll_offset; }
CSSPixelRect viewport_rect() const { return { m_viewport_scroll_offset, m_size }; }
void set_viewport_size(CSSPixelSize);
virtual void set_viewport_size(CSSPixelSize);
void perform_scroll_of_viewport(CSSPixelPoint position);
void set_needs_display(InvalidateDisplayList = InvalidateDisplayList::Yes);

View file

@ -53,8 +53,12 @@ TraversableNavigable::TraversableNavigable(GC::Ref<Page> page)
, m_session_history_traversal_queue(vm().heap().allocate<SessionHistoryTraversalQueue>())
{
auto display_list_player_type = page->client().display_list_player_type();
if (display_list_player_type == DisplayListPlayerType::SkiaGPUIfAvailable)
if (display_list_player_type == DisplayListPlayerType::SkiaGPUIfAvailable) {
m_skia_backend_context = get_skia_backend_context();
m_skia_player = make<Painting::DisplayListPlayerSkia>(m_skia_backend_context);
} else {
m_skia_player = make<Painting::DisplayListPlayerSkia>();
}
}
TraversableNavigable::~TraversableNavigable() = default;
@ -1384,6 +1388,45 @@ GC::Ptr<DOM::Node> TraversableNavigable::currently_focused_area()
return candidate;
}
void TraversableNavigable::set_viewport_size(CSSPixelSize size)
{
Navigable::set_viewport_size(size);
// Invalidate the surface cache if the traversable changed size.
m_bitmap_to_surface.clear();
}
NonnullRefPtr<Gfx::PaintingSurface> TraversableNavigable::painting_surface_for_backing_store(Painting::BackingStore& backing_store)
{
auto& bitmap = backing_store.bitmap();
auto cached_surface = m_bitmap_to_surface.find(&bitmap);
if (cached_surface != m_bitmap_to_surface.end())
return cached_surface->value;
RefPtr<Gfx::PaintingSurface> new_surface;
if (page().client().display_list_player_type() == DisplayListPlayerType::SkiaGPUIfAvailable && m_skia_backend_context) {
#ifdef USE_VULKAN
// Vulkan: Try to create an accelerated surface.
new_surface = Gfx::PaintingSurface::create_with_size(m_skia_backend_context, backing_store.size(), Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied);
new_surface->on_flush = [&bitmap = bitmap](auto& surface) { surface.read_into_bitmap(bitmap); };
#endif
#ifdef AK_OS_MACOS
// macOS: Wrap an IOSurface if available.
if (is<Painting::IOSurfaceBackingStore>(backing_store)) {
auto& iosurface_backing_store = static_cast<Painting::IOSurfaceBackingStore&>(backing_store);
new_surface = Gfx::PaintingSurface::wrap_iosurface(iosurface_backing_store.iosurface_handle(), *m_skia_backend_context);
}
#endif
}
// CPU and fallback: wrap the backing store bitmap directly.
if (!new_surface)
new_surface = Gfx::PaintingSurface::wrap_bitmap(bitmap);
m_bitmap_to_surface.set(&bitmap, *new_surface);
return *new_surface;
}
void TraversableNavigable::paint(DevicePixelRect const& content_rect, Painting::BackingStore& target, PaintOptions paint_options)
{
auto document = active_document();
@ -1391,10 +1434,9 @@ void TraversableNavigable::paint(DevicePixelRect const& content_rect, Painting::
return;
for (auto& navigable : all_navigables()) {
if (auto active_document = navigable->active_document(); active_document && active_document->paintable()) {
if (auto active_document = navigable->active_document(); active_document && active_document->paintable())
active_document->paintable()->refresh_scroll_state();
}
}
DOM::Document::PaintConfig paint_config;
paint_config.paint_overlay = paint_options.paint_overlay == PaintOptions::PaintOverlay::Yes;
@ -1402,43 +1444,12 @@ void TraversableNavigable::paint(DevicePixelRect const& content_rect, Painting::
paint_config.has_focus = paint_options.has_focus;
paint_config.canvas_fill_rect = Gfx::IntRect { {}, content_rect.size() };
auto display_list = document->record_display_list(paint_config);
if (!display_list) {
if (!display_list)
return;
}
switch (page().client().display_list_player_type()) {
case DisplayListPlayerType::SkiaGPUIfAvailable: {
#ifdef USE_VULKAN
if (m_skia_backend_context) {
Painting::DisplayListPlayerSkia player(*m_skia_backend_context, target.bitmap());
player.execute(*display_list);
return;
}
#endif
#ifdef AK_OS_MACOS
if (m_skia_backend_context && is<Painting::IOSurfaceBackingStore>(target)) {
auto& iosurface_backing_store = static_cast<Painting::IOSurfaceBackingStore&>(target);
auto painting_surface = Gfx::PaintingSurface::wrap_iosurface(iosurface_backing_store.iosurface_handle(), *m_skia_backend_context);
Painting::DisplayListPlayerSkia player(*m_skia_backend_context, painting_surface);
player.execute(*display_list);
return;
}
#endif
// Fallback to CPU backend if GPU is not available
Painting::DisplayListPlayerSkia player(target.bitmap());
player.execute(*display_list);
break;
}
case DisplayListPlayerType::SkiaCPU: {
Painting::DisplayListPlayerSkia player(target.bitmap());
player.execute(*display_list);
break;
}
default:
VERIFY_NOT_REACHED();
}
auto painting_surface = painting_surface_for_backing_store(target);
m_skia_player->set_surface(painting_surface);
m_skia_player->execute(*display_list);
}
}

View file

@ -14,15 +14,16 @@
#include <LibWeb/Page/Page.h>
#include <LibWeb/Painting/DisplayListPlayerSkia.h>
#include <LibWeb/StorageAPI/StorageShed.h>
#ifdef USE_VULKAN
# include <LibGfx/VulkanContext.h>
#endif
#include <WebContent/BackingStoreManager.h>
#ifdef AK_OS_MACOS
# include <LibGfx/MetalContext.h>
#endif
#ifdef USE_VULKAN
# include <LibGfx/VulkanContext.h>
#endif
namespace Web::HTML {
// https://html.spec.whatwg.org/multipage/document-sequences.html#traversable-navigable
@ -110,6 +111,8 @@ public:
StorageAPI::StorageShed& storage_shed() { return m_storage_shed; }
StorageAPI::StorageShed const& storage_shed() const { return m_storage_shed; }
void set_viewport_size(CSSPixelSize) override;
private:
TraversableNavigable(GC::Ref<Page>);
@ -131,6 +134,8 @@ private:
[[nodiscard]] bool can_go_forward() const;
NonnullRefPtr<Gfx::PaintingSurface> painting_surface_for_backing_store(Painting::BackingStore&);
// https://html.spec.whatwg.org/multipage/document-sequences.html#tn-current-session-history-step
int m_current_session_history_step { 0 };
@ -154,6 +159,8 @@ private:
String m_window_handle;
RefPtr<Gfx::SkiaBackendContext> m_skia_backend_context;
OwnPtr<Painting::DisplayListPlayerSkia> m_skia_player;
HashMap<Gfx::Bitmap*, NonnullRefPtr<Gfx::PaintingSurface>> m_bitmap_to_surface;
};
struct BrowsingContextAndDocument {

View file

@ -41,6 +41,8 @@ void DisplayListPlayer::execute(DisplayList& display_list)
auto const& scroll_state = display_list.scroll_state();
auto device_pixels_per_css_pixel = display_list.device_pixels_per_css_pixel();
VERIFY(m_surface);
size_t next_command_index = 0;
while (next_command_index < commands.size()) {
auto scroll_frame_id = commands[next_command_index].scroll_frame_id;
@ -128,6 +130,8 @@ void DisplayListPlayer::execute(DisplayList& display_list)
else VERIFY_NOT_REACHED();
// clang-format on
}
flush();
}
}

View file

@ -1,5 +1,6 @@
/*
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -25,9 +26,14 @@ class DisplayListPlayer {
public:
virtual ~DisplayListPlayer() = default;
void execute(DisplayList& display_list);
void execute(DisplayList&);
void set_surface(NonnullRefPtr<Gfx::PaintingSurface> surface) { m_surface = surface; }
protected:
Gfx::PaintingSurface& surface() const { return *m_surface; }
private:
virtual void flush() = 0;
virtual void draw_glyph_run(DrawGlyphRun const&) = 0;
virtual void fill_rect(FillRect const&) = 0;
virtual void draw_painting_surface(DrawPaintingSurface const&) = 0;
@ -65,6 +71,8 @@ private:
virtual void apply_transform(ApplyTransform const&) = 0;
virtual void apply_mask_bitmap(ApplyMaskBitmap const&) = 0;
virtual bool would_be_fully_clipped_by_painter(Gfx::IntRect) const = 0;
RefPtr<Gfx::PaintingSurface> m_surface;
};
class DisplayList : public RefCounted<DisplayList> {

View file

@ -1,5 +1,6 @@
/*
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -29,36 +30,13 @@
namespace Web::Painting {
#ifdef USE_VULKAN
DisplayListPlayerSkia::DisplayListPlayerSkia(Gfx::SkiaBackendContext& context, Gfx::Bitmap& bitmap)
DisplayListPlayerSkia::DisplayListPlayerSkia(RefPtr<Gfx::SkiaBackendContext> context)
: m_context(context)
{
m_surface = Gfx::PaintingSurface::create_with_size(m_context, bitmap.size(), Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied);
m_flush_context = [&bitmap, surface = m_surface] mutable {
surface->read_into_bitmap(bitmap);
};
}
#endif
#ifdef AK_OS_MACOS
DisplayListPlayerSkia::DisplayListPlayerSkia(Gfx::SkiaBackendContext& context, NonnullRefPtr<Gfx::PaintingSurface> surface)
: m_context(context)
, m_surface(move(surface))
{
}
#endif
DisplayListPlayerSkia::DisplayListPlayerSkia(Gfx::Bitmap& bitmap)
{
m_surface = Gfx::PaintingSurface::wrap_bitmap(bitmap);
}
DisplayListPlayerSkia::~DisplayListPlayerSkia()
DisplayListPlayerSkia::DisplayListPlayerSkia()
{
m_surface->flush();
if (m_flush_context) {
m_flush_context();
}
}
static SkRRect to_skia_rrect(auto const& rect, CornerRadii const& corner_radii)
@ -88,9 +66,11 @@ static SkMatrix to_skia_matrix(Gfx::AffineTransform const& affine_transform)
return matrix;
}
Gfx::PaintingSurface& DisplayListPlayerSkia::surface() const
void DisplayListPlayerSkia::flush()
{
return *m_surface;
if (m_context)
m_context->flush_and_submit(&surface().sk_surface());
surface().flush();
}
void DisplayListPlayerSkia::draw_glyph_run(DrawGlyphRun const& command)
@ -850,10 +830,10 @@ 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);
auto previous_surface = move(m_surface);
m_surface = mask_surface;
NonnullRefPtr old_surface = surface();
set_surface(mask_surface);
execute(*command.display_list);
m_surface = move(previous_surface);
set_surface(old_surface);
SkMatrix mask_matrix;
mask_matrix.setTranslate(rect.x(), rect.y());

View file

@ -14,21 +14,13 @@ class GrDirectContext;
namespace Web::Painting {
class DisplayListPlayerSkia : public DisplayListPlayer {
class DisplayListPlayerSkia final : public DisplayListPlayer {
public:
DisplayListPlayerSkia(Gfx::Bitmap&);
#ifdef USE_VULKAN
DisplayListPlayerSkia(Gfx::SkiaBackendContext&, Gfx::Bitmap&);
#endif
#ifdef AK_OS_MACOS
DisplayListPlayerSkia(Gfx::SkiaBackendContext&, NonnullRefPtr<Gfx::PaintingSurface>);
#endif
virtual ~DisplayListPlayerSkia() override;
DisplayListPlayerSkia(RefPtr<Gfx::SkiaBackendContext>);
DisplayListPlayerSkia();
private:
void flush() override;
void draw_glyph_run(DrawGlyphRun const&) override;
void fill_rect(FillRect const&) override;
void draw_painting_surface(DrawPaintingSurface const&) override;
@ -68,12 +60,7 @@ private:
bool would_be_fully_clipped_by_painter(Gfx::IntRect) const override;
Gfx::PaintingSurface& surface() const;
RefPtr<Gfx::SkiaBackendContext> m_context {};
RefPtr<Gfx::PaintingSurface> m_surface {};
Function<void()> m_flush_context;
RefPtr<Gfx::SkiaBackendContext> m_context;
};
}

View file

@ -90,7 +90,9 @@ RefPtr<Gfx::ImmutableBitmap> SVGMaskable::calculate_mask_of_svg(PaintContext& co
paint_context.set_svg_transform(graphics_element.get_transform());
paint_context.set_draw_svg_geometry_for_clip_path(is<SVGClipPaintable>(paintable));
StackingContext::paint_svg(paint_context, paintable, PaintPhase::Foreground);
DisplayListPlayerSkia display_list_player { *mask_bitmap };
auto painting_surface = Gfx::PaintingSurface::wrap_bitmap(*mask_bitmap);
DisplayListPlayerSkia display_list_player;
display_list_player.set_surface(painting_surface);
display_list_player.execute(display_list);
return mask_bitmap;
};

View file

@ -103,7 +103,9 @@ RefPtr<Gfx::Bitmap> SVGDecodedImageData::render(Gfx::IntSize size) const
switch (painting_command_executor_type) {
case DisplayListPlayerType::SkiaGPUIfAvailable:
case DisplayListPlayerType::SkiaCPU: {
Painting::DisplayListPlayerSkia display_list_player { *bitmap };
auto painting_surface = Gfx::PaintingSurface::wrap_bitmap(*bitmap);
Painting::DisplayListPlayerSkia display_list_player;
display_list_player.set_surface(painting_surface);
display_list_player.execute(*display_list);
break;
}