LibWeb: Add optimized painting command for repeated background

With this change, instead of recording a display list item for each
instance of a repeated background, a new DrawRepeatedImmutableBitmap
type is used. This allows the painter to use optimized repeated image
painting and, when the GPU backend is used, avoid re-uploading the image
texture for each repetition.

Some screenshot tests are affected, but there are no visible
regressions.

https://null.com/games/chainstaff works a lof faster with this change.
This commit is contained in:
Aliaksandr Kalenik 2024-07-23 15:36:28 +03:00 committed by Andreas Kling
parent a4b289ebac
commit c5afe70f77
Notes: github-actions[bot] 2024-07-24 09:13:52 +00:00
13 changed files with 74 additions and 0 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 364 KiB

After

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 272 KiB

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 114 KiB

View File

@ -141,6 +141,11 @@ void ImageStyleValue::paint(PaintContext& context, DevicePixelRect const& dest_r
}
}
Gfx::ImmutableBitmap const* ImageStyleValue::current_frame_bitmap(DevicePixelRect const& dest_rect) const
{
return bitmap(m_current_frame_index, dest_rect.size().to_type<int>());
}
JS::GCPtr<HTML::DecodedImageData> ImageStyleValue::image_data() const
{
if (!m_image_request)

View File

@ -48,6 +48,7 @@ public:
void paint(PaintContext& context, DevicePixelRect const& dest_rect, CSS::ImageRendering image_rendering, Vector<Gfx::Path> const& clip_paths = {}) const override;
virtual Optional<Gfx::Color> color_if_single_pixel_bitmap() const override;
Gfx::ImmutableBitmap const* current_frame_bitmap(DevicePixelRect const& dest_rect) const;
Function<void()> on_animate;

View File

@ -351,6 +351,8 @@ void paint_background(PaintContext& context, Layout::NodeWithStyleAndBoxModelMet
// Repetition
bool repeat_x = false;
bool repeat_y = false;
bool repeat_x_has_gap = false;
bool repeat_y_has_gap = false;
CSSPixels x_step = 0;
CSSPixels y_step = 0;
@ -368,6 +370,7 @@ void paint_background(PaintContext& context, Layout::NodeWithStyleAndBoxModelMet
auto space = fmod(background_positioning_area.width().to_double(), image_rect.width().to_double());
x_step = image_rect.width() + CSSPixels::nearest_value_for(space / static_cast<double>(whole_images - 1));
repeat_x = true;
repeat_x_has_gap = true;
}
break;
}
@ -399,6 +402,7 @@ void paint_background(PaintContext& context, Layout::NodeWithStyleAndBoxModelMet
auto space = fmod(background_positioning_area.height().to_float(), image_rect.height().to_float());
y_step = image_rect.height() + CSSPixels::nearest_value_for(static_cast<double>(space) / static_cast<double>(whole_images - 1));
repeat_y = true;
repeat_y_has_gap = true;
}
break;
}
@ -456,6 +460,13 @@ void paint_background(PaintContext& context, Layout::NodeWithStyleAndBoxModelMet
}
});
display_list_recorder.fill_rect(fill_rect->to_type<int>(), color.value(), clip_paths);
} else if (is<CSS::ImageStyleValue>(image) && repeat_x && repeat_y && !repeat_x_has_gap && !repeat_y_has_gap) {
// Use a dedicated painting command for repeated images instead of recording a separate command for each instance
// of a repeated background, so the painter has the opportunity to optimize the painting of repeated images.
auto dest_rect = context.rounded_device_rect(image_rect);
auto const* bitmap = static_cast<CSS::ImageStyleValue const&>(image).current_frame_bitmap(dest_rect);
auto scaling_mode = to_gfx_scaling_mode(image_rendering, bitmap->rect(), dest_rect.to_type<int>());
context.display_list_recorder().draw_repeated_immutable_bitmap(dest_rect.to_type<int>(), *bitmap, scaling_mode, { .x = repeat_x, .y = repeat_y }, clip_paths);
} else {
for_each_image_device_rect([&](auto const& image_device_rect) {
image.paint(context, image_device_rect, image_rendering, clip_paths);

View File

@ -75,6 +75,21 @@ struct DrawScaledImmutableBitmap {
void translate_by(Gfx::IntPoint const& offset) { dst_rect.translate_by(offset); }
};
struct DrawRepeatedImmutableBitmap {
struct Repeat {
bool x { false };
bool y { false };
};
Gfx::IntRect dst_rect;
NonnullRefPtr<Gfx::ImmutableBitmap> bitmap;
Gfx::ScalingMode scaling_mode;
Repeat repeat;
Vector<Gfx::Path> clip_paths;
void translate_by(Gfx::IntPoint const& offset) { dst_rect.translate_by(offset); }
};
struct Save { };
struct Restore { };
@ -352,6 +367,7 @@ using Command = Variant<
FillRect,
DrawScaledBitmap,
DrawScaledImmutableBitmap,
DrawRepeatedImmutableBitmap,
Save,
Restore,
AddClipRect,

View File

@ -152,6 +152,7 @@ void DisplayList::execute(DisplayListPlayer& executor)
else HANDLE_COMMAND(FillRect, fill_rect)
else HANDLE_COMMAND(DrawScaledBitmap, draw_scaled_bitmap)
else HANDLE_COMMAND(DrawScaledImmutableBitmap, draw_scaled_immutable_bitmap)
else HANDLE_COMMAND(DrawRepeatedImmutableBitmap, draw_repeated_immutable_bitmap)
else HANDLE_COMMAND(AddClipRect, add_clip_rect)
else HANDLE_COMMAND(Save, save)
else HANDLE_COMMAND(Restore, restore)

View File

@ -45,6 +45,7 @@ public:
virtual CommandResult fill_rect(FillRect const&) = 0;
virtual CommandResult draw_scaled_bitmap(DrawScaledBitmap const&) = 0;
virtual CommandResult draw_scaled_immutable_bitmap(DrawScaledImmutableBitmap const&) = 0;
virtual CommandResult draw_repeated_immutable_bitmap(DrawRepeatedImmutableBitmap const&) = 0;
virtual CommandResult save(Save const&) = 0;
virtual CommandResult restore(Restore const&) = 0;
virtual CommandResult add_clip_rect(AddClipRect const&) = 0;

View File

@ -459,6 +459,31 @@ CommandResult DisplayListPlayerSkia::draw_scaled_immutable_bitmap(DrawScaledImmu
return CommandResult::Continue;
}
CommandResult DisplayListPlayerSkia::draw_repeated_immutable_bitmap(DrawRepeatedImmutableBitmap const& command)
{
APPLY_PATH_CLIP_IF_NEEDED
auto bitmap = to_skia_bitmap(command.bitmap->bitmap());
auto image = SkImages::RasterFromBitmap(bitmap);
SkMatrix matrix;
auto dst_rect = command.dst_rect.to_type<float>();
auto src_size = command.bitmap->size().to_type<float>();
matrix.setScale(dst_rect.width() / src_size.width(), dst_rect.height() / src_size.height());
matrix.postTranslate(dst_rect.x(), dst_rect.y());
auto sampling_options = to_skia_sampling_options(command.scaling_mode);
auto tile_mode_x = command.repeat.x ? SkTileMode::kRepeat : SkTileMode::kDecal;
auto tile_mode_y = command.repeat.y ? SkTileMode::kRepeat : SkTileMode::kDecal;
auto shader = image->makeShader(tile_mode_x, tile_mode_y, sampling_options, matrix);
SkPaint paint;
paint.setShader(shader);
auto& canvas = surface().canvas();
canvas.drawPaint(paint);
return CommandResult::Continue;
}
CommandResult DisplayListPlayerSkia::add_clip_rect(AddClipRect const& command)
{
auto& canvas = surface().canvas();

View File

@ -37,6 +37,7 @@ public:
CommandResult fill_rect(FillRect const&) override;
CommandResult draw_scaled_bitmap(DrawScaledBitmap const&) override;
CommandResult draw_scaled_immutable_bitmap(DrawScaledImmutableBitmap const&) override;
CommandResult draw_repeated_immutable_bitmap(DrawRepeatedImmutableBitmap const&) override;
CommandResult add_clip_rect(AddClipRect const&) override;
CommandResult save(Save const&) override;
CommandResult restore(Restore const&) override;

View File

@ -210,6 +210,17 @@ void DisplayListRecorder::draw_scaled_immutable_bitmap(Gfx::IntRect const& dst_r
});
}
void DisplayListRecorder::draw_repeated_immutable_bitmap(Gfx::IntRect dst_rect, NonnullRefPtr<Gfx::ImmutableBitmap> bitmap, Gfx::ScalingMode scaling_mode, DrawRepeatedImmutableBitmap::Repeat repeat, Vector<Gfx::Path> const& clip_paths)
{
append(DrawRepeatedImmutableBitmap {
.dst_rect = dst_rect,
.bitmap = move(bitmap),
.scaling_mode = scaling_mode,
.repeat = repeat,
.clip_paths = clip_paths,
});
}
void DisplayListRecorder::draw_line(Gfx::IntPoint from, Gfx::IntPoint to, Color color, int thickness, Gfx::LineStyle style, Color alternate_color)
{
append(DrawLine {

View File

@ -89,6 +89,8 @@ public:
void draw_scaled_bitmap(Gfx::IntRect const& dst_rect, Gfx::Bitmap const& bitmap, Gfx::IntRect const& src_rect, Gfx::ScalingMode scaling_mode = Gfx::ScalingMode::NearestNeighbor);
void draw_scaled_immutable_bitmap(Gfx::IntRect const& dst_rect, Gfx::ImmutableBitmap const& bitmap, Gfx::IntRect const& src_rect, Gfx::ScalingMode scaling_mode = Gfx::ScalingMode::NearestNeighbor, Vector<Gfx::Path> const& clip_paths = {});
void draw_repeated_immutable_bitmap(Gfx::IntRect dst_rect, NonnullRefPtr<Gfx::ImmutableBitmap> bitmap, Gfx::ScalingMode scaling_mode, DrawRepeatedImmutableBitmap::Repeat, Vector<Gfx::Path> const& clip_paths = {});
void draw_line(Gfx::IntPoint from, Gfx::IntPoint to, Color color, int thickness = 1, Gfx::LineStyle style = Gfx::LineStyle::Solid, Color alternate_color = Color::Transparent);
void draw_text(Gfx::IntRect const&, String, Gfx::Font const&, Gfx::TextAlignment, Color);