ladybird/Libraries/LibGfx/CanvasCommandPlayer.cpp
Aliaksandr Kalenik be9ee28afc LibGfx: Stop caching Skia images during canvas playback
The decoded-frame Skia image cache is useful for display-list
rasterization because decoded image resources can be replayed over many
frames. The cache lets that path reuse SkImage wrappers and GPU-backed
copies instead of rebuilding them whenever the same resource is painted.

For canvas, commands are consumed into one backing surface and decoded
frames are already held by the command or paint style for the draw. A
per-painter cache does not match that usage model, and can keep decoded
frames and Skia images alive after the draw has consumed them.

This removes the cache from PainterSkia and drops the now-unused pruning
hook from CanvasCommandPlayer. With the cache gone, PainterSkia can hold
its painting surface directly instead of allocating a private Impl.
DisplayListPlayerSkia keeps owning the cache, so display-list
rasterization keeps the SkImage reuse behavior.
2026-06-16 00:38:30 +02:00

131 lines
5.1 KiB
C++

/*
* Copyright (c) 2026, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Assertions.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/CanvasCommandPlayer.h>
#include <LibGfx/Color.h>
#include <LibGfx/PainterSkia.h>
#include <LibGfx/PaintingSurface.h>
namespace Gfx {
CanvasCommandPlayer::CanvasCommandPlayer(RefPtr<SkiaBackendContext> skia_backend_context, IntSize size, BitmapFormat format, AlphaType alpha_type)
: m_surface(PaintingSurface::create_with_size(size, format, alpha_type, move(skia_backend_context)))
, m_painter(make<PainterSkia>(*m_surface))
{
}
CanvasCommandPlayer::~CanvasCommandPlayer() = default;
NonnullRefPtr<PaintingSurface> CanvasCommandPlayer::surface() const
{
return m_surface;
}
void CanvasCommandPlayer::clear(Color color)
{
m_painter->clear_rect({ {}, m_surface->size().to_type<float>() }, color);
}
void CanvasCommandPlayer::play(CanvasCommandList const& command_list)
{
for (auto const& command : command_list.commands())
command.visit([&](auto const& command) { play_command(command); });
}
void CanvasCommandPlayer::play_command(CanvasCommands::ClearRect const& command)
{
m_painter->clear_rect(command.rect, command.color);
}
void CanvasCommandPlayer::play_command(CanvasCommands::FillRect const& command)
{
m_painter->fill_rect(command.rect, command.color);
}
void CanvasCommandPlayer::play_command(CanvasCommands::DrawBitmap const& command)
{
m_painter->draw_bitmap(command.dst_rect, command.frame, command.src_rect, command.scaling_mode, command.filter, command.global_alpha, command.compositing_and_blending_operator);
}
void CanvasCommandPlayer::play_command(CanvasCommands::FillPath const& command)
{
// Shadows are recorded as blurred solid-color fills; everything else goes through the general paint-style overload.
if (command.blur_radius > 0 && command.style.has<Color>()) {
m_painter->fill_path(command.path, command.style.get<Color>(), command.winding_rule, command.blur_radius, command.compositing_and_blending_operator);
return;
}
m_painter->fill_path(command.path, resolve_paint_style(command.style), command.filter, command.global_alpha, command.compositing_and_blending_operator, command.winding_rule);
}
void CanvasCommandPlayer::play_command(CanvasCommands::StrokePath const& command)
{
if (command.blur_radius > 0 && command.style.has<Color>()) {
m_painter->stroke_path(command.path, command.style.get<Color>(), command.thickness, command.blur_radius, command.compositing_and_blending_operator, command.cap_style, command.join_style, command.miter_limit, command.dash_array, command.dash_offset);
return;
}
m_painter->stroke_path(command.path, resolve_paint_style(command.style), command.filter, command.thickness, command.global_alpha, command.compositing_and_blending_operator, command.cap_style, command.join_style, command.miter_limit, command.dash_array, command.dash_offset);
}
void CanvasCommandPlayer::play_command(CanvasCommands::SetTransform const& command)
{
m_painter->set_transform(command.transform);
}
void CanvasCommandPlayer::play_command(CanvasCommands::Save const&)
{
m_painter->save();
}
void CanvasCommandPlayer::play_command(CanvasCommands::Restore const&)
{
m_painter->restore();
}
void CanvasCommandPlayer::play_command(CanvasCommands::ClipPath const& command)
{
m_painter->clip(command.path, command.winding_rule);
}
void CanvasCommandPlayer::play_command(CanvasCommands::Reset const&)
{
m_painter->reset();
}
NonnullRefPtr<PaintStyle> CanvasCommandPlayer::resolve_paint_style(CanvasPaintStyle const& style) const
{
auto with_color_stops = [](auto paint_style, auto const& gradient) -> NonnullRefPtr<PaintStyle> {
paint_style->set_color_stops(Vector<ColorStop> { gradient.color_stops });
if (gradient.repeat_length.has_value())
paint_style->set_repeat_length(*gradient.repeat_length);
return paint_style;
};
return style.visit(
[](Color const& color) -> NonnullRefPtr<PaintStyle> {
return MUST(SolidColorPaintStyle::create(color));
},
[&](CanvasLinearGradient const& gradient) -> NonnullRefPtr<PaintStyle> {
return with_color_stops(MUST(CanvasLinearGradientPaintStyle::create(gradient.start_point, gradient.end_point)), gradient);
},
[&](CanvasRadialGradient const& gradient) -> NonnullRefPtr<PaintStyle> {
return with_color_stops(MUST(CanvasRadialGradientPaintStyle::create(gradient.start_center, gradient.start_radius, gradient.end_center, gradient.end_radius)), gradient);
},
[&](CanvasConicGradient const& gradient) -> NonnullRefPtr<PaintStyle> {
return with_color_stops(MUST(CanvasConicGradientPaintStyle::create(gradient.center, gradient.start_angle)), gradient);
},
[&](CanvasPatternStyle const& pattern) -> NonnullRefPtr<PaintStyle> {
auto paint_style = MUST(CanvasPatternPaintStyle::create(pattern.image, pattern.repetition));
if (pattern.transform.has_value())
paint_style->set_transform(*pattern.transform);
return paint_style;
});
}
}