From 15a1d9aa944bcc9a216aea532be037ed33b82859 Mon Sep 17 00:00:00 2001 From: Tom Date: Sun, 7 Feb 2021 22:26:31 -0700 Subject: [PATCH] WindowServer: Cache rendered window frame in bitmap This only renders the window frame once until the size of the window changes, or some other event requires re-rendering. It is rendered to a temporary bitmap, and then the top and bottom part is stored in one bitmap as well as the left and right part. This also adds an opacity setting, allowing it to be rendered with a different opacity. This makes it easier to enhance window themes and allows using arbitrary bitmaps with e.g. alpha channels for e.g. shadows. --- Userland/Services/WindowServer/Compositor.cpp | 3 +- .../Services/WindowServer/WindowFrame.cpp | 117 +++++++++++++++++- Userland/Services/WindowServer/WindowFrame.h | 24 +++- .../Services/WindowServer/WindowManager.cpp | 4 +- 4 files changed, 138 insertions(+), 10 deletions(-) diff --git a/Userland/Services/WindowServer/Compositor.cpp b/Userland/Services/WindowServer/Compositor.cpp index b0353a496a..55e4f6a2ae 100644 --- a/Userland/Services/WindowServer/Compositor.cpp +++ b/Userland/Services/WindowServer/Compositor.cpp @@ -288,11 +288,10 @@ void Compositor::compose() auto compose_window_rect = [&](Gfx::Painter& painter, const Gfx::IntRect& rect) { if (!window.is_fullscreen()) { rect.for_each_intersected(frame_rects, [&](const Gfx::IntRect& intersected_rect) { - // TODO: Should optimize this to use a backing buffer Gfx::PainterStateSaver saver(painter); painter.add_clip_rect(intersected_rect); dbgln_if(COMPOSE_DEBUG, " render frame: {}", intersected_rect); - window.frame().paint(painter); + window.frame().paint(painter, intersected_rect); return IterationDecision::Continue; }); } diff --git a/Userland/Services/WindowServer/WindowFrame.cpp b/Userland/Services/WindowServer/WindowFrame.cpp index c4e4dd0f2a..77b1863ffc 100644 --- a/Userland/Services/WindowServer/WindowFrame.cpp +++ b/Userland/Services/WindowServer/WindowFrame.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -200,14 +201,54 @@ void WindowFrame::paint_normal_frame(Gfx::Painter& painter) Gfx::WindowTheme::current().paint_normal_frame(painter, window_state_for_theme(), m_window.rect(), title_text, m_window.icon(), palette, leftmost_button_rect); } -void WindowFrame::paint(Gfx::Painter& painter) +void WindowFrame::paint(Gfx::Painter& painter, const Gfx::IntRect& rect) +{ + render_to_cache(); + + auto frame_rect = this->rect(); + auto window_rect = m_window.rect(); + + if (m_top_bottom) { + auto top_bottom_height = frame_rect.height() - window_rect.height(); + if (m_bottom_y > 0) { + // We have a top piece + auto src_rect = rect.intersected({ frame_rect.location(), { frame_rect.width(), m_bottom_y } }); + if (!src_rect.is_empty()) + painter.blit(src_rect.location(), *m_top_bottom, src_rect.translated(-frame_rect.location()), m_opacity); + } + if (m_bottom_y < top_bottom_height) { + // We have a bottom piece + Gfx::IntRect rect_in_frame { frame_rect.x(), window_rect.bottom() + 1, frame_rect.width(), top_bottom_height - m_bottom_y }; + auto src_rect = rect.intersected(rect_in_frame); + if (!src_rect.is_empty()) + painter.blit(src_rect.location(), *m_top_bottom, src_rect.translated(-rect_in_frame.x(), -rect_in_frame.y() + m_bottom_y), m_opacity); + } + } + + if (m_left_right) { + auto left_right_width = frame_rect.width() - window_rect.width(); + if (m_right_x > 0) { + // We have a left piece + Gfx::IntRect rect_in_frame { frame_rect.x(), window_rect.y(), m_right_x, window_rect.height() }; + auto src_rect = rect.intersected(rect_in_frame); + if (!src_rect.is_empty()) + painter.blit(src_rect.location(), *m_left_right, src_rect.translated(-rect_in_frame.location()), m_opacity); + } + if (m_right_x < left_right_width) { + // We have a right piece + Gfx::IntRect rect_in_frame { window_rect.right() + 1, window_rect.y(), left_right_width - m_right_x, window_rect.height() }; + auto src_rect = rect.intersected(rect_in_frame); + if (!src_rect.is_empty()) + painter.blit(src_rect.location(), *m_left_right, src_rect.translated(-rect_in_frame.x() + m_right_x, -rect_in_frame.y()), m_opacity); + } + } +} + +void WindowFrame::render(Gfx::Painter& painter) { if (m_window.is_frameless()) return; - Gfx::PainterStateSaver saver(painter); - painter.translate(rect().location()); - if (m_window.type() == WindowType::Notification) paint_notification_frame(painter); else if (m_window.type() == WindowType::Normal) @@ -220,6 +261,70 @@ void WindowFrame::paint(Gfx::Painter& painter) } } +void WindowFrame::render_to_cache() +{ + if (!m_dirty) + return; + m_dirty = true; + + static RefPtr s_tmp_bitmap; + auto frame_rect = rect(); + auto window_rect = m_window.rect(); + auto scale = Screen::the().scale_factor(); + if (!s_tmp_bitmap || !s_tmp_bitmap->size().contains(frame_rect.size()) || s_tmp_bitmap->scale() != scale) + s_tmp_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::RGBA32, frame_rect.size(), scale); + + Gfx::Painter painter(*s_tmp_bitmap); + painter.fill_rect({ { 0, 0 }, frame_rect.size() }, Color::White); + render(painter); + + auto top_bottom_height = frame_rect.height() - window_rect.height(); + if (top_bottom_height > 0) { + if (!m_top_bottom || m_top_bottom->width() != frame_rect.width() || m_top_bottom->height() != top_bottom_height || m_top_bottom->scale() != scale) + m_top_bottom = Gfx::Bitmap::create(Gfx::BitmapFormat::RGBA32, { frame_rect.width(), top_bottom_height }, scale); + m_bottom_y = window_rect.y() - frame_rect.y(); + ASSERT(m_bottom_y >= 0); + + Gfx::Painter top_bottom_painter(*m_top_bottom); + if (m_bottom_y > 0) + top_bottom_painter.blit({ 0, 0 }, *s_tmp_bitmap, { 0, 0, frame_rect.width(), m_bottom_y }); + if (m_bottom_y < top_bottom_height) + top_bottom_painter.blit({ 0, m_bottom_y }, *s_tmp_bitmap, { 0, frame_rect.height() - (frame_rect.bottom() - window_rect.bottom()), frame_rect.width(), top_bottom_height - m_bottom_y }); + } else { + m_top_bottom = nullptr; + m_bottom_y = 0; + } + + auto left_right_width = frame_rect.width() - window_rect.width(); + if (left_right_width > 0) { + if (!m_left_right || m_left_right->height() != frame_rect.height() || m_left_right->width() != left_right_width || m_left_right->scale() != scale) + m_left_right = Gfx::Bitmap::create(Gfx::BitmapFormat::RGBA32, { left_right_width, frame_rect.height() }, scale); + m_right_x = window_rect.x() - frame_rect.x(); + ASSERT(m_right_x >= 0); + + Gfx::Painter left_right_painter(*m_left_right); + if (m_right_x > 0) + left_right_painter.blit({ 0, 0 }, *s_tmp_bitmap, { 0, m_bottom_y, m_right_x, window_rect.height() }); + if (m_right_x < left_right_width) + left_right_painter.blit({ m_right_x, 0 }, *s_tmp_bitmap, { (window_rect.right() - frame_rect.x()) + 1, m_bottom_y, frame_rect.width() - (frame_rect.right() - window_rect.right()), window_rect.height() }); + } else { + m_left_right = nullptr; + m_right_x = 0; + } +} + +void WindowFrame::set_opacity(float opacity) +{ + if (m_opacity == opacity) + return; + bool was_opaque = is_opaque(); + m_opacity = opacity; + if (was_opaque != is_opaque()) + Compositor::the().invalidate_occlusions(); + Compositor::the().invalidate_screen(rect()); + WindowManager::the().notify_opacity_changed(m_window); +} + static Gfx::IntRect frame_rect_for_window(Window& window, const Gfx::IntRect& rect) { if (window.is_frameless()) @@ -239,6 +344,7 @@ Gfx::IntRect WindowFrame::rect() const void WindowFrame::invalidate_title_bar() { + m_dirty = true; invalidate(title_bar_rect()); } @@ -255,6 +361,9 @@ void WindowFrame::notify_window_rect_changed(const Gfx::IntRect& old_rect, const layout_buttons(); auto old_frame_rect = frame_rect_for_window(m_window, old_rect); + auto new_frame_rect = frame_rect_for_window(m_window, new_rect); + if (old_frame_rect.width() != new_frame_rect.width()) + m_dirty = true; auto& compositor = Compositor::the(); for (auto& dirty : old_frame_rect.shatter(rect())) compositor.invalidate_screen(dirty); diff --git a/Userland/Services/WindowServer/WindowFrame.h b/Userland/Services/WindowServer/WindowFrame.h index 26d2f6621d..808bc1be82 100644 --- a/Userland/Services/WindowServer/WindowFrame.h +++ b/Userland/Services/WindowServer/WindowFrame.h @@ -45,7 +45,9 @@ public: ~WindowFrame(); Gfx::IntRect rect() const; - void paint(Gfx::Painter&); + void paint(Gfx::Painter&, const Gfx::IntRect&); + void render(Gfx::Painter&); + void render_to_cache(); void on_mouse_event(const MouseEvent&); void notify_window_rect_changed(const Gfx::IntRect& old_rect, const Gfx::IntRect& new_rect); void invalidate_title_bar(); @@ -62,17 +64,26 @@ public: void start_flash_animation(); + bool has_alpha_channel() const { return m_has_alpha_channel; } + void set_has_alpha_channel(bool value) { m_has_alpha_channel = value; } + + void set_opacity(float); float opacity() const { return m_opacity; } bool is_opaque() const { if (opacity() < 1.0f) return false; - //if (has_alpha_channel()) - // return false; + if (has_alpha_channel()) + return false; return true; } + void scale_changed() + { + m_dirty = true; + } + private: void paint_notification_frame(Gfx::Painter&); void paint_normal_frame(Gfx::Painter&); @@ -85,9 +96,16 @@ private: Button* m_maximize_button { nullptr }; Button* m_minimize_button { nullptr }; + RefPtr m_top_bottom; + RefPtr m_left_right; + int m_bottom_y { 0 }; // y-offset in m_top_bottom for the bottom half + int m_right_x { 0 }; // x-offset in m_left_right for the right half + RefPtr m_flash_timer; size_t m_flash_counter { 0 }; float m_opacity { 1 }; + bool m_has_alpha_channel { false }; + bool m_dirty { false }; }; } diff --git a/Userland/Services/WindowServer/WindowManager.cpp b/Userland/Services/WindowServer/WindowManager.cpp index 3ce041bc83..2c52c695c0 100644 --- a/Userland/Services/WindowServer/WindowManager.cpp +++ b/Userland/Services/WindowServer/WindowManager.cpp @@ -1515,7 +1515,9 @@ void WindowManager::reload_icon_bitmaps_after_scale_change(bool allow_hidpi_icon m_allow_hidpi_icons = allow_hidpi_icons; reload_config(); for_each_window([&](Window& window) { - window.frame().set_button_icons(); + auto& window_frame = window.frame(); + window_frame.set_button_icons(); + window_frame.scale_changed(); return IterationDecision::Continue; }); }