diff --git a/Userland/Services/WindowServer/Compositor.cpp b/Userland/Services/WindowServer/Compositor.cpp index 5c207745ddd..b51b7cab2d2 100644 --- a/Userland/Services/WindowServer/Compositor.cpp +++ b/Userland/Services/WindowServer/Compositor.cpp @@ -75,33 +75,11 @@ const Gfx::Bitmap* Compositor::cursor_bitmap_for_screenshot(Badge, Screen& screen) const { - return *m_screen_data[screen.index()].m_front_bitmap; + return *screen.compositor_screen_data().m_front_bitmap; } -void Compositor::ScreenData::init_bitmaps(Compositor& compositor, Screen& screen) +void CompositorScreenData::init_bitmaps(Compositor& compositor, Screen& screen) { - m_has_flipped = false; - m_have_flush_rects = false; - m_buffers_are_flipped = false; - m_screen_can_set_buffer = screen.can_set_buffer(); - - auto size = screen.size(); - - m_front_bitmap = Gfx::Bitmap::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()); - - if (m_screen_can_set_buffer) - m_back_bitmap = Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::BGRx8888, size, screen.scale_factor(), screen.pitch(), screen.scanline(1, 0)); - else - m_back_bitmap = Gfx::Bitmap::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 = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, size, screen.scale_factor()); - m_temp_painter = make(*m_temp_bitmap); - m_temp_painter->translate(-screen.rect().location()); - // 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); @@ -109,13 +87,40 @@ void Compositor::ScreenData::init_bitmaps(Compositor& compositor, Screen& screen } 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::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::create_wrapper(Gfx::BitmapFormat::BGRx8888, size, screen.scale_factor(), screen.pitch(), screen.scanline(1, 0)); + else + m_back_bitmap = Gfx::Bitmap::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::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() { - m_screen_data.resize(Screen::count()); Screen::for_each([&](auto& screen) { - m_screen_data[screen.index()].init_bitmaps(*this, screen); + screen.compositor_screen_data().init_bitmaps(*this, screen); return IterationDecision::Continue; }); @@ -231,12 +236,14 @@ void Compositor::compose() auto& cursor_screen = ScreenInput::the().cursor_location_screen(); - for (auto& screen_data : m_screen_data) { + 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(); @@ -247,8 +254,7 @@ void Compositor::compose() 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; - auto& screen_data = m_screen_data[cursor_screen.index()]; - if (screen_data.restore_cursor_back(cursor_screen, previous_cursor_rect)) + if (cursor_screen.compositor_screen_data().restore_cursor_back(cursor_screen, previous_cursor_rect)) previous_cursor_screen = &screen; } }; @@ -257,15 +263,14 @@ void Compositor::compose() // 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) { - auto& screen_data = m_screen_data[m_current_cursor_screen->index()]; - if (screen_data.restore_cursor_back(*m_current_cursor_screen, previous_cursor_rect)) + 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 = m_screen_data[screen.index()]; + 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)); @@ -275,7 +280,7 @@ void Compositor::compose() }; auto prepare_transparency_rect = [&](Screen& screen, const Gfx::IntRect& rect) { - auto& screen_data = m_screen_data[screen.index()]; + 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()) { @@ -288,7 +293,7 @@ void Compositor::compose() check_restore_cursor_back(screen, rect); }; - if (!m_screen_data[cursor_screen.index()].m_cursor_back_bitmap || m_invalidated_cursor) + 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) { @@ -319,7 +324,7 @@ void Compositor::compose() auto screen_rect = screen.rect(); auto screen_render_rect = screen_rect.intersected(render_rect); if (!screen_render_rect.is_empty()) { - auto& back_painter = *m_screen_data[screen.index()].m_back_painter; + auto& back_painter = *screen.compositor_screen_data().m_back_painter; dbgln_if(COMPOSE_DEBUG, " render wallpaper opaque: {} on screen #{}", screen_render_rect, screen.index()); prepare_rect(screen, render_rect); paint_wallpaper(screen, back_painter, render_rect, screen_rect); @@ -450,7 +455,7 @@ void Compositor::compose() dbgln_if(COMPOSE_DEBUG, " render opaque: {} on screen #{}", screen_render_rect, screen->index()); prepare_rect(*screen, screen_render_rect); - auto& back_painter = *m_screen_data[screen->index()].m_back_painter; + 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); @@ -471,7 +476,7 @@ void Compositor::compose() continue; dbgln_if(COMPOSE_DEBUG, " render wallpaper: {} on screen #{}", screen_render_rect, screen->index()); - auto& temp_painter = *m_screen_data[screen->index()].m_temp_painter; + 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); } @@ -489,7 +494,7 @@ void Compositor::compose() dbgln_if(COMPOSE_DEBUG, " render transparent: {} on screen #{}", screen_render_rect, screen->index()); prepare_transparency_rect(*screen, screen_render_rect); - auto& temp_painter = *m_screen_data[screen->index()].m_temp_painter; + 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); @@ -518,7 +523,7 @@ void Compositor::compose() VERIFY(![&]() { bool is_overlapping = false; Screen::for_each([&](auto& screen) { - auto& screen_data = m_screen_data[screen.index()]; + 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()) { @@ -543,7 +548,7 @@ void Compositor::compose() // Copy anything rendered to the temporary buffer to the back buffer Screen::for_each([&](auto& screen) { auto screen_rect = screen.rect(); - auto& screen_data = m_screen_data[screen.index()]; + 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; @@ -556,7 +561,7 @@ void Compositor::compose() if (!m_animations.is_empty()) { Screen::for_each([&](auto& screen) { - auto& screen_data = m_screen_data[screen.index()]; + 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; @@ -568,7 +573,7 @@ void Compositor::compose() } if (need_to_draw_cursor) { - auto& screen_data = m_screen_data[cursor_screen.index()]; + auto& screen_data = cursor_screen.compositor_screen_data(); screen_data.draw_cursor(cursor_screen, cursor_rect); } @@ -580,7 +585,7 @@ void Compositor::compose() void Compositor::flush(Screen& screen) { - auto& screen_data = m_screen_data[screen.index()]; + 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)) { @@ -793,7 +798,7 @@ bool Compositor::set_wallpaper(const String& path, Function&& callba return true; } -void Compositor::ScreenData::flip_buffers(Screen& screen) +void CompositorScreenData::flip_buffers(Screen& screen) { VERIFY(m_screen_can_set_buffer); swap(m_front_bitmap, m_back_bitmap); @@ -861,7 +866,7 @@ 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 = m_screen_data[screen->index()]; + auto& screen_data = screen->compositor_screen_data(); auto& painter = screen_data.overlay_painter(); screen_data.for_each_intersected_flushing_rect(overlay.current_render_rect(), [&](auto& intersected_overlay_rect) { Gfx::PainterStateSaver saver(painter); @@ -905,7 +910,7 @@ void Compositor::remove_overlay(Overlay& overlay) overlay_rects_changed(); } -void Compositor::ScreenData::draw_cursor(Screen& screen, const Gfx::IntRect& cursor_rect) +void CompositorScreenData::draw_cursor(Screen& screen, const Gfx::IntRect& cursor_rect) { auto& wm = WindowManager::the(); @@ -927,7 +932,7 @@ void Compositor::ScreenData::draw_cursor(Screen& screen, const Gfx::IntRect& cur m_cursor_back_is_valid = true; } -bool Compositor::ScreenData::restore_cursor_back(Screen& screen, Gfx::IntRect& last_cursor_rect) +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; @@ -969,17 +974,19 @@ void Compositor::decrement_display_link_count(Badge) void Compositor::invalidate_current_screen_number_rects() { - for (auto& screen_data : m_screen_data) { + 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 = m_screen_data[screen.index()]; + 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); @@ -991,8 +998,10 @@ void Compositor::decrement_show_screen_number(Badge) { if (--m_show_screen_number_count == 0) { invalidate_current_screen_number_rects(); - for (auto& screen_data : m_screen_data) - screen_data.m_screen_number_overlay = nullptr; + Screen::for_each([&](auto& screen) { + screen.compositor_screen_data().m_screen_number_overlay = nullptr; + return IterationDecision::Continue; + }); } } @@ -1412,7 +1421,7 @@ void Compositor::unregister_animation(Badge, Animation& animation) void Compositor::update_animations(Screen& screen, Gfx::DisjointRectSet& flush_rects) { - auto& painter = *m_screen_data[screen.index()].m_back_painter; + auto& painter = *screen.compositor_screen_data().m_back_painter; for (RefPtr animation : m_animations) { animation->update({}, painter, screen, flush_rects); } @@ -1422,7 +1431,7 @@ void Compositor::create_window_stack_switch_overlay(WindowStack& target_stack) { stop_window_stack_switch_overlay_timer(); Screen::for_each([&](auto& screen) { - auto& screen_data = m_screen_data[screen.index()]; + 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); @@ -1433,8 +1442,7 @@ void Compositor::create_window_stack_switch_overlay(WindowStack& target_stack) void Compositor::remove_window_stack_switch_overlays() { Screen::for_each([&](auto& screen) { - auto& screen_data = m_screen_data[screen.index()]; - screen_data.m_window_stack_switch_overlay = nullptr; + screen.compositor_screen_data().m_window_stack_switch_overlay = nullptr; return IterationDecision::Continue; }); } @@ -1456,8 +1464,7 @@ void Compositor::start_window_stack_switch_overlay_timer() } bool have_overlay = false; Screen::for_each([&](auto& screen) { - auto& screen_data = m_screen_data[screen.index()]; - if (screen_data.m_window_stack_switch_overlay) { + if (screen.compositor_screen_data().m_window_stack_switch_overlay) { have_overlay = true; return IterationDecision::Break; } diff --git a/Userland/Services/WindowServer/Compositor.h b/Userland/Services/WindowServer/Compositor.h index cbb9e8819d0..3cfd6772685 100644 --- a/Userland/Services/WindowServer/Compositor.h +++ b/Userland/Services/WindowServer/Compositor.h @@ -18,6 +18,7 @@ namespace WindowServer { class Animation; class ClientConnection; +class Compositor; class Cursor; class MultiScaleBitmaps; class Window; @@ -31,8 +32,62 @@ enum class WallpaperMode { Unchecked }; +struct CompositorScreenData { + RefPtr m_front_bitmap; + RefPtr m_back_bitmap; + RefPtr m_temp_bitmap; + OwnPtr m_back_painter; + OwnPtr m_front_painter; + OwnPtr m_temp_painter; + RefPtr m_cursor_back_bitmap; + OwnPtr m_cursor_back_painter; + Gfx::IntRect m_last_cursor_rect; + OwnPtr m_screen_number_overlay; + OwnPtr m_window_stack_switch_overlay; + bool m_buffers_are_flipped { false }; + bool m_screen_can_set_buffer { false }; + bool m_has_flipped { false }; + bool m_cursor_back_is_valid { false }; + bool m_have_flush_rects { false }; + + Gfx::DisjointRectSet m_flush_rects; + Gfx::DisjointRectSet m_flush_transparent_rects; + Gfx::DisjointRectSet m_flush_special_rects; + + Gfx::Painter& overlay_painter() { return *m_temp_painter; } + + void init_bitmaps(Compositor&, Screen&); + void flip_buffers(Screen&); + void draw_cursor(Screen&, const Gfx::IntRect&); + bool restore_cursor_back(Screen&, Gfx::IntRect&); + + template + IterationDecision for_each_intersected_flushing_rect(Gfx::IntRect const& intersecting_rect, F f) + { + auto iterate_flush_rects = [&](Gfx::DisjointRectSet const& flush_rects) { + for (auto& rect : flush_rects.rects()) { + auto intersection = intersecting_rect.intersected(rect); + if (intersection.is_empty()) + continue; + IterationDecision decision = f(intersection); + if (decision != IterationDecision::Continue) + return decision; + } + return IterationDecision::Continue; + }; + auto decision = iterate_flush_rects(m_flush_rects); + if (decision != IterationDecision::Continue) + return decision; + // We do not have to iterate m_flush_special_rects here as these + // technically should be removed anyway. m_flush_rects and + // m_flush_transparent_rects should cover everything already + return iterate_flush_rects(m_flush_transparent_rects); + } +}; + class Compositor final : public Core::Object { C_OBJECT(Compositor) + friend struct CompositorScreenData; friend class Overlay; public: @@ -127,6 +182,11 @@ public: void set_flash_flush(bool b) { m_flash_flush = b; } + static NonnullOwnPtr create_screen_data(Badge) + { + return adopt_own(*new CompositorScreenData()); + } + private: Compositor(); void init_bitmaps(); @@ -160,61 +220,6 @@ private: bool m_invalidated_cursor { false }; bool m_overlay_rects_changed { false }; - struct ScreenData { - RefPtr m_front_bitmap; - RefPtr m_back_bitmap; - RefPtr m_temp_bitmap; - OwnPtr m_back_painter; - OwnPtr m_front_painter; - OwnPtr m_temp_painter; - RefPtr m_cursor_back_bitmap; - OwnPtr m_cursor_back_painter; - Gfx::IntRect m_last_cursor_rect; - OwnPtr m_screen_number_overlay; - OwnPtr m_window_stack_switch_overlay; - bool m_buffers_are_flipped { false }; - bool m_screen_can_set_buffer { false }; - bool m_has_flipped { false }; - bool m_cursor_back_is_valid { false }; - bool m_have_flush_rects { false }; - - Gfx::DisjointRectSet m_flush_rects; - Gfx::DisjointRectSet m_flush_transparent_rects; - Gfx::DisjointRectSet m_flush_special_rects; - - Gfx::Painter& overlay_painter() { return *m_temp_painter; } - - void init_bitmaps(Compositor&, Screen&); - void flip_buffers(Screen&); - void draw_cursor(Screen&, const Gfx::IntRect&); - bool restore_cursor_back(Screen&, Gfx::IntRect&); - - template - IterationDecision for_each_intersected_flushing_rect(Gfx::IntRect const& intersecting_rect, F f) - { - auto iterate_flush_rects = [&](Gfx::DisjointRectSet const& flush_rects) { - for (auto& rect : flush_rects.rects()) { - auto intersection = intersecting_rect.intersected(rect); - if (intersection.is_empty()) - continue; - IterationDecision decision = f(intersection); - if (decision != IterationDecision::Continue) - return decision; - } - return IterationDecision::Continue; - }; - auto decision = iterate_flush_rects(m_flush_rects); - if (decision != IterationDecision::Continue) - return decision; - // We do not have to iterate m_flush_special_rects here as these - // technically should be removed anyway. m_flush_rects and - // m_flush_transparent_rects should cover everything already - return iterate_flush_rects(m_flush_transparent_rects); - } - }; - friend struct ScreenData; - Vector m_screen_data; - IntrusiveList, &Overlay::m_list_node> m_overlay_list; Gfx::DisjointRectSet m_overlay_rects; Gfx::DisjointRectSet m_dirty_screen_rects; diff --git a/Userland/Services/WindowServer/Screen.cpp b/Userland/Services/WindowServer/Screen.cpp index 5251a172152..113dd9d7554 100644 --- a/Userland/Services/WindowServer/Screen.cpp +++ b/Userland/Services/WindowServer/Screen.cpp @@ -19,7 +19,7 @@ namespace WindowServer { -NonnullOwnPtrVector Screen::s_screens; +NonnullRefPtrVector Screen::s_screens; Screen* Screen::s_main_screen { nullptr }; Gfx::IntRect Screen::s_bounding_screens_rect {}; ScreenLayout Screen::s_layout; @@ -55,39 +55,137 @@ bool Screen::apply_layout(ScreenLayout&& screen_layout, String& error_msg) if (!screen_layout.is_valid(&error_msg)) return false; + if (screen_layout == s_layout) + return true; + + bool place_cursor_on_main_screen = find_by_location(ScreenInput::the().cursor_location()) == nullptr; + + HashMap current_to_new_indices_map; + HashMap new_to_current_indices_map; + HashMap> devices_no_longer_used; + for (size_t i = 0; i < s_layout.screens.size(); i++) { + auto& screen = s_layout.screens[i]; + bool found = false; + for (size_t j = 0; j < screen_layout.screens.size(); j++) { + auto& new_screen = screen_layout.screens[j]; + if (new_screen.device == screen.device) { + current_to_new_indices_map.set(i, j); + new_to_current_indices_map.set(j, i); + found = true; + break; + } + } + + if (!found) + devices_no_longer_used.set(i, s_screens[i]); + } + HashMap screens_with_resolution_change; + HashMap screens_with_scale_change; + for (auto& it : current_to_new_indices_map) { + auto& screen = s_layout.screens[it.key]; + auto& new_screen = screen_layout.screens[it.value]; + if (screen.resolution != new_screen.resolution) + screens_with_resolution_change.set(&s_screens[it.key], it.value); + if (screen.scale_factor != new_screen.scale_factor) + screens_with_scale_change.set(&s_screens[it.key], it.value); + } + auto screens_backup = move(s_screens); auto layout_backup = move(s_layout); - for (auto& old_screen : screens_backup) - old_screen.close_device(); + + for (auto& it : screens_with_resolution_change) { + auto& existing_screen = *it.key; + dbgln("Closing device {} in preparation for resolution change", layout_backup.screens[existing_screen.index()].device); + existing_screen.close_device(); + } AK::ArmedScopeGuard rollback([&] { for (auto& screen : s_screens) screen.close_device(); s_screens = move(screens_backup); s_layout = move(layout_backup); - for (auto& old_screen : screens_backup) { - if (!old_screen.open_device()) { - // Don't set error_msg here, it should already be set - dbgln("Rolling back screen layout failed: could not open device"); + for (size_t i = 0; i < s_screens.size(); i++) { + auto& old_screen = s_screens[i]; + // Restore the original screen index in case it changed + old_screen.set_index(i); + if (i == s_layout.main_screen_index) + old_screen.make_main_screen(); + bool changed_scale = screens_with_scale_change.contains(&old_screen); + if (screens_with_resolution_change.contains(&old_screen)) { + if (old_screen.open_device()) { + // The resolution was changed, so we also implicitly applied the new scale factor + changed_scale = false; + } else { + // Don't set error_msg here, it should already be set + dbgln("Rolling back screen layout failed: could not open device"); + } } + + old_screen.update_virtual_rect(); + if (changed_scale) + old_screen.scale_factor_changed(); } update_bounding_rect(); }); s_layout = move(screen_layout); for (size_t index = 0; index < s_layout.screens.size(); index++) { - auto* screen = WindowServer::Screen::create(s_layout.screens[index]); - if (!screen) { - error_msg = String::formatted("Error creating screen #{}", index); + Screen* screen; + bool need_to_open_device; + if (auto it = new_to_current_indices_map.find(index); it != new_to_current_indices_map.end()) { + // Re-use the existing screen instance + screen = &screens_backup[it->value]; + s_screens.append(*screen); + screen->set_index(index); + + need_to_open_device = screens_with_resolution_change.contains(screen); + } else { + screen = WindowServer::Screen::create(index); + if (!screen) { + error_msg = String::formatted("Error creating screen #{}", index); + return false; + } + + need_to_open_device = true; + } + + if (need_to_open_device && !screen->open_device()) { + error_msg = String::formatted("Error opening device for screen #{}", index); return false; } + screen->update_virtual_rect(); + if (!need_to_open_device && screens_with_scale_change.contains(screen)) + screen->scale_factor_changed(); + + VERIFY(screen); + VERIFY(index == screen->index()); + if (s_layout.main_screen_index == index) screen->make_main_screen(); - - screen->open_device(); } rollback.disarm(); + + if (place_cursor_on_main_screen) { + ScreenInput::the().set_cursor_location(Screen::main().rect().center()); + } else { + auto cursor_location = ScreenInput::the().cursor_location(); + if (!find_by_location(cursor_location)) { + // Cursor is off screen, try to find the closest location on another screen + float closest_distance = 0; + Optional closest_point; + for (auto& screen : s_screens) { + auto closest_point_on_screen_rect = screen.rect().closest_to(cursor_location); + auto distance = closest_point_on_screen_rect.distance_from(cursor_location); + if (!closest_point.has_value() || distance < closest_distance) { + closest_distance = distance; + closest_point = closest_point_on_screen_rect; + } + } + ScreenInput::the().set_cursor_location(closest_point.value()); // We should always have one + } + } + update_bounding_rect(); update_scale_factors_in_use(); return true; @@ -108,17 +206,13 @@ void Screen::update_scale_factors_in_use() }); } -Screen::Screen(ScreenLayout::Screen& screen_info) - : m_virtual_rect(screen_info.location, { screen_info.resolution.width() / screen_info.scale_factor, screen_info.resolution.height() / screen_info.scale_factor }) +Screen::Screen(size_t screen_index) + : m_index(screen_index) , m_framebuffer_data(adopt_own(*new ScreenFBData())) - , m_info(screen_info) + , m_compositor_screen_data(Compositor::create_screen_data({})) { + update_virtual_rect(); open_device(); - - // If the cursor is not in a valid screen (yet), force it into one - dbgln("Screen() current physical cursor location: {} rect: {}", ScreenInput::the().cursor_location(), rect()); - if (!find_by_location(ScreenInput::the().cursor_location())) - ScreenInput::the().set_cursor_location(rect().center()); } Screen::~Screen() @@ -129,9 +223,10 @@ Screen::~Screen() bool Screen::open_device() { close_device(); - m_framebuffer_fd = open(m_info.device.characters(), O_RDWR | O_CLOEXEC); + auto& info = screen_layout_info(); + m_framebuffer_fd = open(info.device.characters(), O_RDWR | O_CLOEXEC); if (m_framebuffer_fd < 0) { - perror(String::formatted("failed to open {}", m_info.device).characters()); + perror(String::formatted("failed to open {}", info.device).characters()); return false; } @@ -156,6 +251,19 @@ void Screen::close_device() } } +void Screen::update_virtual_rect() +{ + auto& screen_info = screen_layout_info(); + m_virtual_rect = { screen_info.location, { screen_info.resolution.width() / screen_info.scale_factor, screen_info.resolution.height() / screen_info.scale_factor } }; + dbgln("update_virtual_rect for screen #{}: {}", index(), m_virtual_rect); +} + +void Screen::scale_factor_changed() +{ + // Flush rects are affected by the screen factor + constrain_pending_flush_rects(); +} + void Screen::init() { set_resolution(true); @@ -208,12 +316,13 @@ bool Screen::set_resolution(bool initial) if (!initial) screen_with_cursor = &ScreenInput::the().cursor_location_screen(); - FBResolution physical_resolution { 0, (unsigned)m_info.resolution.width(), (unsigned)m_info.resolution.height() }; + auto& info = screen_layout_info(); + FBResolution physical_resolution { 0, (unsigned)info.resolution.width(), (unsigned)info.resolution.height() }; int rc = fb_set_resolution(m_framebuffer_fd, &physical_resolution); dbgln_if(WSSCREEN_DEBUG, "Screen #{}: fb_set_resolution() - return code {}", index(), rc); auto on_change_resolution = [&]() { - if (initial || physical_resolution.width != (unsigned)m_info.resolution.width() || physical_resolution.height != (unsigned)m_info.resolution.height()) { + if (initial || physical_resolution.width != (unsigned)info.resolution.width() || physical_resolution.height != (unsigned)info.resolution.height()) { if (m_framebuffer) { size_t previous_size_in_bytes = m_size_in_bytes; int rc = munmap(m_framebuffer, previous_size_in_bytes); @@ -240,8 +349,15 @@ bool Screen::set_resolution(bool initial) } } - m_info.resolution = { physical_resolution.width, physical_resolution.height }; + info.resolution = { physical_resolution.width, physical_resolution.height }; m_pitch = physical_resolution.pitch; + + update_virtual_rect(); + + // Since pending flush rects are affected by the scale factor + // update even if only the scale factor changed + constrain_pending_flush_rects(); + if (this == screen_with_cursor) { auto& screen_input = ScreenInput::the(); screen_input.set_cursor_location(screen_input.cursor_location().constrained(rect())); @@ -254,7 +370,7 @@ bool Screen::set_resolution(bool initial) } if (rc == -1) { int err = errno; - dbgln("Screen #{}: Failed to set resolution {}: {}", index(), m_info.resolution, strerror(err)); + dbgln("Screen #{}: Failed to set resolution {}: {}", index(), info.resolution, strerror(err)); on_change_resolution(); return false; } @@ -345,6 +461,30 @@ void ScreenInput::on_receive_keyboard_data(::KeyEvent kernel_event) Core::EventLoop::current().post_event(WindowManager::the(), move(message)); } +void Screen::constrain_pending_flush_rects() +{ + auto& fb_data = *m_framebuffer_data; + if (fb_data.pending_flush_rects.is_empty()) + return; + Gfx::IntRect screen_rect({}, rect().size()); + Gfx::DisjointRectSet rects; + for (auto& fb_rect : fb_data.pending_flush_rects) { + Gfx::IntRect rect { (int)fb_rect.x, (int)fb_rect.y, (int)fb_rect.width, (int)fb_rect.height }; + auto intersected_rect = rect.intersected(screen_rect); + if (!intersected_rect.is_empty()) + rects.add(intersected_rect); + } + fb_data.pending_flush_rects.clear_with_capacity(); + for (auto& rect : rects.rects()) { + fb_data.pending_flush_rects.append({ + x : (unsigned)rect.x(), + y : (unsigned)rect.y(), + width : (unsigned)rect.width(), + height : (unsigned)rect.height() + }); + } +} + void Screen::queue_flush_display_rect(Gfx::IntRect const& flush_region) { // NOTE: we don't scale until in Screen::flush_display so that when @@ -396,6 +536,7 @@ void Screen::flush_display(int buffer_index) // Now that we have a final set of rects, apply the scale factor auto scale_factor = this->scale_factor(); for (auto& flush_rect : fb_data.pending_flush_rects) { + VERIFY(Gfx::IntRect({}, m_virtual_rect.size()).contains({ (int)flush_rect.x, (int)flush_rect.y, (int)flush_rect.width, (int)flush_rect.height })); flush_rect.x *= scale_factor; flush_rect.y *= scale_factor; flush_rect.width *= scale_factor; @@ -425,6 +566,7 @@ void Screen::flush_display_front_buffer(int front_buffer_index, Gfx::IntRect& re .height = (unsigned)(rect.height() * scale_factor) }; + VERIFY(Gfx::IntRect({}, m_virtual_rect.size()).contains(rect)); if (fb_flush_buffers(m_framebuffer_fd, front_buffer_index, &flush_rect, 1) < 0) { int err = errno; if (err == ENOTSUP) diff --git a/Userland/Services/WindowServer/Screen.h b/Userland/Services/WindowServer/Screen.h index 2182c9c139f..0a961a6cb2e 100644 --- a/Userland/Services/WindowServer/Screen.h +++ b/Userland/Services/WindowServer/Screen.h @@ -7,7 +7,7 @@ #pragma once #include "ScreenLayout.h" -#include +#include #include #include #include @@ -58,14 +58,15 @@ private: unsigned m_scroll_step_size { 1 }; }; +struct CompositorScreenData; struct ScreenFBData; -class Screen { +class Screen : public RefCounted { public: template static Screen* create(Args&&... args) { - auto screen = adopt_own(*new Screen(forward(args)...)); + auto screen = adopt_ref(*new Screen(forward(args)...)); if (!screen->is_opened()) return nullptr; auto* screen_ptr = screen.ptr(); @@ -155,7 +156,7 @@ public: int width() const { return m_virtual_rect.width(); } int height() const { return m_virtual_rect.height(); } - int scale_factor() const { return m_info.scale_factor; } + int scale_factor() const { return screen_layout_info().scale_factor; } Gfx::RGBA32* scanline(int buffer_index, int y); @@ -169,12 +170,16 @@ public: void flush_display(int buffer_index); void flush_display_front_buffer(int front_buffer_index, Gfx::IntRect&); + CompositorScreenData& compositor_screen_data() { return *m_compositor_screen_data; } + private: - Screen(ScreenLayout::Screen&); + Screen(size_t); bool open_device(); void close_device(); void init(); + void scale_factor_changed(); bool set_resolution(bool initial); + void constrain_pending_flush_rects(); static void update_indices() { for (size_t i = 0; i < s_screens.size(); i++) @@ -185,7 +190,12 @@ private: bool is_opened() const { return m_framebuffer_fd >= 0; } - static NonnullOwnPtrVector s_screens; + void set_index(size_t index) { m_index = index; } + void update_virtual_rect(); + ScreenLayout::Screen& screen_layout_info() { return s_layout.screens[m_index]; } + ScreenLayout::Screen const& screen_layout_info() const { return s_layout.screens[m_index]; } + + static NonnullRefPtrVector s_screens; static Screen* s_main_screen; static Gfx::IntRect s_bounding_screens_rect; static ScreenLayout s_layout; @@ -203,8 +213,7 @@ private: Gfx::IntRect m_virtual_rect; int m_framebuffer_fd { -1 }; NonnullOwnPtr m_framebuffer_data; - - ScreenLayout::Screen& m_info; + NonnullOwnPtr m_compositor_screen_data; }; inline Gfx::RGBA32* Screen::scanline(int buffer_index, int y) diff --git a/Userland/Services/WindowServer/ScreenLayout.h b/Userland/Services/WindowServer/ScreenLayout.h index c467ba9f42b..90d11a61efe 100644 --- a/Userland/Services/WindowServer/ScreenLayout.h +++ b/Userland/Services/WindowServer/ScreenLayout.h @@ -35,7 +35,7 @@ public: unsigned main_screen_index { 0 }; bool is_valid(String* error_msg = nullptr) const; - void normalize(); + bool normalize(); bool load_config(const Core::ConfigFile& config_file, String* error_msg = nullptr); bool save_config(Core::ConfigFile& config_file, bool sync = true) const; bool try_auto_add_framebuffer(String const&); diff --git a/Userland/Services/WindowServer/ScreenLayout.ipp b/Userland/Services/WindowServer/ScreenLayout.ipp index 7428a4ebdbe..cc86f7fae9a 100644 --- a/Userland/Services/WindowServer/ScreenLayout.ipp +++ b/Userland/Services/WindowServer/ScreenLayout.ipp @@ -105,21 +105,124 @@ bool ScreenLayout::is_valid(String* error_msg) const return true; } -void ScreenLayout::normalize() +bool ScreenLayout::normalize() { + // Check for any overlaps and try to move screens + Vector screen_virtual_rects; + for (auto& screen : screens) + screen_virtual_rects.append(screen.virtual_rect()); + + bool did_change = false; + for (;;) { + // Separate any overlapping screens + if (Gfx::IntRect::disperse(screen_virtual_rects)) { + did_change = true; + continue; + } + + // Check if all screens are still reachable + Vector reachable_rects; + + auto recalculate_reachable = [&]() { + reachable_rects = { &screen_virtual_rects[main_screen_index] }; + bool did_reach_another; + do { + did_reach_another = false; + auto& latest_reachable_rect = *reachable_rects[reachable_rects.size() - 1]; + for (auto& rect : screen_virtual_rects) { + if (&rect == &latest_reachable_rect || reachable_rects.contains_slow(&rect)) + continue; + if (rect.is_adjacent(latest_reachable_rect)) { + reachable_rects.append(&rect); + did_reach_another = true; + break; + } + } + } while (did_reach_another); + }; + + recalculate_reachable(); + if (reachable_rects.size() != screen_virtual_rects.size()) { + // Some screens were not reachable, try to move one somewhere closer + for (auto& screen_rect : screen_virtual_rects) { + if (reachable_rects.contains_slow(&screen_rect)) + continue; + + float closest_distance = 0; + Gfx::IntRect* closest_rect = nullptr; + for (auto& screen_rect2 : screen_virtual_rects) { + if (&screen_rect2 == &screen_rect) + continue; + if (!reachable_rects.contains_slow(&screen_rect2)) + continue; + auto distance = screen_rect.outside_center_point_distance_to(screen_rect2); + if (!closest_rect || distance < closest_distance) { + closest_distance = distance; + closest_rect = &screen_rect2; + } + } + VERIFY(closest_rect); // We should always have one! + VERIFY(closest_rect != &screen_rect); + + // Move the screen_rect closer to closest_rect + auto is_adjacent_to_reachable = [&]() { + for (auto* rect : reachable_rects) { + if (rect == &screen_rect) + continue; + if (screen_rect.is_adjacent(*rect)) + return true; + } + return false; + }; + + // Move it until we're touching a reachable screen + do { + auto outside_center_points = screen_rect.closest_outside_center_points(*closest_rect); + int delta_x = 0; + if (outside_center_points[0].x() < outside_center_points[1].x()) + delta_x = 1; + else if (outside_center_points[0].x() > outside_center_points[1].x()) + delta_x = -1; + int delta_y = 0; + if (outside_center_points[0].y() < outside_center_points[1].y()) + delta_y = 1; + else if (outside_center_points[0].y() > outside_center_points[1].y()) + delta_y = -1; + VERIFY(delta_x != 0 || delta_y != 0); + screen_rect.translate_by(delta_x, delta_y); + } while (!is_adjacent_to_reachable()); + + recalculate_reachable(); + did_change = true; + break; // We only try to move one at at time + } + + // Moved the screen, re-evaluate + continue; + } + break; + } + int smallest_x = 0; int smallest_y = 0; - for (size_t i = 0; i < screens.size(); i++) { - auto& screen = screens[i]; - if (i == 0 || screen.location.x() < smallest_x) - smallest_x = screen.location.x(); - if (i == 0 || screen.location.y() < smallest_y) - smallest_y = screen.location.y(); + for (size_t i = 0; i < screen_virtual_rects.size(); i++) { + auto& rect = screen_virtual_rects[i]; + if (i == 0 || rect.x() < smallest_x) + smallest_x = rect.x(); + if (i == 0 || rect.y() < smallest_y) + smallest_y = rect.y(); } if (smallest_x != 0 || smallest_y != 0) { - for (auto& screen : screens) - screen.location.translate_by(-smallest_x, -smallest_y); + for (auto& rect : screen_virtual_rects) + rect.translate_by(-smallest_x, -smallest_y); + did_change = true; } + + for (size_t i = 0; i < screens.size(); i++) + screens[i].location = screen_virtual_rects[i].location(); + + VERIFY(is_valid()); + return did_change; } bool ScreenLayout::load_config(const Core::ConfigFile& config_file, String* error_msg) @@ -213,11 +316,24 @@ bool ScreenLayout::try_auto_add_framebuffer(String const& device_path) resolution.height = main_screen.resolution.height(); } + auto append_screen = [&](Gfx::IntRect const& new_screen_rect) { + screens.append({ .device = device_path, + .location = new_screen_rect.location(), + .resolution = new_screen_rect.size(), + .scale_factor = 1 }); + }; + + if (screens.is_empty()) { + append_screen({ 0, 0, (int)resolution.width, (int)resolution.height }); + return true; + } + auto original_screens = move(screens); screens = original_screens; ArmedScopeGuard screens_guard([&] { screens = move(original_screens); }); + // Now that we know the current resolution, try to find a location that we can add onto // TODO: make this a little more sophisticated in case a more complex layout is already configured for (auto& screen : screens) { @@ -240,13 +356,7 @@ bool ScreenLayout::try_auto_add_framebuffer(String const& device_path) } if (!collision) { - screens.append({ - .device = device_path, - .location = new_screen_rect.location(), - .resolution = new_screen_rect.size(), - .scale_factor = 1 - }); - + append_screen(new_screen_rect); if (is_valid()) { // We got lucky! screens_guard.disarm(); diff --git a/Userland/Services/WindowServer/main.cpp b/Userland/Services/WindowServer/main.cpp index 5977f657ed0..94a7fd91cb5 100644 --- a/Userland/Services/WindowServer/main.cpp +++ b/Userland/Services/WindowServer/main.cpp @@ -82,32 +82,52 @@ int main(int, char**) AK::HashTable fb_devices_configured; WindowServer::ScreenLayout screen_layout; String error_msg; - if (!screen_layout.load_config(*wm_config, &error_msg)) { + + auto add_unconfigured_devices = [&]() { + // Enumerate the /dev/fbX devices and try to set up any ones we find that we haven't already used + Core::DirIterator di("/dev", Core::DirIterator::SkipParentAndBaseDir); + while (di.has_next()) { + auto path = di.next_path(); + if (!path.starts_with("fb")) + continue; + auto full_path = String::formatted("/dev/{}", path); + if (!Core::File::is_device(full_path)) + continue; + if (fb_devices_configured.find(full_path) != fb_devices_configured.end()) + continue; + if (!screen_layout.try_auto_add_framebuffer(full_path)) + dbgln("Could not auto-add framebuffer device {} to screen layout", full_path); + } + }; + + auto apply_and_generate_generic_screen_layout = [&]() { + screen_layout = {}; + fb_devices_configured = {}; + add_unconfigured_devices(); + if (!WindowServer::Screen::apply_layout(move(screen_layout), error_msg)) { + dbgln("Failed to apply generated fallback screen layout: {}", error_msg); + return false; + } + + dbgln("Applied generated fallback screen layout!"); + return true; + }; + + if (screen_layout.load_config(*wm_config, &error_msg)) { + for (auto& screen_info : screen_layout.screens) + fb_devices_configured.set(screen_info.device); + + add_unconfigured_devices(); + + if (!WindowServer::Screen::apply_layout(move(screen_layout), error_msg)) { + dbgln("Error applying screen layout: {}", error_msg); + if (!apply_and_generate_generic_screen_layout()) + return 1; + } + } else { dbgln("Error loading screen configuration: {}", error_msg); - return 1; - } - - for (auto& screen_info : screen_layout.screens) - fb_devices_configured.set(screen_info.device); - - // Enumerate the /dev/fbX devices and try to set up any ones we find that we haven't already used - Core::DirIterator di("/dev", Core::DirIterator::SkipParentAndBaseDir); - while (di.has_next()) { - auto path = di.next_path(); - if (!path.starts_with("fb")) - continue; - auto full_path = String::formatted("/dev/{}", path); - if (!Core::File::is_device(full_path)) - continue; - if (fb_devices_configured.find(full_path) != fb_devices_configured.end()) - continue; - if (!screen_layout.try_auto_add_framebuffer(full_path)) - dbgln("Could not auto-add framebuffer device {} to screen layout", full_path); - } - - if (!WindowServer::Screen::apply_layout(move(screen_layout), error_msg)) { - dbgln("Error applying screen layout: {}", error_msg); - return 1; + if (!apply_and_generate_generic_screen_layout()) + return 1; } }