Files
ladybird/Libraries/LibWeb/Painting/SVGSVGPaintable.cpp
Aliaksandr Kalenik 5ef132ba1a LibWeb: Replace AddMask/clipShader with saveLayer+DstIn compositing
This applies the same pattern used for background-clip: text (commit
f2e6f70fbb).

Results in visible performance improvement in Discord app where
previously, according to profiles, we spent lots of time allocating
surfaces for masks.
2026-02-24 07:14:16 +01:00

85 lines
2.9 KiB
C++

/*
* Copyright (c) 2018-2022, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGfx/ImmutableBitmap.h>
#include <LibWeb/Painting/DisplayList.h>
#include <LibWeb/Painting/DisplayListRecorder.h>
#include <LibWeb/Painting/SVGSVGPaintable.h>
namespace Web::Painting {
GC_DEFINE_ALLOCATOR(SVGSVGPaintable);
GC::Ref<SVGSVGPaintable> SVGSVGPaintable::create(Layout::SVGSVGBox const& layout_box)
{
return layout_box.heap().allocate<SVGSVGPaintable>(layout_box);
}
SVGSVGPaintable::SVGSVGPaintable(Layout::SVGSVGBox const& layout_box)
: PaintableBox(layout_box)
{
}
void SVGSVGPaintable::paint_svg_box(DisplayListRecordingContext& context, PaintableBox const& svg_box, PaintPhase phase)
{
context.display_list_recorder().set_accumulated_visual_context(svg_box.accumulated_visual_context());
// For elements with SVG filters, emit a transparent FillRect to trigger filter application.
// This ensures content-generating filters (feFlood, feImage) work even with empty source.
if (auto const& bounds = svg_box.filter().svg_filter_bounds; bounds.has_value()) {
auto device_rect = context.enclosing_device_rect(*bounds).to_type<int>();
context.display_list_recorder().fill_rect_transparent(device_rect);
}
// Collect masks (SVG <mask>, SVG <clipPath>).
Vector<DisplayListRecorder::MaskInfo> masks;
bool skip_painting = false;
auto mask_area = svg_box.get_mask_area();
if (mask_area.has_value()) {
if (mask_area->is_empty()) {
skip_painting = true;
} else if (auto mask_display_list = svg_box.calculate_mask(context, *mask_area)) {
auto rect = context.enclosing_device_rect(*mask_area).to_type<int>();
auto kind = svg_box.get_mask_type().value_or(Gfx::MaskKind::Alpha);
masks.append({ mask_display_list, rect, kind });
}
}
auto clip_area = svg_box.get_clip_area();
if (clip_area.has_value()) {
if (clip_area->is_empty()) {
skip_painting = true;
} else if (auto clip_display_list = svg_box.calculate_clip(context, *clip_area)) {
auto rect = context.enclosing_device_rect(*clip_area).to_type<int>();
masks.append({ clip_display_list, rect, Gfx::MaskKind::Alpha });
}
}
context.display_list_recorder().begin_masks(masks);
if (!skip_painting) {
svg_box.paint(context, PaintPhase::Foreground);
paint_descendants(context, svg_box, phase);
}
context.display_list_recorder().end_masks(masks);
}
void SVGSVGPaintable::paint_descendants(DisplayListRecordingContext& context, PaintableBox const& paintable, PaintPhase phase)
{
if (phase != PaintPhase::Foreground)
return;
paintable.for_each_child_of_type<PaintableBox>([&](PaintableBox& child) {
paint_svg_box(context, child, phase);
return IterationDecision::Continue;
});
}
}