summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkleines Filmröllchen <malu.bertsch@gmail.com>2021-04-19 19:10:24 +0200
committerAndreas Kling <kling@serenityos.org>2021-04-23 22:35:49 +0200
commitc1345bda3edd52df8de8cb5c0953a9f551ee4033 (patch)
treedfaff8c8c8e2eefccf4c20809368b3b969f8afbc
parent511ffa8d68996473457f655869e0af6987778148 (diff)
downloadserenity-c1345bda3edd52df8de8cb5c0953a9f551ee4033.zip
Userland: Piano: Optimize repaints
The Piano application used to perform very poorly due to unnecessary draw calls. This is solved with two optimziations: 1. Don't draw the widgets as often as possible. The widgets are instead at least updated every 150ms, except for other events. 2. Don't re-draw the entire piano roll sheet. The piano roll background, excluding in-motion objects (notes, the play cursor), is only re-drawn when its "viewport" changes. A minor drawback of this change is that notes will appear on top of the pitch labels if placed at the left edge of the roll. This is IMO acceptable or may be changed by moving the text to the "foreground".
-rw-r--r--Userland/Applications/Piano/RollWidget.cpp84
-rw-r--r--Userland/Applications/Piano/RollWidget.h7
-rw-r--r--Userland/Applications/Piano/main.cpp7
3 files changed, 79 insertions, 19 deletions
diff --git a/Userland/Applications/Piano/RollWidget.cpp b/Userland/Applications/Piano/RollWidget.cpp
index 2a247fc7e0..f4c5e36f8b 100644
--- a/Userland/Applications/Piano/RollWidget.cpp
+++ b/Userland/Applications/Piano/RollWidget.cpp
@@ -1,6 +1,7 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2019-2020, William McPherson <willmcpherson2@gmail.com>
+ * Copyright (c) 2021, kleines Filmröllchen <malu.bertsch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -68,6 +69,62 @@ void RollWidget::paint_event(GUI::PaintEvent& event)
int horizontal_notes_to_paint = horizontal_paint_area / m_note_width;
GUI::Painter painter(*this);
+
+ // Draw the background, if necessary.
+ if (viewport_changed() || paint_area != m_background->height()) {
+ m_background = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, Gfx::IntSize(m_roll_width, paint_area));
+ Gfx::Painter background_painter(*m_background);
+
+ background_painter.translate(frame_thickness(), frame_thickness());
+ background_painter.translate(-horizontal_note_offset_remainder, -note_offset_remainder);
+
+ for (int y = 0; y < notes_to_paint; ++y) {
+ int y_pos = y * note_height;
+
+ for (int x = 0; x < horizontal_notes_to_paint; ++x) {
+ // This is needed to avoid rounding errors. You can't just use
+ // m_note_width as the width.
+ int x_pos = x * m_note_width;
+ int next_x_pos = (x + 1) * m_note_width;
+ int distance_to_next_x = next_x_pos - x_pos;
+ Gfx::IntRect rect(x_pos, y_pos, distance_to_next_x, note_height);
+
+ if (key_pattern[key_pattern_index] == Black)
+ background_painter.fill_rect(rect, Color::LightGray);
+ else
+ background_painter.fill_rect(rect, Color::White);
+
+ background_painter.draw_line(rect.top_right(), rect.bottom_right(), Color::Black);
+ background_painter.draw_line(rect.bottom_left(), rect.bottom_right(), Color::Black);
+ }
+
+ if (--key_pattern_index == -1)
+ key_pattern_index = notes_per_octave - 1;
+ }
+
+ background_painter.translate(-x_offset, -y_offset);
+ background_painter.translate(horizontal_note_offset_remainder, note_offset_remainder);
+
+ for (int note = note_count - (note_offset + notes_to_paint); note <= (note_count - 1) - note_offset; ++note) {
+ int y = ((note_count - 1) - note) * note_height;
+
+ Gfx::IntRect note_name_rect(3, y, 1, note_height);
+ const char* note_name = note_names[note % notes_per_octave];
+
+ background_painter.draw_text(note_name_rect, note_name, Gfx::TextAlignment::CenterLeft);
+ note_name_rect.move_by(Gfx::FontDatabase::default_font().width(note_name) + 2, 0);
+ if (note % notes_per_octave == 0)
+ background_painter.draw_text(note_name_rect, String::formatted("{}", note / notes_per_octave + 1), Gfx::TextAlignment::CenterLeft);
+ }
+
+ m_prev_zoom_level = m_zoom_level;
+ m_prev_scroll_x = horizontal_scrollbar().value();
+ m_prev_scroll_y = vertical_scrollbar().value();
+ }
+
+ painter.blit(Gfx::IntPoint(0, 0), *m_background, m_background->rect());
+
+ // Draw the notes, mouse interaction, and time position.
painter.translate(frame_thickness(), frame_thickness());
painter.add_clip_rect(event.rect());
painter.translate(-horizontal_note_offset_remainder, -note_offset_remainder);
@@ -84,20 +141,9 @@ void RollWidget::paint_event(GUI::PaintEvent& event)
int distance_to_next_x = next_x_pos - x_pos;
Gfx::IntRect rect(x_pos, y_pos, distance_to_next_x, note_height);
- if (key_pattern[key_pattern_index] == Black)
- painter.fill_rect(rect, Color::LightGray);
- else
- painter.fill_rect(rect, Color::White);
-
if (keys_widget() && keys_widget()->note_is_set(note))
painter.fill_rect(rect, note_pressed_color.with_alpha(128));
-
- painter.draw_line(rect.top_right(), rect.bottom_right(), Color::Black);
- painter.draw_line(rect.bottom_left(), rect.bottom_right(), Color::Black);
}
-
- if (--key_pattern_index == -1)
- key_pattern_index = notes_per_octave - 1;
}
painter.translate(-x_offset, -y_offset);
@@ -119,13 +165,6 @@ void RollWidget::paint_event(GUI::PaintEvent& event)
painter.fill_rect(rect, note_pressed_color);
painter.draw_rect(rect, Color::Black);
}
- Gfx::IntRect note_name_rect(3, y, 1, note_height);
- const char* note_name = note_names[note % notes_per_octave];
-
- painter.draw_text(note_name_rect, note_name, Gfx::TextAlignment::CenterLeft);
- note_name_rect.move_by(Gfx::FontDatabase::default_font().width(note_name) + 2, 0);
- if (note % notes_per_octave == 0)
- painter.draw_text(note_name_rect, String::formatted("{}", note / notes_per_octave + 1), Gfx::TextAlignment::CenterLeft);
}
int x = m_roll_width * (static_cast<double>(m_track_manager.time()) / roll_length);
@@ -135,6 +174,15 @@ void RollWidget::paint_event(GUI::PaintEvent& event)
GUI::Frame::paint_event(event);
}
+bool RollWidget::viewport_changed() const
+{
+ // height is complicated to check, will be done in paint_event
+ return m_background.is_null()
+ || m_roll_width != m_background->width()
+ || m_prev_scroll_x != horizontal_scrollbar().value() || m_prev_scroll_y != vertical_scrollbar().value()
+ || m_prev_zoom_level != m_zoom_level;
+}
+
void RollWidget::mousedown_event(GUI::MouseEvent& event)
{
if (!widget_inner_rect().contains(event.x(), event.y()))
diff --git a/Userland/Applications/Piano/RollWidget.h b/Userland/Applications/Piano/RollWidget.h
index 705b0e0afb..85b0d16d82 100644
--- a/Userland/Applications/Piano/RollWidget.h
+++ b/Userland/Applications/Piano/RollWidget.h
@@ -1,6 +1,7 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2019-2020, William McPherson <willmcpherson2@gmail.com>
+ * Copyright (c) 2021, kleines Filmröllchen <malu.bertsch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -29,6 +30,7 @@ private:
virtual void mousemove_event(GUI::MouseEvent& event) override;
virtual void mouseup_event(GUI::MouseEvent& event) override;
virtual void mousewheel_event(GUI::MouseEvent&) override;
+ bool viewport_changed() const;
TrackManager& m_track_manager;
const KeysWidget* m_keys_widget;
@@ -41,4 +43,9 @@ private:
Optional<Gfx::IntPoint> m_note_drag_start;
Optional<RollNote> m_note_drag_location;
int m_drag_note;
+
+ RefPtr<Gfx::Bitmap> m_background;
+ int m_prev_zoom_level { m_zoom_level };
+ int m_prev_scroll_x { horizontal_scrollbar().value() };
+ int m_prev_scroll_y { vertical_scrollbar().value() };
};
diff --git a/Userland/Applications/Piano/main.cpp b/Userland/Applications/Piano/main.cpp
index 7579abf16e..2bea8731d3 100644
--- a/Userland/Applications/Piano/main.cpp
+++ b/Userland/Applications/Piano/main.cpp
@@ -1,6 +1,7 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2019-2020, William McPherson <willmcpherson2@gmail.com>
+ * Copyright (c) 2021, kleines Filmröllchen <malu.bertsch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -64,7 +65,6 @@ int main(int argc, char** argv)
while (!Core::EventLoop::current().was_exit_requested()) {
track_manager.fill_buffer(buffer);
audio->write(reinterpret_cast<u8*>(buffer.data()), buffer_size);
- Core::EventLoop::current().post_event(main_widget, make<Core::CustomEvent>(0));
Core::EventLoop::wake();
if (need_to_write_wav) {
@@ -84,6 +84,11 @@ int main(int argc, char** argv)
});
audio_thread->start();
+ auto main_widget_updater = Core::Timer::construct(150, [&] {
+ Core::EventLoop::current().post_event(main_widget, make<Core::CustomEvent>(0));
+ });
+ main_widget_updater->start();
+
auto menubar = GUI::Menubar::construct();
auto& app_menu = menubar->add_menu("File");