mirror of
https://github.com/zebrajr/ladybird.git
synced 2025-12-06 12:20:00 +01:00
LibWeb: Calculate and use bounds for "simple" stacking contexts
Teach the display list executor to derive a bounding rectangle for stacking contexts whose inner commands can all report bounds, that is, most contexts without nested stacking contexts. This yields a large performance improvement on https://tc39.es/ecma262/ where the display list contains thousands of groups like: ``` PushStackingContext blending=Multiply DrawGlyphRun PopStackingContext ``` Previously, `PushStackingContext` triggered an unbounded `saveLayer()` even when the glyph run lies wholly outside the viewport. With this change, we (1) cull stacking contexts that fall outside the viewport and (2) provide bounds to `saveLayer()` when they are visible. With this change rendering thread goes from 70% to 1% in profiles of https://tc39.es/ecma262/. Also makes a huge performance difference on Discord.
This commit is contained in:
parent
4acde45d5e
commit
ba2926f8b3
|
|
@ -125,6 +125,28 @@ void DisplayListPlayer::execute_impl(DisplayList& display_list, ScrollStateSnaps
|
||||||
|
|
||||||
VERIFY(!m_surfaces.is_empty());
|
VERIFY(!m_surfaces.is_empty());
|
||||||
|
|
||||||
|
auto translate_command_by_scroll = [&](auto& command, int scroll_frame_id) {
|
||||||
|
auto cumulative_offset = scroll_state.cumulative_offset_for_frame_with_id(scroll_frame_id);
|
||||||
|
auto scroll_offset = cumulative_offset.to_type<double>().scaled(device_pixels_per_css_pixel).to_type<int>();
|
||||||
|
command.visit(
|
||||||
|
[scroll_offset](auto& command) {
|
||||||
|
if constexpr (requires { command.translate_by(scroll_offset); }) {
|
||||||
|
command.translate_by(scroll_offset);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
auto compute_stacking_context_bounds = [&](PushStackingContext const& push_stacking_context, size_t push_stacking_context_index) {
|
||||||
|
Gfx::IntRect bounding_rect;
|
||||||
|
display_list.for_each_command_in_range(push_stacking_context_index + 1, push_stacking_context.matching_pop_index, [&](auto command, auto scroll_frame_id) {
|
||||||
|
if (scroll_frame_id.has_value())
|
||||||
|
translate_command_by_scroll(command, scroll_frame_id.value());
|
||||||
|
bounding_rect.unite(*command_bounding_rectangle(command));
|
||||||
|
return IterationDecision::Continue;
|
||||||
|
});
|
||||||
|
return bounding_rect;
|
||||||
|
};
|
||||||
|
|
||||||
Vector<RefPtr<ClipFrame const>> clip_frames_stack;
|
Vector<RefPtr<ClipFrame const>> clip_frames_stack;
|
||||||
clip_frames_stack.append({});
|
clip_frames_stack.append({});
|
||||||
for (size_t command_index = 0; command_index < commands.size(); command_index++) {
|
for (size_t command_index = 0; command_index < commands.size(); command_index++) {
|
||||||
|
|
@ -164,18 +186,19 @@ void DisplayListPlayer::execute_impl(DisplayList& display_list, ScrollStateSnaps
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scroll_frame_id.has_value()) {
|
if (scroll_frame_id.has_value())
|
||||||
auto cumulative_offset = scroll_state.cumulative_offset_for_frame_with_id(scroll_frame_id.value());
|
translate_command_by_scroll(command, scroll_frame_id.value());
|
||||||
auto scroll_offset = cumulative_offset.to_type<double>().scaled(device_pixels_per_css_pixel).to_type<int>();
|
|
||||||
command.visit(
|
|
||||||
[&](auto& command) {
|
|
||||||
if constexpr (requires { command.translate_by(scroll_offset); }) {
|
|
||||||
command.translate_by(scroll_offset);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
auto bounding_rect = command_bounding_rectangle(command);
|
auto bounding_rect = command_bounding_rectangle(command);
|
||||||
|
|
||||||
|
if (command.has<PushStackingContext>()) {
|
||||||
|
auto& push_stacking_context = command.get<PushStackingContext>();
|
||||||
|
if (push_stacking_context.can_aggregate_children_bounds) {
|
||||||
|
bounding_rect = compute_stacking_context_bounds(push_stacking_context, command_index);
|
||||||
|
push_stacking_context.bounding_rect = bounding_rect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (bounding_rect.has_value() && (bounding_rect->is_empty() || would_be_fully_clipped_by_painter(*bounding_rect))) {
|
if (bounding_rect.has_value() && (bounding_rect->is_empty() || would_be_fully_clipped_by_painter(*bounding_rect))) {
|
||||||
// Any clip or mask that's located outside of the visible region is equivalent to a simple clip-rect,
|
// Any clip or mask that's located outside of the visible region is equivalent to a simple clip-rect,
|
||||||
// so replace it with one to avoid doing unnecessary work.
|
// so replace it with one to avoid doing unnecessary work.
|
||||||
|
|
@ -186,6 +209,11 @@ void DisplayListPlayer::execute_impl(DisplayList& display_list, ScrollStateSnaps
|
||||||
add_clip_rect({ bounding_rect.release_value() });
|
add_clip_rect({ bounding_rect.release_value() });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (command.has<PushStackingContext>()) {
|
||||||
|
auto pop_stacking_context = command.get<PushStackingContext>().matching_pop_index;
|
||||||
|
command_index = pop_stacking_context;
|
||||||
|
(void)clip_frames_stack.take_last();
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -94,11 +94,21 @@ public:
|
||||||
DisplayListCommand command;
|
DisplayListCommand command;
|
||||||
};
|
};
|
||||||
|
|
||||||
AK::SegmentedVector<DisplayListCommandWithScrollAndClip, 512> const& commands() const { return m_commands; }
|
auto& commands(Badge<DisplayListRecorder>) { return m_commands; }
|
||||||
|
auto const& commands() const { return m_commands; }
|
||||||
double device_pixels_per_css_pixel() const { return m_device_pixels_per_css_pixel; }
|
double device_pixels_per_css_pixel() const { return m_device_pixels_per_css_pixel; }
|
||||||
|
|
||||||
String dump() const;
|
String dump() const;
|
||||||
|
|
||||||
|
template<typename Callback>
|
||||||
|
void for_each_command_in_range(size_t start, size_t end, Callback callback)
|
||||||
|
{
|
||||||
|
for (auto index = start; index < end; ++index) {
|
||||||
|
if (callback(m_commands[index].command, m_commands[index].scroll_frame_id) == IterationDecision::Break)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DisplayList(double device_pixels_per_css_pixel)
|
DisplayList(double device_pixels_per_css_pixel)
|
||||||
: m_device_pixels_per_css_pixel(device_pixels_per_css_pixel)
|
: m_device_pixels_per_css_pixel(device_pixels_per_css_pixel)
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,10 @@ struct PushStackingContext {
|
||||||
StackingContextTransform transform;
|
StackingContextTransform transform;
|
||||||
Optional<Gfx::Path> clip_path = {};
|
Optional<Gfx::Path> clip_path = {};
|
||||||
|
|
||||||
|
size_t matching_pop_index { 0 };
|
||||||
|
bool can_aggregate_children_bounds { false };
|
||||||
|
Optional<Gfx::IntRect> bounding_rect {};
|
||||||
|
|
||||||
void translate_by(Gfx::IntPoint const& offset)
|
void translate_by(Gfx::IntPoint const& offset)
|
||||||
{
|
{
|
||||||
transform.origin.translate_by(offset.to_type<float>());
|
transform.origin.translate_by(offset.to_type<float>());
|
||||||
|
|
|
||||||
|
|
@ -216,9 +216,12 @@ void DisplayListPlayerSkia::push_stacking_context(PushStackingContext const& com
|
||||||
paint.setAlphaf(command.opacity);
|
paint.setAlphaf(command.opacity);
|
||||||
paint.setBlender(Gfx::to_skia_blender(command.compositing_and_blending_operator));
|
paint.setBlender(Gfx::to_skia_blender(command.compositing_and_blending_operator));
|
||||||
|
|
||||||
// FIXME: If we knew the bounds of the stacking context including any transformed descendants etc,
|
if (command.bounding_rect.has_value()) {
|
||||||
// we could use saveLayer with a bounds rect. For now, we pass nullptr and let Skia figure it out.
|
auto bounds = to_skia_rect(command.bounding_rect.value());
|
||||||
|
canvas.saveLayer(bounds, &paint);
|
||||||
|
} else {
|
||||||
canvas.saveLayer(nullptr, &paint);
|
canvas.saveLayer(nullptr, &paint);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
canvas.save();
|
canvas.save();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -313,12 +313,36 @@ void DisplayListRecorder::push_stacking_context(PushStackingContextParams params
|
||||||
.transform = params.transform,
|
.transform = params.transform,
|
||||||
.clip_path = params.clip_path });
|
.clip_path = params.clip_path });
|
||||||
m_clip_frame_stack.append({});
|
m_clip_frame_stack.append({});
|
||||||
|
m_push_sc_index_stack.append(m_display_list.commands().size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool command_has_bounding_rectangle(DisplayListCommand const& command)
|
||||||
|
{
|
||||||
|
return command.visit(
|
||||||
|
[&](auto const& command) {
|
||||||
|
if constexpr (requires { command.bounding_rect(); })
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayListRecorder::pop_stacking_context()
|
void DisplayListRecorder::pop_stacking_context()
|
||||||
{
|
{
|
||||||
APPEND(PopStackingContext {});
|
APPEND(PopStackingContext {});
|
||||||
(void)m_clip_frame_stack.take_last();
|
(void)m_clip_frame_stack.take_last();
|
||||||
|
auto pop_index = m_display_list.commands().size() - 1;
|
||||||
|
auto push_index = m_push_sc_index_stack.take_last();
|
||||||
|
auto& push_stacking_context = m_display_list.commands({})[push_index].command.get<PushStackingContext>();
|
||||||
|
push_stacking_context.matching_pop_index = m_display_list.commands().size() - 1;
|
||||||
|
|
||||||
|
push_stacking_context.can_aggregate_children_bounds = true;
|
||||||
|
m_display_list.for_each_command_in_range(push_index + 1, pop_index, [&](auto const& command, auto) {
|
||||||
|
if (!command_has_bounding_rectangle(command)) {
|
||||||
|
push_stacking_context.can_aggregate_children_bounds = false;
|
||||||
|
return IterationDecision::Break;
|
||||||
|
}
|
||||||
|
return IterationDecision::Continue;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayListRecorder::apply_backdrop_filter(Gfx::IntRect const& backdrop_region, BorderRadiiData const& border_radii_data, Gfx::Filter const& backdrop_filter)
|
void DisplayListRecorder::apply_backdrop_filter(Gfx::IntRect const& backdrop_region, BorderRadiiData const& border_radii_data, Gfx::Filter const& backdrop_filter)
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,7 @@ public:
|
||||||
private:
|
private:
|
||||||
Vector<Optional<i32>> m_scroll_frame_id_stack;
|
Vector<Optional<i32>> m_scroll_frame_id_stack;
|
||||||
Vector<RefPtr<ClipFrame const>> m_clip_frame_stack;
|
Vector<RefPtr<ClipFrame const>> m_clip_frame_stack;
|
||||||
|
Vector<size_t> m_push_sc_index_stack;
|
||||||
DisplayList& m_display_list;
|
DisplayList& m_display_list;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user