mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-27 10:07:15 +02:00
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.
85 lines
2.9 KiB
C++
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;
|
|
});
|
|
}
|
|
|
|
}
|