LibWeb: Skip unnecessary sample corner and blit corner commands

Before this change we were recording and executing sample/blit commands
for each painting phase, even if there are no painting commands
in-between sample and blit that produce result visible on a canvas.

This change adds an optimization pass that goes through recorded
painting commands list and marks sample and blit commands that could
be skipped.

Reduces sample and blit corners executing from 17% to 8% on Discord.
This commit is contained in:
Aliaksandr Kalenik 2024-04-27 20:15:28 +00:00 committed by Andreas Kling
parent c2829ce2a0
commit b1205f0aa1
Notes: sideshowbarker 2024-07-17 00:47:29 +09:00
3 changed files with 48 additions and 4 deletions

View file

@ -2164,6 +2164,7 @@ void Navigable::paint(Painting::RecordingPainter& recording_painter, PaintConfig
scroll_offsets_by_frame_id[scrollable_frame->id] = scroll_offset;
}
recording_painter.commands_list().apply_scroll_offsets(scroll_offsets_by_frame_id);
recording_painter.commands_list().mark_unnecessary_commands();
}
m_needs_repaint = false;

View file

@ -39,6 +39,43 @@ void CommandList::apply_scroll_offsets(Vector<Gfx::IntPoint> const& offsets_by_f
}
}
void CommandList::mark_unnecessary_commands()
{
// The pair sample_under_corners and blit_corner_clipping commands is not needed if there are no painting commands
// in between them that produce visible output.
struct SampleCornersBlitCornersRange {
u32 sample_command_index;
bool has_painting_commands_in_between { false };
};
// Stack of sample_under_corners commands that have not been matched with a blit_corner_clipping command yet.
Vector<SampleCornersBlitCornersRange> sample_blit_ranges;
for (u32 command_index = 0; command_index < m_commands.size(); ++command_index) {
auto const& command = m_commands[command_index].command;
if (command.has<SampleUnderCorners>()) {
sample_blit_ranges.append({
.sample_command_index = command_index,
.has_painting_commands_in_between = false,
});
} else if (command.has<BlitCornerClipping>()) {
auto range = sample_blit_ranges.take_last();
if (!range.has_painting_commands_in_between) {
m_commands[range.sample_command_index].skip = true;
m_commands[command_index].skip = true;
}
} else {
// SetClipRect and ClearClipRect commands do not produce visible output
auto update_clip_command = command.has<SetClipRect>() || command.has<ClearClipRect>();
if (sample_blit_ranges.size() > 0 && !update_clip_command) {
// If painting command is found for sample_under_corners command on top of the stack, then all
// sample_under_corners commands below should also not be skipped.
for (auto& sample_blit_range : sample_blit_ranges)
sample_blit_range.has_painting_commands_in_between = true;
}
}
}
VERIFY(sample_blit_ranges.is_empty());
}
void CommandList::execute(CommandExecutor& executor)
{
executor.prepare_to_execute();
@ -76,8 +113,12 @@ void CommandList::execute(CommandExecutor& executor)
HashTable<u32> skipped_sample_corner_commands;
size_t next_command_index = 0;
while (next_command_index < m_commands.size()) {
auto& command_with_scroll_id = m_commands[next_command_index++];
auto& command = command_with_scroll_id.command;
if (m_commands[next_command_index].skip) {
next_command_index++;
continue;
}
auto& command = m_commands[next_command_index++].command;
auto bounding_rect = command_bounding_rectangle(command);
if (bounding_rect.has_value() && (bounding_rect->is_empty() || executor.would_be_fully_clipped_by_painter(*bounding_rect))) {
if (command.has<SampleUnderCorners>()) {

View file

@ -96,15 +96,17 @@ public:
void append(Command&& command, Optional<i32> scroll_frame_id);
void apply_scroll_offsets(Vector<Gfx::IntPoint> const& offsets_by_frame_id);
void mark_unnecessary_commands();
void execute(CommandExecutor&);
private:
struct CommandWithScrollFrame {
struct CommandListItem {
Optional<i32> scroll_frame_id;
Command command;
bool skip { false };
};
AK::SegmentedVector<CommandWithScrollFrame, 512> m_commands;
AK::SegmentedVector<CommandListItem, 512> m_commands;
};
}