/* * Copyright (c) 2018-2021, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ #include "Compositor.h" #include "Animation.h" #include "ClientConnection.h" #include "Event.h" #include "EventLoop.h" #include "MultiScaleBitmaps.h" #include "Screen.h" #include "Window.h" #include "WindowManager.h" #include #include #include #include #include #include #include #include namespace WindowServer { Compositor& Compositor::the() { static Compositor s_the; return s_the; } static WallpaperMode mode_to_enum(const String& name) { if (name == "tile") return WallpaperMode::Tile; if (name == "stretch") return WallpaperMode::Stretch; if (name == "center") return WallpaperMode::Center; return WallpaperMode::Center; } Compositor::Compositor() { m_display_link_notify_timer = add( 1000 / 60, [this] { notify_display_links(); }); m_display_link_notify_timer->stop(); m_compose_timer = Core::Timer::create_single_shot( 1000 / 60, [this] { compose(); }, this); m_immediate_compose_timer = Core::Timer::create_single_shot( 0, [this] { compose(); }, this); init_bitmaps(); } const Gfx::Bitmap* Compositor::cursor_bitmap_for_screenshot(Badge, Screen& screen) const { if (!m_current_cursor) return nullptr; return &m_current_cursor->bitmap(screen.scale_factor()); } const Gfx::Bitmap& Compositor::front_bitmap_for_screenshot(Badge, Screen& screen) const { return *screen.compositor_screen_data().m_front_bitmap; } void CompositorScreenData::init_bitmaps(Compositor& compositor, Screen& screen) { // Recreate the screen-number overlay as the Screen instances may have changed, or get rid of it if we no longer need it if (compositor.showing_screen_numbers()) { m_screen_number_overlay = compositor.create_overlay(screen); m_screen_number_overlay->set_enabled(true); } else { m_screen_number_overlay = nullptr; } m_has_flipped = false; m_have_flush_rects = false; m_buffers_are_flipped = false; m_screen_can_set_buffer = screen.can_set_buffer(); m_flush_rects.clear_with_capacity(); m_flush_transparent_rects.clear_with_capacity(); m_flush_special_rects.clear_with_capacity(); auto size = screen.size(); m_front_bitmap = nullptr; m_front_bitmap = Gfx::Bitmap::try_create_wrapper(Gfx::BitmapFormat::BGRx8888, size, screen.scale_factor(), screen.pitch(), screen.scanline(0, 0)); m_front_painter = make(*m_front_bitmap); m_front_painter->translate(-screen.rect().location()); m_back_bitmap = nullptr; if (m_screen_can_set_buffer) m_back_bitmap = Gfx::Bitmap::try_create_wrapper(Gfx::BitmapFormat::BGRx8888, size, screen.scale_factor(), screen.pitch(), screen.scanline(1, 0)); else m_back_bitmap = Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRx8888, size, screen.scale_factor()); m_back_painter = make(*m_back_bitmap); m_back_painter->translate(-screen.rect().location()); m_temp_bitmap = nullptr; m_temp_bitmap = Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRx8888, size, screen.scale_factor()); m_temp_painter = make(*m_temp_bitmap); m_temp_painter->translate(-screen.rect().location()); } void Compositor::init_bitmaps() { Screen::for_each([&](auto& screen) { screen.compositor_screen_data().init_bitmaps(*this, screen); return IterationDecision::Continue; }); invalidate_screen(); } void Compositor::did_construct_window_manager(Badge) { auto& wm = WindowManager::the(); m_current_window_stack = &wm.current_window_stack(); m_wallpaper_mode = mode_to_enum(wm.config()->read_entry("Background", "Mode", "center")); m_custom_background_color = Color::from_string(wm.config()->read_entry("Background", "Color", "")); invalidate_screen(); invalidate_occlusions(); compose(); } Gfx::IntPoint Compositor::window_transition_offset(Window& window) { if (WindowManager::is_stationary_window_type(window.type())) return {}; if (window.is_moving_to_another_stack()) return {}; return window.window_stack().transition_offset(); } void Compositor::compose() { auto& wm = WindowManager::the(); { auto& current_cursor = wm.active_cursor(); if (m_current_cursor != ¤t_cursor) { change_cursor(¤t_cursor); m_invalidated_cursor = m_invalidated_any = true; } } if (!m_invalidated_any) { // nothing dirtied since the last compose pass. return; } if (m_occlusions_dirty) { m_occlusions_dirty = false; recompute_occlusions(); } // We should have recomputed occlusions if any overlay rects were changed VERIFY(!m_overlay_rects_changed); auto dirty_screen_rects = move(m_dirty_screen_rects); bool window_stack_transition_in_progress = m_transitioning_to_window_stack != nullptr; // Mark window regions as dirty that need to be re-rendered wm.for_each_visible_window_from_back_to_front([&](Window& window) { auto transition_offset = window_transition_offset(window); auto frame_rect = window.frame().render_rect(); auto frame_rect_on_screen = frame_rect.translated(transition_offset); for (auto& dirty_rect : dirty_screen_rects.rects()) { auto invalidate_rect = dirty_rect.intersected(frame_rect_on_screen); if (!invalidate_rect.is_empty()) { auto inner_rect_offset = window.rect().location() - frame_rect.location(); invalidate_rect.translate_by(-(frame_rect.location() + inner_rect_offset + transition_offset)); window.invalidate_no_notify(invalidate_rect); m_invalidated_window = true; } } window.prepare_dirty_rects(); if (window_stack_transition_in_progress) window.dirty_rects().translate_by(transition_offset); return IterationDecision::Continue; }); // Any dirty rects in transparency areas may require windows above or below // to also be marked dirty in these areas wm.for_each_visible_window_from_back_to_front([&](Window& window) { auto& dirty_rects = window.dirty_rects(); // dirty rects have already been adjusted for transition offset! if (dirty_rects.is_empty()) return IterationDecision::Continue; auto& affected_transparency_rects = window.affected_transparency_rects(); if (affected_transparency_rects.is_empty()) return IterationDecision::Continue; // If we have transparency rects that affect others, we better have transparency rects ourselves... auto& transparency_rects = window.transparency_rects(); VERIFY(!transparency_rects.is_empty()); for (auto& it : affected_transparency_rects) { auto& affected_window_dirty_rects = it.key->dirty_rects(); auto& affected_rects = it.value; affected_rects.for_each_intersected(dirty_rects, [&](auto& dirty_rect) { affected_window_dirty_rects.add(dirty_rect); return IterationDecision::Continue; }); } return IterationDecision::Continue; }); Color background_color = wm.palette().desktop_background(); if (m_custom_background_color.has_value()) background_color = m_custom_background_color.value(); if constexpr (COMPOSE_DEBUG) { dbgln("COMPOSE: invalidated: window: {} cursor: {}, any: {}", m_invalidated_window, m_invalidated_cursor, m_invalidated_any); for (auto& r : dirty_screen_rects.rects()) dbgln("dirty screen: {}", r); } auto& cursor_screen = ScreenInput::the().cursor_location_screen(); Screen::for_each([&](auto& screen) { auto& screen_data = screen.compositor_screen_data(); screen_data.m_have_flush_rects = false; screen_data.m_flush_rects.clear_with_capacity(); screen_data.m_flush_transparent_rects.clear_with_capacity(); screen_data.m_flush_special_rects.clear_with_capacity(); return IterationDecision::Continue; }); auto cursor_rect = current_cursor_rect(); bool need_to_draw_cursor = false; Gfx::IntRect previous_cursor_rect; Screen* previous_cursor_screen = nullptr; auto check_restore_cursor_back = [&](Screen& screen, const Gfx::IntRect& rect) { if (&screen == &cursor_screen && !previous_cursor_screen && !need_to_draw_cursor && rect.intersects(cursor_rect)) { // Restore what's behind the cursor if anything touches the area of the cursor need_to_draw_cursor = true; if (cursor_screen.compositor_screen_data().restore_cursor_back(cursor_screen, previous_cursor_rect)) previous_cursor_screen = &screen; } }; if (&cursor_screen != m_current_cursor_screen) { // Cursor moved to another screen, restore on the cursor's background on the previous screen need_to_draw_cursor = true; if (m_current_cursor_screen) { if (m_current_cursor_screen->compositor_screen_data().restore_cursor_back(*m_current_cursor_screen, previous_cursor_rect)) previous_cursor_screen = m_current_cursor_screen; } m_current_cursor_screen = &cursor_screen; } auto prepare_rect = [&](Screen& screen, const Gfx::IntRect& rect) { auto& screen_data = screen.compositor_screen_data(); dbgln_if(COMPOSE_DEBUG, " -> flush opaque: {}", rect); VERIFY(!screen_data.m_flush_rects.intersects(rect)); VERIFY(!screen_data.m_flush_transparent_rects.intersects(rect)); screen_data.m_have_flush_rects = true; screen_data.m_flush_rects.add(rect); check_restore_cursor_back(screen, rect); }; auto prepare_transparency_rect = [&](Screen& screen, const Gfx::IntRect& rect) { auto& screen_data = screen.compositor_screen_data(); dbgln_if(COMPOSE_DEBUG, " -> flush transparent: {}", rect); VERIFY(!screen_data.m_flush_rects.intersects(rect)); for (auto& r : screen_data.m_flush_transparent_rects.rects()) { if (r == rect) return; } screen_data.m_have_flush_rects = true; screen_data.m_flush_transparent_rects.add(rect); check_restore_cursor_back(screen, rect); }; if (!cursor_screen.compositor_screen_data().m_cursor_back_bitmap || m_invalidated_cursor) check_restore_cursor_back(cursor_screen, cursor_rect); auto paint_wallpaper = [&](Screen& screen, Gfx::Painter& painter, const Gfx::IntRect& rect, const Gfx::IntRect& screen_rect) { // FIXME: If the wallpaper is opaque and covers the whole rect, no need to fill with color! painter.fill_rect(rect, background_color); if (m_wallpaper) { if (m_wallpaper_mode == WallpaperMode::Center) { Gfx::IntPoint offset { (screen.width() - m_wallpaper->width()) / 2, (screen.height() - m_wallpaper->height()) / 2 }; painter.blit_offset(rect.location(), *m_wallpaper, rect.translated(-screen_rect.location()), offset); } else if (m_wallpaper_mode == WallpaperMode::Tile) { painter.draw_tiled_bitmap(rect, *m_wallpaper); } else if (m_wallpaper_mode == WallpaperMode::Stretch) { float hscale = (float)m_wallpaper->width() / (float)screen.width(); float vscale = (float)m_wallpaper->height() / (float)screen.height(); // TODO: this may look ugly, we should scale to a backing bitmap and then blit auto relative_rect = rect.translated(-screen_rect.location()); auto src_rect = Gfx::FloatRect { relative_rect.x() * hscale, relative_rect.y() * vscale, relative_rect.width() * hscale, relative_rect.height() * vscale }; painter.draw_scaled_bitmap(rect, *m_wallpaper, src_rect); } else { VERIFY_NOT_REACHED(); } } }; { // Paint any desktop wallpaper rects that are not somehow underneath any window transparency // rects and outside of any opaque window areas m_opaque_wallpaper_rects.for_each_intersected(dirty_screen_rects, [&](auto& render_rect) { Screen::for_each([&](auto& screen) { auto screen_rect = screen.rect(); auto screen_render_rect = screen_rect.intersected(render_rect); if (!screen_render_rect.is_empty()) { dbgln_if(COMPOSE_DEBUG, " render wallpaper opaque: {} on screen #{}", screen_render_rect, screen.index()); prepare_rect(screen, render_rect); auto& back_painter = *screen.compositor_screen_data().m_back_painter; paint_wallpaper(screen, back_painter, render_rect, screen_rect); } return IterationDecision::Continue; }); return IterationDecision::Continue; }); m_transparent_wallpaper_rects.for_each_intersected(dirty_screen_rects, [&](auto& render_rect) { Screen::for_each([&](auto& screen) { auto screen_rect = screen.rect(); auto screen_render_rect = screen_rect.intersected(render_rect); if (!screen_render_rect.is_empty()) { dbgln_if(COMPOSE_DEBUG, " render wallpaper transparent: {} on screen #{}", screen_render_rect, screen.index()); prepare_transparency_rect(screen, render_rect); auto& temp_painter = *screen.compositor_screen_data().m_temp_painter; paint_wallpaper(screen, temp_painter, render_rect, screen_rect); } return IterationDecision::Continue; }); return IterationDecision::Continue; }); } auto compose_window = [&](Window& window) -> IterationDecision { if (window.screens().is_empty()) { // This window doesn't intersect with any screens, so there's nothing to render return IterationDecision::Continue; } auto transition_offset = window_transition_offset(window); auto frame_rect = window.frame().render_rect().translated(transition_offset); auto window_rect = window.rect().translated(transition_offset); auto frame_rects = frame_rect.shatter(window_rect); dbgln_if(COMPOSE_DEBUG, " window {} frame rect: {}", window.title(), frame_rect); RefPtr backing_store = window.backing_store(); auto compose_window_rect = [&](Screen& screen, Gfx::Painter& painter, const Gfx::IntRect& rect) { if (!window.is_fullscreen()) { rect.for_each_intersected(frame_rects, [&](const Gfx::IntRect& intersected_rect) { Gfx::PainterStateSaver saver(painter); painter.add_clip_rect(intersected_rect); painter.translate(transition_offset); dbgln_if(COMPOSE_DEBUG, " render frame: {}", intersected_rect); window.frame().paint(screen, painter, intersected_rect.translated(-transition_offset)); return IterationDecision::Continue; }); } auto clear_window_rect = [&](const Gfx::IntRect& clear_rect) { auto fill_color = wm.palette().window(); if (!window.is_opaque()) fill_color.set_alpha(255 * window.opacity()); painter.fill_rect(clear_rect, fill_color); }; if (!backing_store) { clear_window_rect(window_rect.intersected(rect)); return; } // Decide where we would paint this window's backing store. // This is subtly different from widow.rect(), because window // size may be different from its backing store size. This // happens when the window has been resized and the client // has not yet attached a new backing store. In this case, // we want to try to blit the backing store at the same place // it was previously, and fill the rest of the window with its // background color. Gfx::IntRect backing_rect; backing_rect.set_size(backing_store->size()); switch (WindowManager::the().resize_direction_of_window(window)) { case ResizeDirection::None: case ResizeDirection::Right: case ResizeDirection::Down: case ResizeDirection::DownRight: backing_rect.set_location(window_rect.location()); break; case ResizeDirection::Left: case ResizeDirection::Up: case ResizeDirection::UpLeft: backing_rect.set_right_without_resize(window_rect.right()); backing_rect.set_bottom_without_resize(window_rect.bottom()); break; case ResizeDirection::UpRight: backing_rect.set_left(window.rect().left()); backing_rect.set_bottom_without_resize(window_rect.bottom()); break; case ResizeDirection::DownLeft: backing_rect.set_right_without_resize(window_rect.right()); backing_rect.set_top(window_rect.top()); break; } Gfx::IntRect dirty_rect_in_backing_coordinates = rect.intersected(window_rect) .intersected(backing_rect) .translated(-backing_rect.location()); if (!dirty_rect_in_backing_coordinates.is_empty()) { auto dst = backing_rect.location().translated(dirty_rect_in_backing_coordinates.location()); if (window.client() && window.client()->is_unresponsive()) { if (window.is_opaque()) { painter.blit_filtered(dst, *backing_store, dirty_rect_in_backing_coordinates, [](Color src) { return src.to_grayscale().darkened(0.75f); }); } else { u8 alpha = 255 * window.opacity(); painter.blit_filtered(dst, *backing_store, dirty_rect_in_backing_coordinates, [&](Color src) { auto color = src.to_grayscale().darkened(0.75f); color.set_alpha(alpha); return color; }); } } else { painter.blit(dst, *backing_store, dirty_rect_in_backing_coordinates, window.opacity()); } } for (auto background_rect : window_rect.shatter(backing_rect)) clear_window_rect(background_rect); }; auto& dirty_rects = window.dirty_rects(); if constexpr (COMPOSE_DEBUG) { for (auto& dirty_rect : dirty_rects.rects()) dbgln(" dirty: {}", dirty_rect); for (auto& r : window.opaque_rects().rects()) dbgln(" opaque: {}", r); for (auto& r : window.transparency_rects().rects()) dbgln(" transparent: {}", r); } // Render opaque portions directly to the back buffer auto& opaque_rects = window.opaque_rects(); if (!opaque_rects.is_empty()) { opaque_rects.for_each_intersected(dirty_rects, [&](const Gfx::IntRect& render_rect) { for (auto* screen : window.screens()) { auto screen_render_rect = render_rect.intersected(screen->rect()); if (screen_render_rect.is_empty()) continue; dbgln_if(COMPOSE_DEBUG, " render opaque: {} on screen #{}", screen_render_rect, screen->index()); prepare_rect(*screen, screen_render_rect); auto& back_painter = *screen->compositor_screen_data().m_back_painter; Gfx::PainterStateSaver saver(back_painter); back_painter.add_clip_rect(screen_render_rect); compose_window_rect(*screen, back_painter, screen_render_rect); } return IterationDecision::Continue; }); } // Render the wallpaper for any transparency directly covering // the wallpaper auto& transparency_wallpaper_rects = window.transparency_wallpaper_rects(); if (!transparency_wallpaper_rects.is_empty()) { transparency_wallpaper_rects.for_each_intersected(dirty_rects, [&](const Gfx::IntRect& render_rect) { for (auto* screen : window.screens()) { auto screen_rect = screen->rect(); auto screen_render_rect = render_rect.intersected(screen_rect); if (screen_render_rect.is_empty()) continue; dbgln_if(COMPOSE_DEBUG, " render wallpaper: {} on screen #{}", screen_render_rect, screen->index()); auto& temp_painter = *screen->compositor_screen_data().m_temp_painter; prepare_transparency_rect(*screen, screen_render_rect); paint_wallpaper(*screen, temp_painter, screen_render_rect, screen_rect); } return IterationDecision::Continue; }); } auto& transparency_rects = window.transparency_rects(); if (!transparency_rects.is_empty()) { transparency_rects.for_each_intersected(dirty_rects, [&](const Gfx::IntRect& render_rect) { for (auto* screen : window.screens()) { auto screen_rect = screen->rect(); auto screen_render_rect = render_rect.intersected(screen_rect); if (screen_render_rect.is_empty()) continue; dbgln_if(COMPOSE_DEBUG, " render transparent: {} on screen #{}", screen_render_rect, screen->index()); prepare_transparency_rect(*screen, screen_render_rect); auto& temp_painter = *screen->compositor_screen_data().m_temp_painter; Gfx::PainterStateSaver saver(temp_painter); temp_painter.add_clip_rect(screen_render_rect); compose_window_rect(*screen, temp_painter, screen_render_rect); } return IterationDecision::Continue; }); } return IterationDecision::Continue; }; // Paint the window stack. if (m_invalidated_window) { auto* fullscreen_window = wm.active_fullscreen_window(); if (fullscreen_window && fullscreen_window->is_opaque()) { compose_window(*fullscreen_window); fullscreen_window->clear_dirty_rects(); } else { wm.for_each_visible_window_from_back_to_front([&](Window& window) { compose_window(window); window.clear_dirty_rects(); return IterationDecision::Continue; }); } // Check that there are no overlapping transparent and opaque flush rectangles VERIFY(![&]() { bool is_overlapping = false; Screen::for_each([&](auto& screen) { auto& screen_data = screen.compositor_screen_data(); auto& flush_transparent_rects = screen_data.m_flush_transparent_rects; auto& flush_rects = screen_data.m_flush_rects; for (auto& rect_transparent : flush_transparent_rects.rects()) { for (auto& rect_opaque : flush_rects.rects()) { if (rect_opaque.intersects(rect_transparent)) { dbgln("Transparent rect {} overlaps opaque rect: {}: {}", rect_transparent, rect_opaque, rect_opaque.intersected(rect_transparent)); is_overlapping = true; return IterationDecision::Break; } } } return IterationDecision::Continue; }); return is_overlapping; }()); if (!m_overlay_list.is_empty()) { // Render everything to the temporary buffer before we copy it back render_overlays(); } // Copy anything rendered to the temporary buffer to the back buffer Screen::for_each([&](auto& screen) { auto screen_rect = screen.rect(); auto& screen_data = screen.compositor_screen_data(); for (auto& rect : screen_data.m_flush_transparent_rects.rects()) screen_data.m_back_painter->blit(rect.location(), *screen_data.m_temp_bitmap, rect.translated(-screen_rect.location())); return IterationDecision::Continue; }); } m_invalidated_any = false; m_invalidated_window = false; m_invalidated_cursor = false; if (!m_animations.is_empty()) { Screen::for_each([&](auto& screen) { auto& screen_data = screen.compositor_screen_data(); update_animations(screen, screen_data.m_flush_special_rects); if (!screen_data.m_flush_special_rects.is_empty()) screen_data.m_have_flush_rects = true; return IterationDecision::Continue; }); // As long as animations are running make sure we keep rendering frames m_invalidated_any = true; start_compose_async_timer(); } if (need_to_draw_cursor) { auto& screen_data = cursor_screen.compositor_screen_data(); screen_data.draw_cursor(cursor_screen, cursor_rect); } Screen::for_each([&](auto& screen) { flush(screen); return IterationDecision::Continue; }); } void Compositor::flush(Screen& screen) { auto& screen_data = screen.compositor_screen_data(); bool device_can_flush_buffers = screen.can_device_flush_buffers(); if (!screen_data.m_have_flush_rects && (!screen_data.m_screen_can_set_buffer || screen_data.m_has_flipped)) { dbgln_if(COMPOSE_DEBUG, "Nothing to flush on screen #{} {}", screen.index(), screen_data.m_have_flush_rects); return; } screen_data.m_have_flush_rects = false; auto screen_rect = screen.rect(); if (m_flash_flush) { Gfx::IntRect bounding_flash; for (auto& rect : screen_data.m_flush_rects.rects()) { screen_data.m_front_painter->fill_rect(rect, Color::Yellow); bounding_flash = bounding_flash.united(rect); } for (auto& rect : screen_data.m_flush_transparent_rects.rects()) { screen_data.m_front_painter->fill_rect(rect, Color::Green); bounding_flash = bounding_flash.united(rect); } if (!bounding_flash.is_empty()) { if (device_can_flush_buffers) { // If the device needs a flush we need to let it know that we // modified the front buffer! bounding_flash.translate_by(-screen_rect.location()); screen.flush_display_front_buffer((!screen_data.m_screen_can_set_buffer || !screen_data.m_buffers_are_flipped) ? 0 : 1, bounding_flash); } usleep(10000); } } if (device_can_flush_buffers && screen_data.m_screen_can_set_buffer) { if (!screen_data.m_has_flipped) { // If we have not flipped any buffers before, we should be flushing // the entire buffer to make sure that the device has all the bits we wrote screen_data.m_flush_rects = { screen.rect() }; } // If we also support buffer flipping we need to make sure we transfer all // updated areas to the device before we flip. We already modified the framebuffer // memory, but the device needs to know what areas we actually did update. for (auto& rect : screen_data.m_flush_rects.rects()) screen.queue_flush_display_rect(rect.translated(-screen_rect.location())); for (auto& rect : screen_data.m_flush_transparent_rects.rects()) screen.queue_flush_display_rect(rect.translated(-screen_rect.location())); for (auto& rect : screen_data.m_flush_special_rects.rects()) screen.queue_flush_display_rect(rect.translated(-screen_rect.location())); screen.flush_display((!screen_data.m_screen_can_set_buffer || screen_data.m_buffers_are_flipped) ? 0 : 1); } if (screen_data.m_screen_can_set_buffer) { screen_data.flip_buffers(screen); screen_data.m_has_flipped = true; } auto do_flush = [&](Gfx::IntRect rect) { VERIFY(screen_rect.contains(rect)); rect.translate_by(-screen_rect.location()); // Almost everything in Compositor is in logical coordinates, with the painters having // a scale applied. But this routine accesses the backbuffer pixels directly, so it // must work in physical coordinates. auto scaled_rect = rect * screen.scale_factor(); Gfx::RGBA32* front_ptr = screen_data.m_front_bitmap->scanline(scaled_rect.y()) + scaled_rect.x(); Gfx::RGBA32* back_ptr = screen_data.m_back_bitmap->scanline(scaled_rect.y()) + scaled_rect.x(); size_t pitch = screen_data.m_back_bitmap->pitch(); // NOTE: The meaning of a flush depends on whether we can flip buffers or not. // // If flipping is supported, flushing means that we've flipped, and now we // copy the changed bits from the front buffer to the back buffer, to keep // them in sync. // // If flipping is not supported, flushing means that we copy the changed // rects from the backing bitmap to the display framebuffer. Gfx::RGBA32* to_ptr; const Gfx::RGBA32* from_ptr; if (screen_data.m_screen_can_set_buffer) { to_ptr = back_ptr; from_ptr = front_ptr; } else { to_ptr = front_ptr; from_ptr = back_ptr; } for (int y = 0; y < scaled_rect.height(); ++y) { fast_u32_copy(to_ptr, from_ptr, scaled_rect.width()); from_ptr = (const Gfx::RGBA32*)((const u8*)from_ptr + pitch); to_ptr = (Gfx::RGBA32*)((u8*)to_ptr + pitch); } if (device_can_flush_buffers) { // Whether or not we need to flush buffers, we need to at least track what we modified // so that we can flush these areas next time before we flip buffers. Or, if we don't // support buffer flipping then we will flush them shortly. screen.queue_flush_display_rect(rect); } }; for (auto& rect : screen_data.m_flush_rects.rects()) do_flush(rect); for (auto& rect : screen_data.m_flush_transparent_rects.rects()) do_flush(rect); for (auto& rect : screen_data.m_flush_special_rects.rects()) do_flush(rect); if (device_can_flush_buffers && !screen_data.m_screen_can_set_buffer) { // If we also support flipping buffers we don't really need to flush these areas right now. // Instead, we skip this step and just keep track of them until shortly before the next flip. // If we however don't support flipping buffers then we need to flush the changed areas right // now so that they can be sent to the device. screen.flush_display(screen_data.m_buffers_are_flipped ? 1 : 0); } } void Compositor::invalidate_screen() { invalidate_screen(Screen::bounding_rect()); } void Compositor::invalidate_screen(const Gfx::IntRect& screen_rect) { m_dirty_screen_rects.add(screen_rect.intersected(Screen::bounding_rect())); if (m_invalidated_any) return; m_invalidated_any = true; m_invalidated_window = true; start_compose_async_timer(); } void Compositor::invalidate_screen(Gfx::DisjointRectSet const& rects) { m_dirty_screen_rects.add(rects.intersected(Screen::bounding_rect())); if (m_invalidated_any) return; m_invalidated_any = true; m_invalidated_window = true; start_compose_async_timer(); } void Compositor::invalidate_window() { if (m_invalidated_window) return; m_invalidated_window = true; m_invalidated_any = true; start_compose_async_timer(); } void Compositor::start_compose_async_timer() { // We delay composition by a timer interval, but to not affect latency too // much, if a pending compose is not already scheduled, we also schedule an // immediate compose the next spin of the event loop. if (!m_compose_timer->is_active()) { m_compose_timer->start(); m_immediate_compose_timer->start(); } } bool Compositor::set_background_color(const String& background_color) { auto color = Color::from_string(background_color); if (!color.has_value()) return false; m_custom_background_color = color; auto& wm = WindowManager::the(); wm.config()->write_entry("Background", "Color", background_color); bool ret_val = wm.config()->sync(); if (ret_val) Compositor::invalidate_screen(); return ret_val; } bool Compositor::set_wallpaper_mode(const String& mode) { auto& wm = WindowManager::the(); wm.config()->write_entry("Background", "Mode", mode); bool ret_val = wm.config()->sync(); if (ret_val) { m_wallpaper_mode = mode_to_enum(mode); Compositor::invalidate_screen(); } return ret_val; } bool Compositor::set_wallpaper(const String& path, Function&& callback) { Threading::BackgroundAction>::create( [path](auto&) { return Gfx::Bitmap::try_load_from_file(path); }, [this, path, callback = move(callback)](RefPtr bitmap) { m_wallpaper_path = path; m_wallpaper = move(bitmap); invalidate_screen(); callback(true); }); return true; } void CompositorScreenData::flip_buffers(Screen& screen) { VERIFY(m_screen_can_set_buffer); swap(m_front_bitmap, m_back_bitmap); swap(m_front_painter, m_back_painter); screen.set_buffer(m_buffers_are_flipped ? 0 : 1); m_buffers_are_flipped = !m_buffers_are_flipped; } void Compositor::screen_resolution_changed() { // Screens may be gone now, invalidate any references to them m_current_cursor_screen = nullptr; init_bitmaps(); invalidate_occlusions(); overlay_rects_changed(); compose(); } Gfx::IntRect Compositor::current_cursor_rect() const { auto& wm = WindowManager::the(); auto& current_cursor = m_current_cursor ? *m_current_cursor : wm.active_cursor(); return { ScreenInput::the().cursor_location().translated(-current_cursor.params().hotspot()), current_cursor.size() }; } void Compositor::invalidate_cursor(bool compose_immediately) { if (m_invalidated_cursor) return; m_invalidated_cursor = true; m_invalidated_any = true; if (compose_immediately) compose(); else start_compose_async_timer(); } void Compositor::change_cursor(const Cursor* cursor) { if (m_current_cursor == cursor) return; m_current_cursor = cursor; m_current_cursor_frame = 0; if (m_cursor_timer) { m_cursor_timer->stop(); m_cursor_timer = nullptr; } if (cursor && cursor->params().frames() > 1 && cursor->params().frame_ms() != 0) { m_cursor_timer = add( cursor->params().frame_ms(), [this, cursor] { if (m_current_cursor != cursor) return; auto frames = cursor->params().frames(); if (++m_current_cursor_frame >= frames) m_current_cursor_frame = 0; invalidate_cursor(true); }); } } void Compositor::render_overlays() { // NOTE: overlays should always be rendered to the temporary buffer! for (auto& overlay : m_overlay_list) { for (auto* screen : overlay.m_screens) { auto& screen_data = screen->compositor_screen_data(); auto& painter = screen_data.overlay_painter(); auto render_overlay_rect = [&](auto& intersected_overlay_rect) { Gfx::PainterStateSaver saver(painter); painter.add_clip_rect(intersected_overlay_rect); painter.translate(overlay.m_current_rect.location()); overlay.render(painter, *screen); return IterationDecision::Continue; }; auto const& render_rect = overlay.current_render_rect(); screen_data.for_each_intersected_flushing_rect(render_rect, render_overlay_rect); // Now render any areas that are not somehow underneath any window or transparency area m_transparent_wallpaper_rects.for_each_intersected(render_rect, render_overlay_rect); } } } void Compositor::add_overlay(Overlay& overlay) { VERIFY(!overlay.m_list_node.is_in_list()); auto zorder = overlay.zorder(); bool did_insert = false; for (auto& other_overlay : m_overlay_list) { if (other_overlay.zorder() > zorder) { m_overlay_list.insert_before(other_overlay, overlay); did_insert = true; break; } } if (!did_insert) m_overlay_list.append(overlay); overlay.clear_invalidated(); overlay_rects_changed(); auto& rect = overlay.rect(); if (!rect.is_empty()) invalidate_screen(rect); } void Compositor::remove_overlay(Overlay& overlay) { auto& current_render_rect = overlay.current_render_rect(); if (!current_render_rect.is_empty()) invalidate_screen(current_render_rect); m_overlay_list.remove(overlay); overlay_rects_changed(); } void CompositorScreenData::draw_cursor(Screen& screen, const Gfx::IntRect& cursor_rect) { auto& wm = WindowManager::the(); if (!m_cursor_back_bitmap || m_cursor_back_bitmap->size() != cursor_rect.size() || m_cursor_back_bitmap->scale() != screen.scale_factor()) { m_cursor_back_bitmap = Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRx8888, cursor_rect.size(), screen.scale_factor()); m_cursor_back_painter = make(*m_cursor_back_bitmap); } auto& compositor = Compositor::the(); auto& current_cursor = compositor.m_current_cursor ? *compositor.m_current_cursor : wm.active_cursor(); auto screen_rect = screen.rect(); m_cursor_back_painter->blit({ 0, 0 }, *m_back_bitmap, current_cursor.rect().translated(cursor_rect.location()).intersected(screen_rect).translated(-screen_rect.location())); auto cursor_src_rect = current_cursor.source_rect(compositor.m_current_cursor_frame); m_back_painter->blit(cursor_rect.location(), current_cursor.bitmap(screen.scale_factor()), cursor_src_rect); m_flush_special_rects.add(Gfx::IntRect(cursor_rect.location(), cursor_src_rect.size()).intersected(screen.rect())); m_have_flush_rects = true; m_last_cursor_rect = cursor_rect; VERIFY(compositor.m_current_cursor_screen == &screen); m_cursor_back_is_valid = true; } bool CompositorScreenData::restore_cursor_back(Screen& screen, Gfx::IntRect& last_cursor_rect) { if (!m_cursor_back_is_valid || !m_cursor_back_bitmap || m_cursor_back_bitmap->scale() != m_back_bitmap->scale()) return false; last_cursor_rect = m_last_cursor_rect.intersected(screen.rect()); m_back_painter->blit(last_cursor_rect.location(), *m_cursor_back_bitmap, { { 0, 0 }, last_cursor_rect.size() }); m_flush_special_rects.add(last_cursor_rect.intersected(screen.rect())); m_have_flush_rects = true; m_cursor_back_is_valid = false; return true; } void Compositor::update_fonts() { ScreenNumberOverlay::pick_font(); } void Compositor::notify_display_links() { ClientConnection::for_each_client([](auto& client) { client.notify_display_link({}); }); } void Compositor::increment_display_link_count(Badge) { ++m_display_link_count; if (m_display_link_count == 1) m_display_link_notify_timer->start(); } void Compositor::decrement_display_link_count(Badge) { VERIFY(m_display_link_count); --m_display_link_count; if (!m_display_link_count) m_display_link_notify_timer->stop(); } void Compositor::invalidate_current_screen_number_rects() { Screen::for_each([&](auto& screen) { auto& screen_data = screen.compositor_screen_data(); if (screen_data.m_screen_number_overlay) screen_data.m_screen_number_overlay->invalidate(); return IterationDecision::Continue; }); } void Compositor::increment_show_screen_number(Badge) { if (m_show_screen_number_count++ == 0) { Screen::for_each([&](auto& screen) { auto& screen_data = screen.compositor_screen_data(); VERIFY(!screen_data.m_screen_number_overlay); screen_data.m_screen_number_overlay = create_overlay(screen); screen_data.m_screen_number_overlay->set_enabled(true); return IterationDecision::Continue; }); } } void Compositor::decrement_show_screen_number(Badge) { if (--m_show_screen_number_count == 0) { invalidate_current_screen_number_rects(); Screen::for_each([&](auto& screen) { screen.compositor_screen_data().m_screen_number_overlay = nullptr; return IterationDecision::Continue; }); } } void Compositor::overlays_theme_changed() { for (auto& overlay : m_overlay_list) overlay.theme_changed(); overlay_rects_changed(); } void Compositor::overlay_rects_changed() { if (m_overlay_rects_changed) return; m_overlay_rects_changed = true; m_invalidated_any = true; invalidate_occlusions(); for (auto& rect : m_overlay_rects.rects()) invalidate_screen(rect); start_compose_async_timer(); } void Compositor::recompute_overlay_rects() { // The purpose of this is to gather all areas that we will render over // regular window contents. This effectively just forces those areas to // be rendered as transparency areas, which allows us to render these // flicker-free. m_overlay_rects.clear_with_capacity(); for (auto& overlay : m_overlay_list) { auto& render_rect = overlay.rect(); m_overlay_rects.add(render_rect); // Save the rectangle we are using for rendering from now on overlay.did_recompute_occlusions(); // Cache which screens this overlay are rendered on overlay.m_screens.clear_with_capacity(); Screen::for_each([&](auto& screen) { if (render_rect.intersects(screen.rect())) overlay.m_screens.append(&screen); return IterationDecision::Continue; }); invalidate_screen(render_rect); } } void Compositor::recompute_occlusions() { auto& wm = WindowManager::the(); bool is_switcher_visible = wm.m_switcher.is_visible(); auto never_occlude = [&](WindowStack& window_stack) { if (is_switcher_visible) { switch (wm.m_switcher.mode()) { case WindowSwitcher::Mode::ShowCurrentDesktop: // Any window on the currently rendered desktop should not be occluded, even if it's behind // another window entirely. return &window_stack == m_current_window_stack || &window_stack == m_transitioning_to_window_stack; case WindowSwitcher::Mode::ShowAllWindows: // The window switcher wants to know about all windows, even those on other desktops return true; } } return false; }; wm.for_each_window_stack([&](WindowStack& window_stack) { if (&window_stack == m_current_window_stack || &window_stack == m_transitioning_to_window_stack) { // We'll calculate precise occlusions for these further down. Changing occlusions right now // may trigger an additional unnecessary notification } else { window_stack.set_all_occluded(!never_occlude(window_stack)); } return IterationDecision::Continue; }); if (m_overlay_rects_changed) { m_overlay_rects_changed = false; recompute_overlay_rects(); } if constexpr (OCCLUSIONS_DEBUG) { dbgln("OCCLUSIONS:"); for (auto& rect : m_overlay_rects.rects()) dbgln(" overlay: {}", rect); } bool window_stack_transition_in_progress = m_transitioning_to_window_stack != nullptr; auto& main_screen = Screen::main(); auto* fullscreen_window = wm.active_fullscreen_window(); if (fullscreen_window) { // TODO: support fullscreen windows on all screens auto screen_rect = main_screen.rect(); wm.for_each_visible_window_from_front_to_back([&](Window& w) { auto& visible_opaque = w.opaque_rects(); auto& transparency_rects = w.transparency_rects(); auto& transparency_wallpaper_rects = w.transparency_wallpaper_rects(); w.affected_transparency_rects().clear(); w.screens().clear_with_capacity(); if (&w == fullscreen_window) { w.screens().append(&main_screen); if (w.is_opaque()) { visible_opaque = screen_rect; transparency_rects.clear(); transparency_wallpaper_rects.clear(); } else { visible_opaque.clear(); transparency_rects = screen_rect; transparency_wallpaper_rects = screen_rect; } } else { visible_opaque.clear(); transparency_rects.clear(); transparency_wallpaper_rects.clear(); } return IterationDecision::Continue; }); m_opaque_wallpaper_rects.clear(); } if (!fullscreen_window || (fullscreen_window && !fullscreen_window->is_opaque())) { Gfx::DisjointRectSet remaining_visible_screen_rects; remaining_visible_screen_rects.add_many(Screen::rects()); bool have_transparent = false; wm.for_each_visible_window_from_front_to_back([&](Window& w) { VERIFY(!w.is_minimized()); w.transparency_wallpaper_rects().clear(); auto previous_visible_opaque = move(w.opaque_rects()); auto previous_visible_transparency = move(w.transparency_rects()); auto invalidate_previous_render_rects = [&](Gfx::IntRect const& new_render_rect) { if (!previous_visible_opaque.is_empty()) { if (new_render_rect.is_empty()) invalidate_screen(previous_visible_opaque); else invalidate_screen(previous_visible_opaque.shatter(new_render_rect)); } if (!previous_visible_transparency.is_empty()) { if (new_render_rect.is_empty()) invalidate_screen(previous_visible_transparency); else invalidate_screen(previous_visible_transparency.shatter(new_render_rect)); } }; auto& visible_opaque = w.opaque_rects(); auto& transparency_rects = w.transparency_rects(); bool should_invalidate_old = w.should_invalidate_last_rendered_screen_rects(); auto& affected_transparency_rects = w.affected_transparency_rects(); affected_transparency_rects.clear(); w.screens().clear_with_capacity(); auto transition_offset = window_transition_offset(w); auto transparent_frame_render_rects = w.frame().transparent_render_rects(); auto opaque_frame_render_rects = w.frame().opaque_render_rects(); if (window_stack_transition_in_progress) { transparent_frame_render_rects.translate_by(transition_offset); opaque_frame_render_rects.translate_by(transition_offset); } if (should_invalidate_old) { for (auto& rect : opaque_frame_render_rects.rects()) invalidate_previous_render_rects(rect); for (auto& rect : transparent_frame_render_rects.rects()) invalidate_previous_render_rects(rect); } if (auto transparent_render_rects = transparent_frame_render_rects.intersected(remaining_visible_screen_rects); !transparent_render_rects.is_empty()) transparency_rects = move(transparent_render_rects); if (auto opaque_render_rects = opaque_frame_render_rects.intersected(remaining_visible_screen_rects); !opaque_render_rects.is_empty()) visible_opaque = move(opaque_render_rects); auto render_rect_on_screen = w.frame().render_rect().translated(transition_offset); auto visible_window_rects = remaining_visible_screen_rects.intersected(w.rect().translated(transition_offset)); Gfx::DisjointRectSet opaque_covering; Gfx::DisjointRectSet transparent_covering; bool found_this_window = false; wm.for_each_visible_window_from_back_to_front([&](Window& w2) { if (!found_this_window) { if (&w == &w2) found_this_window = true; return IterationDecision::Continue; } VERIFY(!w2.is_minimized()); auto w2_render_rect = w2.frame().render_rect(); auto w2_render_rect_on_screen = w2_render_rect; auto w2_transition_offset = window_transition_offset(w2); if (window_stack_transition_in_progress) w2_render_rect_on_screen.translate_by(w2_transition_offset); if (!render_rect_on_screen.intersects(w2_render_rect_on_screen)) return IterationDecision::Continue; auto opaque_rects = w2.frame().opaque_render_rects(); auto transparent_rects = w2.frame().transparent_render_rects(); if (window_stack_transition_in_progress) { auto transition_offset_2 = window_transition_offset(w2); opaque_rects.translate_by(transition_offset_2); transparent_rects.translate_by(transition_offset_2); } opaque_rects = opaque_rects.intersected(render_rect_on_screen); transparent_rects = transparent_rects.intersected(render_rect_on_screen); if (opaque_rects.is_empty() && transparent_rects.is_empty()) return IterationDecision::Continue; VERIFY(!opaque_rects.intersects(transparent_rects)); for (auto& covering : opaque_rects.rects()) { opaque_covering.add(covering); if (!visible_window_rects.is_empty()) visible_window_rects = visible_window_rects.shatter(covering); if (!visible_opaque.is_empty()) { auto uncovered_opaque = visible_opaque.shatter(covering); visible_opaque = move(uncovered_opaque); } if (!transparency_rects.is_empty()) { auto uncovered_transparency = transparency_rects.shatter(covering); transparency_rects = move(uncovered_transparency); } if (!transparent_covering.is_empty()) { auto uncovered_transparency = transparent_covering.shatter(covering); transparent_covering = move(uncovered_transparency); } } if (!transparent_rects.is_empty()) transparent_covering.add(transparent_rects.shatter(opaque_covering)); VERIFY(!transparent_covering.intersects(opaque_covering)); return IterationDecision::Continue; }); VERIFY(opaque_covering.is_empty() || render_rect_on_screen.contains(opaque_covering.rects())); if (!m_overlay_rects.is_empty() && m_overlay_rects.intersects(visible_opaque)) { // In order to render overlays flicker-free we need to force this area into the // temporary transparency rendering buffer transparent_covering.add(m_overlay_rects.intersected(visible_opaque)); } if (!transparent_covering.is_empty()) { VERIFY(!transparent_covering.intersects(opaque_covering)); transparency_rects.add(transparent_covering); if (!visible_opaque.is_empty()) { auto uncovered_opaque = visible_opaque.shatter(transparent_covering); visible_opaque = move(uncovered_opaque); } // Now that we know what transparency rectangles are immediately covering our window // figure out what windows they belong to and add them to the affected transparency rects. // We can't do the same with the windows below as we haven't gotten to those yet. These // will be determined after we're done with this pass. found_this_window = false; wm.for_each_visible_window_from_back_to_front([&](Window& w2) { if (!found_this_window) { if (&w == &w2) found_this_window = true; return IterationDecision::Continue; } auto affected_transparency = transparent_covering.intersected(w2.transparency_rects()); if (!affected_transparency.is_empty()) { auto result = affected_transparency_rects.set(&w2, move(affected_transparency)); VERIFY(result == AK::HashSetResult::InsertedNewEntry); } return IterationDecision::Continue; }); } // This window should not be occluded while the window switcher is interested in it (depending // on the mode it's in). If it isn't then determine occlusions based on whether the window // rect has any visible areas at all. w.set_occluded(never_occlude(w.window_stack()) ? false : visible_window_rects.is_empty()); bool have_opaque = !visible_opaque.is_empty(); if (!transparency_rects.is_empty()) have_transparent = true; if (have_transparent || have_opaque) { // Figure out what screens this window is rendered on // We gather this information so we can more quickly // render the window on each of the screens that it // needs to be rendered on. Screen::for_each([&](auto& screen) { auto screen_rect = screen.rect(); for (auto& r : visible_opaque.rects()) { if (r.intersects(screen_rect)) { w.screens().append(&screen); return IterationDecision::Continue; } } for (auto& r : transparency_rects.rects()) { if (r.intersects(screen_rect)) { w.screens().append(&screen); return IterationDecision::Continue; } } return IterationDecision::Continue; }); } if (!visible_opaque.is_empty()) { VERIFY(!visible_opaque.intersects(transparency_rects)); // Determine visible area for the window below remaining_visible_screen_rects = remaining_visible_screen_rects.shatter(visible_opaque); } return IterationDecision::Continue; }); if (have_transparent) { // Also, now that we have completed the first pass we can determine the affected // transparency rects below a given window wm.for_each_visible_window_from_back_to_front([&](Window& w) { // Any area left in remaining_visible_screen_rects will need to be rendered with the wallpaper first auto& transparency_rects = w.transparency_rects(); auto& transparency_wallpaper_rects = w.transparency_wallpaper_rects(); if (transparency_rects.is_empty()) { VERIFY(transparency_wallpaper_rects.is_empty()); // Should have been cleared in the first pass } else { transparency_wallpaper_rects = remaining_visible_screen_rects.intersected(transparency_rects); if (!transparency_wallpaper_rects.is_empty()) { auto remaining_visible = remaining_visible_screen_rects.shatter(transparency_wallpaper_rects); remaining_visible_screen_rects = move(remaining_visible); } } // Figure out the affected transparency rects underneath. First figure out if any transparency is visible at all Gfx::DisjointRectSet transparent_underneath; wm.for_each_visible_window_from_back_to_front([&](Window& w2) { if (&w == &w2) return IterationDecision::Break; auto& opaque_rects2 = w2.opaque_rects(); if (!opaque_rects2.is_empty()) { auto uncovered_transparency = transparent_underneath.shatter(opaque_rects2); transparent_underneath = move(uncovered_transparency); } w2.transparency_rects().for_each_intersected(transparency_rects, [&](auto& rect) { transparent_underneath.add(rect); return IterationDecision::Continue; }); return IterationDecision::Continue; }); if (!transparent_underneath.is_empty()) { // Now that we know there are some transparency rects underneath that are visible // figure out what windows they belong to auto& affected_transparency_rects = w.affected_transparency_rects(); wm.for_each_visible_window_from_back_to_front([&](Window& w2) { if (&w == &w2) return IterationDecision::Break; auto& transparency_rects2 = w2.transparency_rects(); if (transparency_rects2.is_empty()) return IterationDecision::Continue; auto affected_transparency = transparent_underneath.intersected(transparency_rects2); if (!affected_transparency.is_empty()) { auto result = affected_transparency_rects.set(&w2, move(affected_transparency)); VERIFY(result == AK::HashSetResult::InsertedNewEntry); } return IterationDecision::Continue; }); } return IterationDecision::Continue; }); } m_transparent_wallpaper_rects.clear_with_capacity(); if (!m_overlay_rects.is_empty() && m_overlay_rects.intersects(remaining_visible_screen_rects)) { // Check if any overlay rects are remaining that are not somehow above any windows m_transparent_wallpaper_rects = m_overlay_rects.intersected(remaining_visible_screen_rects); auto remaining_visible_not_covered = remaining_visible_screen_rects.shatter(m_overlay_rects); remaining_visible_screen_rects = move(remaining_visible_not_covered); } m_opaque_wallpaper_rects = move(remaining_visible_screen_rects); } if constexpr (OCCLUSIONS_DEBUG) { for (auto& r : m_opaque_wallpaper_rects.rects()) dbgln(" wallpaper opaque: {}", r); } wm.for_each_visible_window_from_back_to_front([&](Window& w) { auto window_frame_rect = w.frame().render_rect(); if (w.is_minimized() || window_frame_rect.is_empty() || w.screens().is_empty()) return IterationDecision::Continue; if constexpr (OCCLUSIONS_DEBUG) { dbgln(" Window {} frame rect: {} rendered on screens: {}", w.title(), window_frame_rect, w.screens().size()); for (auto& s : w.screens()) dbgln(" screen: #{}", s->index()); for (auto& r : w.opaque_rects().rects()) dbgln(" opaque: {}", r); for (auto& r : w.transparency_wallpaper_rects().rects()) dbgln(" transparent wallpaper: {}", r); for (auto& r : w.transparency_rects().rects()) dbgln(" transparent: {}", r); for (auto& it : w.affected_transparency_rects()) { dbgln(" affects {}:", it.key->title()); for (auto& r : it.value.rects()) dbgln(" transparent: {}", r); } } VERIFY(!w.opaque_rects().intersects(m_opaque_wallpaper_rects)); VERIFY(!w.transparency_rects().intersects(m_opaque_wallpaper_rects)); VERIFY(!w.transparency_wallpaper_rects().intersects(m_opaque_wallpaper_rects)); return IterationDecision::Continue; }); } void Compositor::register_animation(Badge, Animation& animation) { bool was_empty = m_animations.is_empty(); auto result = m_animations.set(&animation); VERIFY(result == AK::HashSetResult::InsertedNewEntry); if (was_empty) start_compose_async_timer(); } void Compositor::animation_started(Badge) { m_invalidated_any = true; start_compose_async_timer(); } void Compositor::unregister_animation(Badge, Animation& animation) { bool was_removed = m_animations.remove(&animation); VERIFY(was_removed); } void Compositor::update_animations(Screen& screen, Gfx::DisjointRectSet& flush_rects) { auto& painter = *screen.compositor_screen_data().m_back_painter; for (RefPtr animation : m_animations) { animation->update({}, painter, screen, flush_rects); } } void Compositor::create_window_stack_switch_overlay(WindowStack& target_stack) { stop_window_stack_switch_overlay_timer(); Screen::for_each([&](auto& screen) { auto& screen_data = screen.compositor_screen_data(); screen_data.m_window_stack_switch_overlay = nullptr; // delete it first screen_data.m_window_stack_switch_overlay = create_overlay(screen, target_stack); screen_data.m_window_stack_switch_overlay->set_enabled(true); return IterationDecision::Continue; }); } void Compositor::remove_window_stack_switch_overlays() { Screen::for_each([&](auto& screen) { screen.compositor_screen_data().m_window_stack_switch_overlay = nullptr; return IterationDecision::Continue; }); } void Compositor::stop_window_stack_switch_overlay_timer() { if (m_stack_switch_overlay_timer) { // Cancel any timer, we're going to delete the overlay m_stack_switch_overlay_timer->stop(); m_stack_switch_overlay_timer = nullptr; } } void Compositor::start_window_stack_switch_overlay_timer() { if (m_stack_switch_overlay_timer) { m_stack_switch_overlay_timer->stop(); m_stack_switch_overlay_timer = nullptr; } bool have_overlay = false; Screen::for_each([&](auto& screen) { if (screen.compositor_screen_data().m_window_stack_switch_overlay) { have_overlay = true; return IterationDecision::Break; } return IterationDecision::Continue; }); if (!have_overlay) return; m_stack_switch_overlay_timer = Core::Timer::create_single_shot( 500, [this] { remove_window_stack_switch_overlays(); }, this); m_stack_switch_overlay_timer->start(); } void Compositor::finish_window_stack_switch() { VERIFY(m_transitioning_to_window_stack); VERIFY(m_current_window_stack); VERIFY(m_transitioning_to_window_stack != m_current_window_stack); m_current_window_stack->set_transition_offset({}, {}); m_transitioning_to_window_stack->set_transition_offset({}, {}); auto* previous_window_stack = m_current_window_stack; m_current_window_stack = m_transitioning_to_window_stack; m_transitioning_to_window_stack = nullptr; m_window_stack_transition_animation = nullptr; auto& wm = WindowManager::the(); if (!wm.m_switcher.is_visible()) previous_window_stack->set_all_occluded(true); wm.did_switch_window_stack({}, *previous_window_stack, *m_current_window_stack); invalidate_occlusions(); // Rather than invalidating the entire we could invalidate all render rectangles // that are affected by the transition offset before and after changing it. invalidate_screen(); start_window_stack_switch_overlay_timer(); } void Compositor::set_current_window_stack_no_transition(WindowStack& new_window_stack) { if (m_transitioning_to_window_stack) { finish_window_stack_switch(); VERIFY(!m_window_stack_transition_animation); VERIFY(!m_transitioning_to_window_stack); } if (m_current_window_stack == &new_window_stack) return; m_current_window_stack = &new_window_stack; invalidate_for_window_stack_merge_or_change(); } void Compositor::invalidate_for_window_stack_merge_or_change() { invalidate_occlusions(); invalidate_screen(); } void Compositor::switch_to_window_stack(WindowStack& new_window_stack, bool show_overlay) { if (m_transitioning_to_window_stack) { if (m_transitioning_to_window_stack == &new_window_stack) return; // A switch is in progress, but the user is impatient. Finish the transition instantly finish_window_stack_switch(); VERIFY(!m_window_stack_transition_animation); // Now switch to the next target as usual } VERIFY(m_current_window_stack); if (&new_window_stack == m_current_window_stack) { // So that the user knows which stack they're on, show the overlay briefly if (show_overlay) { create_window_stack_switch_overlay(*m_current_window_stack); start_window_stack_switch_overlay_timer(); } else { stop_window_stack_switch_overlay_timer(); remove_window_stack_switch_overlays(); } return; } VERIFY(!m_transitioning_to_window_stack); m_transitioning_to_window_stack = &new_window_stack; auto window_stack_size = Screen::bounding_rect().size(); int delta_x = 0; if (new_window_stack.column() < m_current_window_stack->column()) delta_x = window_stack_size.width(); else if (new_window_stack.column() > m_current_window_stack->column()) delta_x = -window_stack_size.width(); int delta_y = 0; if (new_window_stack.row() < m_current_window_stack->row()) delta_y = window_stack_size.height(); else if (new_window_stack.row() > m_current_window_stack->row()) { delta_y = -window_stack_size.height(); } m_transitioning_to_window_stack->set_transition_offset({}, { -delta_x, -delta_y }); m_current_window_stack->set_transition_offset({}, {}); if (show_overlay) { // We start the timer when the animation ends! create_window_stack_switch_overlay(*m_transitioning_to_window_stack); } else { stop_window_stack_switch_overlay_timer(); remove_window_stack_switch_overlays(); } VERIFY(!m_window_stack_transition_animation); m_window_stack_transition_animation = Animation::create(); m_window_stack_transition_animation->set_duration(250); m_window_stack_transition_animation->on_update = [this, delta_x, delta_y](float progress, Gfx::Painter&, Screen&, Gfx::DisjointRectSet&) { VERIFY(m_transitioning_to_window_stack); VERIFY(m_current_window_stack); // Set transition offset for the window stack we're transitioning out of auto previous_transition_offset_from = m_current_window_stack->transition_offset(); Gfx::IntPoint transition_offset_from { (float)delta_x * progress, (float)delta_y * progress }; if (previous_transition_offset_from == transition_offset_from) return; { // we need to render both, the existing dirty rectangles as well as where we're shifting to auto translated_dirty_rects = m_dirty_screen_rects.clone(); auto transition_delta = transition_offset_from - previous_transition_offset_from; translated_dirty_rects.translate_by(transition_delta); m_dirty_screen_rects.add(translated_dirty_rects.intersected(Screen::bounding_rect())); } m_current_window_stack->set_transition_offset({}, transition_offset_from); // Set transition offset for the window stack we're transitioning to Gfx::IntPoint transition_offset_to { (float)-delta_x * (1.0f - progress), (float)-delta_y * (1.0f - progress) }; m_transitioning_to_window_stack->set_transition_offset({}, transition_offset_to); invalidate_occlusions(); // Rather than invalidating the entire we could invalidate all render rectangles // that are affected by the transition offset before and after changing it. invalidate_screen(); }; m_window_stack_transition_animation->on_stop = [this] { finish_window_stack_switch(); }; m_window_stack_transition_animation->start(); } }