1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 08:27:46 +00:00

WindowServer: Add initial support for rendering on multiple screens

This allows WindowServer to use multiple framebuffer devices and
compose the desktop with any arbitrary layout. Currently, it is assumed
that it is configured contiguous and non-overlapping, but this should
eventually be enforced.

To make rendering efficient, each window now also tracks on which
screens it needs to be rendered. This way we don't have to iterate all
the windows for each screen but instead use the same rendering loop and
then only render to the screen (or screens) that the window actually
uses.
This commit is contained in:
Tom 2021-06-13 06:16:06 -06:00 committed by Andreas Kling
parent 499c33ae0c
commit 4392da970a
42 changed files with 1127 additions and 563 deletions

View file

@ -61,28 +61,44 @@ Compositor::Compositor()
},
this);
m_screen_can_set_buffer = Screen::the().can_set_buffer();
init_bitmaps();
}
void Compositor::init_bitmaps()
const Gfx::Bitmap& Compositor::front_bitmap_for_screenshot(Badge<ClientConnection>, Screen& screen) const
{
return *m_screen_data[screen.index()].m_front_bitmap;
}
void Compositor::ScreenData::init_bitmaps(Screen& screen)
{
auto& screen = Screen::the();
auto size = screen.size();
m_front_bitmap = Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::BGRx8888, size, screen.scale_factor(), screen.pitch(), screen.scanline(0));
m_front_painter = make<Gfx::Painter>(*m_front_bitmap);
m_front_painter->translate(-screen.rect().location());
if (m_screen_can_set_buffer)
if (screen.can_set_buffer())
m_back_bitmap = Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::BGRx8888, size, screen.scale_factor(), screen.pitch(), screen.scanline(screen.physical_height()));
else
m_back_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, size, screen.scale_factor());
m_back_painter = make<Gfx::Painter>(*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<Gfx::Painter>(*m_temp_bitmap);
m_temp_painter->translate(-screen.rect().location());
m_buffers_are_flipped = false;
m_screen_can_set_buffer = screen.can_set_buffer();
}
void Compositor::init_bitmaps()
{
m_screen_data.resize(Screen::count());
Screen::for_each([&](auto& screen) {
m_screen_data[screen.index()].init_bitmaps(screen);
return IterationDecision::Continue;
});
invalidate_screen();
}
@ -101,7 +117,6 @@ void Compositor::did_construct_window_manager(Badge<WindowManager>)
void Compositor::compose()
{
auto& wm = WindowManager::the();
auto& ws = Screen::the();
{
auto& current_cursor = wm.active_cursor();
@ -122,11 +137,26 @@ void Compositor::compose()
}
auto dirty_screen_rects = move(m_dirty_screen_rects);
dirty_screen_rects.add(m_last_geometry_label_damage_rect.intersected(ws.rect()));
dirty_screen_rects.add(m_last_dnd_rect.intersected(ws.rect()));
if (m_invalidated_cursor) {
if (wm.dnd_client())
dirty_screen_rects.add(wm.dnd_rect().intersected(ws.rect()));
auto* dnd_client = wm.dnd_client();
if (!m_last_geometry_label_damage_rect.is_empty() || !m_last_dnd_rect.is_empty() || (m_invalidated_cursor && dnd_client)) {
Screen::for_each([&](auto& screen) {
if (!m_last_geometry_label_damage_rect.is_empty()) {
auto rect = m_last_geometry_label_damage_rect.intersected(screen.rect());
if (!rect.is_empty())
dirty_screen_rects.add(rect);
}
if (!m_last_dnd_rect.is_empty()) {
auto rect = m_last_dnd_rect.intersected(screen.rect());
if (!rect.is_empty())
dirty_screen_rects.add(rect);
}
if (m_invalidated_cursor && dnd_client) {
auto rect = wm.dnd_rect().intersected(screen.rect());
if (!rect.is_empty())
dirty_screen_rects.add(rect);
}
return IterationDecision::Continue;
});
}
// Mark window regions as dirty that need to be re-rendered
@ -180,61 +210,81 @@ void Compositor::compose()
dbgln("dirty screen: {}", r);
}
Gfx::DisjointRectSet flush_rects;
Gfx::DisjointRectSet flush_transparent_rects;
Gfx::DisjointRectSet flush_special_rects;
auto& cursor_screen = ScreenInput::the().cursor_location_screen();
for (auto& screen_data : m_screen_data) {
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();
}
auto cursor_rect = current_cursor_rect();
bool need_to_draw_cursor = false;
auto back_painter = *m_back_painter;
auto temp_painter = *m_temp_painter;
auto check_restore_cursor_back = [&](const Gfx::IntRect& rect) {
if (!need_to_draw_cursor && rect.intersects(cursor_rect)) {
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;
restore_cursor_back();
auto& screen_data = m_screen_data[cursor_screen.index()];
if (screen_data.restore_cursor_back(cursor_screen, previous_cursor_rect))
previous_cursor_screen = &screen;
}
};
auto prepare_rect = [&](const Gfx::IntRect& rect) {
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) {
auto& screen_data = m_screen_data[m_current_cursor_screen->index()];
if (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()];
dbgln_if(COMPOSE_DEBUG, " -> flush opaque: {}", rect);
VERIFY(!flush_rects.intersects(rect));
VERIFY(!flush_transparent_rects.intersects(rect));
flush_rects.add(rect);
check_restore_cursor_back(rect);
VERIFY(!screen_data.m_flush_rects.intersects(rect));
VERIFY(!screen_data.m_flush_transparent_rects.intersects(rect));
screen_data.m_flush_rects.add(rect);
check_restore_cursor_back(screen, rect);
};
auto prepare_transparency_rect = [&](const Gfx::IntRect& rect) {
auto prepare_transparency_rect = [&](Screen& screen, const Gfx::IntRect& rect) {
auto& screen_data = m_screen_data[screen.index()];
dbgln_if(COMPOSE_DEBUG, " -> flush transparent: {}", rect);
VERIFY(!flush_rects.intersects(rect));
for (auto& r : flush_transparent_rects.rects()) {
VERIFY(!screen_data.m_flush_rects.intersects(rect));
for (auto& r : screen_data.m_flush_transparent_rects.rects()) {
if (r == rect)
return;
}
flush_transparent_rects.add(rect);
check_restore_cursor_back(rect);
screen_data.m_flush_transparent_rects.add(rect);
check_restore_cursor_back(screen, rect);
};
if (!m_cursor_back_bitmap || m_invalidated_cursor)
check_restore_cursor_back(cursor_rect);
if (!m_screen_data[cursor_screen.index()].m_cursor_back_bitmap || m_invalidated_cursor)
check_restore_cursor_back(cursor_screen, cursor_rect);
auto paint_wallpaper = [&](Gfx::Painter& painter, const Gfx::IntRect& 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 { (ws.width() - m_wallpaper->width()) / 2, (ws.height() - m_wallpaper->height()) / 2 };
painter.blit_offset(rect.location(), *m_wallpaper, rect, offset);
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)ws.width();
float vscale = (float)m_wallpaper->height() / (float)ws.height();
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 src_rect = Gfx::FloatRect { rect.x() * hscale, rect.y() * vscale, rect.width() * hscale, rect.height() * vscale };
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();
@ -243,29 +293,39 @@ void Compositor::compose()
};
m_opaque_wallpaper_rects.for_each_intersected(dirty_screen_rects, [&](const Gfx::IntRect& render_rect) {
dbgln_if(COMPOSE_DEBUG, " render wallpaper opaque: {}", render_rect);
prepare_rect(render_rect);
paint_wallpaper(back_painter, 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()) {
auto& back_painter = *m_screen_data[screen.index()].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);
}
return IterationDecision::Continue;
});
return IterationDecision::Continue;
});
auto compose_window = [&](Window& window) -> IterationDecision {
auto frame_rect = window.frame().render_rect();
if (!frame_rect.intersects(ws.rect()))
if (window.screens().is_empty()) {
// This window doesn't intersect with any screens, so there's nothing to render
return IterationDecision::Continue;
}
auto frame_rect = window.frame().render_rect();
auto window_rect = window.rect();
auto frame_rects = frame_rect.shatter(window_rect);
dbgln_if(COMPOSE_DEBUG, " window {} frame rect: {}", window.title(), frame_rect);
RefPtr<Gfx::Bitmap> backing_store = window.backing_store();
auto compose_window_rect = [&](Gfx::Painter& painter, const Gfx::IntRect& rect) {
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);
dbgln_if(COMPOSE_DEBUG, " render frame: {}", intersected_rect);
window.frame().paint(painter, intersected_rect);
window.frame().paint(screen, painter, intersected_rect);
return IterationDecision::Continue;
});
}
@ -359,12 +419,18 @@ void Compositor::compose()
auto& opaque_rects = window.opaque_rects();
if (!opaque_rects.is_empty()) {
opaque_rects.for_each_intersected(dirty_rects, [&](const Gfx::IntRect& render_rect) {
dbgln_if(COMPOSE_DEBUG, " render opaque: {}", 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(render_rect);
Gfx::PainterStateSaver saver(back_painter);
back_painter.add_clip_rect(render_rect);
compose_window_rect(back_painter, render_rect);
prepare_rect(*screen, screen_render_rect);
auto& back_painter = *m_screen_data[screen->index()].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;
});
}
@ -374,22 +440,36 @@ void Compositor::compose()
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) {
dbgln_if(COMPOSE_DEBUG, " render wallpaper: {}", 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());
prepare_transparency_rect(render_rect);
paint_wallpaper(temp_painter, render_rect);
auto& temp_painter = *m_screen_data[screen->index()].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) {
dbgln_if(COMPOSE_DEBUG, " render transparent: {}", 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(render_rect);
Gfx::PainterStateSaver saver(temp_painter);
temp_painter.add_clip_rect(render_rect);
compose_window_rect(temp_painter, render_rect);
prepare_transparency_rect(*screen, screen_render_rect);
auto& temp_painter = *m_screen_data[screen->index()].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;
});
}
@ -411,24 +491,35 @@ void Compositor::compose()
// Check that there are no overlapping transparent and opaque flush rectangles
VERIFY(![&]() {
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));
return true;
bool is_overlapping = false;
Screen::for_each([&](auto& screen) {
auto& screen_data = m_screen_data[screen.index()];
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 false;
return IterationDecision::Continue;
});
return is_overlapping;
}());
// Copy anything rendered to the temporary buffer to the back buffer
for (auto& rect : flush_transparent_rects.rects())
back_painter.blit(rect.location(), *m_temp_bitmap, rect);
Screen::for_each([&](auto& screen) {
auto screen_rect = screen.rect();
auto& screen_data = m_screen_data[screen.index()];
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;
});
Gfx::IntRect geometry_label_damage_rect;
if (draw_geometry_label(geometry_label_damage_rect))
flush_special_rects.add(geometry_label_damage_rect);
draw_geometry_label(cursor_screen);
}
m_invalidated_any = false;
@ -438,34 +529,50 @@ void Compositor::compose()
if (wm.dnd_client()) {
auto dnd_rect = wm.dnd_rect();
// TODO: render once into a backing bitmap, then just blit...
auto render_dnd = [&]() {
back_painter.fill_rect(dnd_rect, wm.palette().selection().with_alpha(200));
back_painter.draw_rect(dnd_rect, wm.palette().selection());
if (!wm.dnd_text().is_empty()) {
auto text_rect = dnd_rect;
if (wm.dnd_bitmap())
text_rect.translate_by(wm.dnd_bitmap()->width() + 8, 0);
back_painter.draw_text(text_rect, wm.dnd_text(), Gfx::TextAlignment::CenterLeft, wm.palette().selection_text());
}
if (wm.dnd_bitmap()) {
back_painter.blit(dnd_rect.top_left().translated(4, 4), *wm.dnd_bitmap(), wm.dnd_bitmap()->rect());
}
};
Screen::for_each([&](auto& screen) {
auto screen_rect = screen.rect();
auto render_dnd_rect = screen_rect.intersected(dnd_rect);
if (render_dnd_rect.is_empty())
return IterationDecision::Continue;
auto& screen_data = m_screen_data[screen.index()];
auto& back_painter = *screen_data.m_back_painter;
dirty_screen_rects.for_each_intersected(dnd_rect, [&](const Gfx::IntRect& render_rect) {
Gfx::PainterStateSaver saver(back_painter);
back_painter.add_clip_rect(render_rect);
render_dnd();
// TODO: render once into a backing bitmap, then just blit...
auto render_dnd = [&]() {
back_painter.fill_rect(dnd_rect, wm.palette().selection().with_alpha(200));
back_painter.draw_rect(dnd_rect, wm.palette().selection());
if (!wm.dnd_text().is_empty()) {
auto text_rect = dnd_rect;
if (wm.dnd_bitmap())
text_rect.translate_by(wm.dnd_bitmap()->width() + 8, 0);
back_painter.draw_text(text_rect, wm.dnd_text(), Gfx::TextAlignment::CenterLeft, wm.palette().selection_text());
}
if (wm.dnd_bitmap()) {
back_painter.blit(dnd_rect.top_left().translated(4, 4), *wm.dnd_bitmap(), wm.dnd_bitmap()->rect());
}
};
dirty_screen_rects.for_each_intersected(dnd_rect, [&](const Gfx::IntRect& render_rect) {
auto screen_render_rect = render_rect.intersected(screen_rect);
if (screen_render_rect.is_empty())
return IterationDecision::Continue;
Gfx::PainterStateSaver saver(back_painter);
back_painter.add_clip_rect(screen_render_rect);
render_dnd();
return IterationDecision::Continue;
});
screen_data.m_flush_transparent_rects.for_each_intersected(dnd_rect, [&](const Gfx::IntRect& render_rect) {
auto screen_render_rect = render_rect.intersected(screen_rect);
if (screen_render_rect.is_empty())
return IterationDecision::Continue;
Gfx::PainterStateSaver saver(back_painter);
back_painter.add_clip_rect(screen_render_rect);
render_dnd();
return IterationDecision::Continue;
});
m_last_dnd_rect = dnd_rect;
return IterationDecision::Continue;
});
flush_transparent_rects.for_each_intersected(dnd_rect, [&](const Gfx::IntRect& render_rect) {
Gfx::PainterStateSaver saver(back_painter);
back_painter.add_clip_rect(render_rect);
render_dnd();
return IterationDecision::Continue;
});
m_last_dnd_rect = dnd_rect;
} else {
if (!m_last_dnd_rect.is_empty()) {
invalidate_screen(m_last_dnd_rect);
@ -473,78 +580,98 @@ void Compositor::compose()
}
}
run_animations(flush_special_rects);
bool did_render_animation = false;
Screen::for_each([&](auto& screen) {
auto& screen_data = m_screen_data[screen.index()];
did_render_animation |= render_animation_frame(screen, screen_data.m_flush_special_rects);
return IterationDecision::Continue;
});
if (need_to_draw_cursor) {
flush_rects.add(cursor_rect);
if (cursor_rect != m_last_cursor_rect)
flush_rects.add(m_last_cursor_rect);
draw_cursor(cursor_rect);
auto& screen_data = m_screen_data[cursor_screen.index()];
screen_data.draw_cursor(cursor_screen, cursor_rect);
screen_data.m_flush_rects.add(cursor_rect);
if (previous_cursor_screen && cursor_rect != previous_cursor_rect)
m_screen_data[previous_cursor_screen->index()].m_flush_rects.add(previous_cursor_rect);
}
if (m_flash_flush) {
for (auto& rect : flush_rects.rects())
m_front_painter->fill_rect(rect, Color::Yellow);
}
Screen::for_each([&](auto& screen) {
flush(screen);
return IterationDecision::Continue;
});
if (m_screen_can_set_buffer)
flip_buffers();
for (auto& rect : flush_rects.rects())
flush(rect);
for (auto& rect : flush_transparent_rects.rects())
flush(rect);
for (auto& rect : flush_special_rects.rects())
flush(rect);
if (did_render_animation)
step_animations();
}
void Compositor::flush(const Gfx::IntRect& a_rect)
void Compositor::flush(Screen& screen)
{
auto rect = Gfx::IntRect::intersection(a_rect, Screen::the().rect());
// 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.
rect = rect * Screen::the().scale_factor();
Gfx::RGBA32* front_ptr = m_front_bitmap->scanline(rect.y()) + rect.x();
Gfx::RGBA32* back_ptr = m_back_bitmap->scanline(rect.y()) + rect.x();
size_t pitch = 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 (m_screen_can_set_buffer) {
to_ptr = back_ptr;
from_ptr = front_ptr;
} else {
to_ptr = front_ptr;
from_ptr = back_ptr;
auto& screen_data = m_screen_data[screen.index()];
if (m_flash_flush) {
for (auto& rect : screen_data.m_flush_rects.rects())
screen_data.m_front_painter->fill_rect(rect, Color::Yellow);
}
for (int y = 0; y < rect.height(); ++y) {
fast_u32_copy(to_ptr, from_ptr, rect.width());
from_ptr = (const Gfx::RGBA32*)((const u8*)from_ptr + pitch);
to_ptr = (Gfx::RGBA32*)((u8*)to_ptr + pitch);
}
if (screen_data.m_screen_can_set_buffer)
screen_data.flip_buffers(screen);
auto screen_rect = screen.rect();
auto do_flush = [&](const Gfx::IntRect& a_rect) {
auto rect = Gfx::IntRect::intersection(a_rect, screen_rect);
if (rect.is_empty())
return;
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.
rect = rect * screen.scale_factor();
Gfx::RGBA32* front_ptr = screen_data.m_front_bitmap->scanline(rect.y()) + rect.x();
Gfx::RGBA32* back_ptr = screen_data.m_back_bitmap->scanline(rect.y()) + 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 < rect.height(); ++y) {
fast_u32_copy(to_ptr, from_ptr, rect.width());
from_ptr = (const Gfx::RGBA32*)((const u8*)from_ptr + pitch);
to_ptr = (Gfx::RGBA32*)((u8*)to_ptr + pitch);
}
};
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);
}
void Compositor::invalidate_screen()
{
invalidate_screen(Screen::the().rect());
invalidate_screen(Screen::bounding_rect());
}
void Compositor::invalidate_screen(const Gfx::IntRect& screen_rect)
{
m_dirty_screen_rects.add(screen_rect.intersected(Screen::the().rect()));
m_dirty_screen_rects.add(screen_rect.intersected(Screen::bounding_rect()));
if (m_invalidated_any)
return;
@ -623,19 +750,21 @@ bool Compositor::set_wallpaper(const String& path, Function<void(bool)>&& callba
return true;
}
void Compositor::flip_buffers()
void Compositor::ScreenData::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::the().set_buffer(m_buffers_are_flipped ? 0 : 1);
screen.set_buffer(m_buffers_are_flipped ? 0 : 1);
m_buffers_are_flipped = !m_buffers_are_flipped;
}
void Compositor::run_animations(Gfx::DisjointRectSet& flush_rects)
static const int minimize_animation_steps = 10;
bool Compositor::render_animation_frame(Screen& screen, Gfx::DisjointRectSet& flush_rects)
{
static const int minimize_animation_steps = 10;
auto& painter = *m_back_painter;
bool did_render_any = false;
auto& painter = *m_screen_data[screen.index()].m_back_painter;
Gfx::PainterStateSaver saver(painter);
painter.set_draw_op(Gfx::Painter::DrawOp::Invert);
@ -658,12 +787,24 @@ void Compositor::run_animations(Gfx::DisjointRectSet& flush_rects)
from_rect.height() - (int)(height_delta_per_step * animation_index)
};
dbgln_if(MINIMIZE_ANIMATION_DEBUG, "Minimize animation from {} to {} frame# {} {}", from_rect, to_rect, animation_index, rect);
dbgln_if(MINIMIZE_ANIMATION_DEBUG, "Minimize animation from {} to {} frame# {} {} on screen #{}", from_rect, to_rect, animation_index, rect, screen.index());
painter.draw_rect(rect, Color::Transparent); // Color doesn't matter, we draw inverted
flush_rects.add(rect);
invalidate_screen(rect);
did_render_any = true;
}
return IterationDecision::Continue;
});
return did_render_any;
}
void Compositor::step_animations()
{
WindowManager::the().window_stack().for_each_window([&](Window& window) {
if (window.in_minimize_animation()) {
window.step_minimize_animation();
if (window.minimize_animation_index() >= minimize_animation_steps)
window.end_minimize_animation();
@ -672,33 +813,18 @@ void Compositor::run_animations(Gfx::DisjointRectSet& flush_rects)
});
}
bool Compositor::set_resolution(int desired_width, int desired_height, int scale_factor)
void Compositor::screen_resolution_changed()
{
auto screen_rect = Screen::the().rect();
if (screen_rect.width() == desired_width && screen_rect.height() == desired_height && Screen::the().scale_factor() == scale_factor)
return true;
// Make sure it's impossible to set an invalid resolution
if (!(desired_width >= 640 && desired_height >= 480 && scale_factor >= 1)) {
dbgln("Compositor: Tried to set invalid resolution: {}x{}", desired_width, desired_height);
return false;
}
int old_scale_factor = Screen::the().scale_factor();
bool success = Screen::the().set_resolution(desired_width, desired_height, scale_factor);
if (success && old_scale_factor != scale_factor)
WindowManager::the().reload_icon_bitmaps_after_scale_change();
init_bitmaps();
invalidate_occlusions();
compose();
return success;
}
Gfx::IntRect Compositor::current_cursor_rect() const
{
auto& wm = WindowManager::the();
auto& current_cursor = m_current_cursor ? *m_current_cursor : wm.active_cursor();
return { Screen::the().cursor_location().translated(-current_cursor.params().hotspot()), current_cursor.size() };
return { ScreenInput::the().cursor_location().translated(-current_cursor.params().hotspot()), current_cursor.size() };
}
void Compositor::invalidate_cursor(bool compose_immediately)
@ -714,13 +840,13 @@ void Compositor::invalidate_cursor(bool compose_immediately)
start_compose_async_timer();
}
bool Compositor::draw_geometry_label(Gfx::IntRect& geometry_label_damage_rect)
void Compositor::draw_geometry_label(Screen& screen)
{
auto& wm = WindowManager::the();
auto* window_being_moved_or_resized = wm.m_move_window ? wm.m_move_window.ptr() : (wm.m_resize_window ? wm.m_resize_window.ptr() : nullptr);
if (!window_being_moved_or_resized) {
m_last_geometry_label_damage_rect = {};
return false;
return;
}
auto geometry_string = window_being_moved_or_resized->rect().to_string();
if (!window_being_moved_or_resized->size_increment().is_null()) {
@ -731,7 +857,7 @@ bool Compositor::draw_geometry_label(Gfx::IntRect& geometry_label_damage_rect)
auto geometry_label_rect = Gfx::IntRect { 0, 0, wm.font().width(geometry_string) + 16, wm.font().glyph_height() + 10 };
geometry_label_rect.center_within(window_being_moved_or_resized->rect());
auto desktop_rect = wm.desktop_rect();
auto desktop_rect = wm.desktop_rect(screen);
if (geometry_label_rect.left() < desktop_rect.left())
geometry_label_rect.set_left(desktop_rect.left());
if (geometry_label_rect.top() < desktop_rect.top())
@ -741,14 +867,17 @@ bool Compositor::draw_geometry_label(Gfx::IntRect& geometry_label_damage_rect)
if (geometry_label_rect.bottom() > desktop_rect.bottom())
geometry_label_rect.set_bottom_without_resize(desktop_rect.bottom());
auto& back_painter = *m_back_painter;
auto& screen_data = m_screen_data[screen.index()];
auto& back_painter = *screen_data.m_back_painter;
auto geometry_label_damage_rect = geometry_label_rect.inflated(2, 2);
Gfx::PainterStateSaver saver(back_painter);
back_painter.add_clip_rect(geometry_label_damage_rect);
back_painter.fill_rect(geometry_label_rect.translated(1, 1), Color(Color::Black).with_alpha(80));
Gfx::StylePainter::paint_button(back_painter, geometry_label_rect.translated(-1, -1), wm.palette(), Gfx::ButtonStyle::Normal, false);
back_painter.draw_text(geometry_label_rect.translated(-1, -1), geometry_string, Gfx::TextAlignment::Center, wm.palette().window_text());
geometry_label_damage_rect = geometry_label_rect.inflated(2, 2);
m_last_geometry_label_damage_rect = geometry_label_damage_rect;
return true;
}
void Compositor::change_cursor(const Cursor* cursor)
@ -774,28 +903,34 @@ void Compositor::change_cursor(const Cursor* cursor)
}
}
void Compositor::draw_cursor(const Gfx::IntRect& cursor_rect)
void Compositor::ScreenData::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::the().scale_factor()) {
m_cursor_back_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, cursor_rect.size(), Screen::the().scale_factor());
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::create(Gfx::BitmapFormat::BGRx8888, cursor_rect.size(), screen.scale_factor());
m_cursor_back_painter = make<Gfx::Painter>(*m_cursor_back_bitmap);
}
auto& current_cursor = m_current_cursor ? *m_current_cursor : wm.active_cursor();
m_cursor_back_painter->blit({ 0, 0 }, *m_back_bitmap, current_cursor.rect().translated(cursor_rect.location()).intersected(Screen::the().rect()));
m_back_painter->blit(cursor_rect.location(), current_cursor.bitmap(), current_cursor.source_rect(m_current_cursor_frame));
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()));
m_back_painter->blit(cursor_rect.location(), current_cursor.bitmap(), current_cursor.source_rect(compositor.m_current_cursor_frame));
m_last_cursor_rect = cursor_rect;
VERIFY(compositor.m_current_cursor_screen == &screen);
m_cursor_back_is_valid = true;
}
void Compositor::restore_cursor_back()
bool Compositor::ScreenData::restore_cursor_back(Screen& screen, Gfx::IntRect& last_cursor_rect)
{
if (!m_cursor_back_bitmap || m_cursor_back_bitmap->scale() != m_back_bitmap->scale())
return;
if (!m_cursor_back_is_valid || !m_cursor_back_bitmap || m_cursor_back_bitmap->scale() != m_back_bitmap->scale())
return false;
auto last_cursor_rect = m_last_cursor_rect.intersected(Screen::the().rect());
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_cursor_back_is_valid = false;
return true;
}
void Compositor::notify_display_links()
@ -863,14 +998,17 @@ void Compositor::recompute_occlusions()
dbgln_if(OCCLUSIONS_DEBUG, "OCCLUSIONS:");
auto screen_rect = Screen::the().rect();
auto& main_screen = Screen::main();
if (auto* fullscreen_window = wm.active_fullscreen_window()) {
// TODO: support fullscreen windows on all screens
auto screen_rect = main_screen.rect();
WindowManager::the().window_stack().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.screens().clear_with_capacity();
if (&w == fullscreen_window) {
w.screens().append(&main_screen);
if (w.is_opaque()) {
visible_opaque = screen_rect;
transparency_rects.clear();
@ -890,28 +1028,39 @@ void Compositor::recompute_occlusions()
m_opaque_wallpaper_rects.clear();
} else {
Gfx::DisjointRectSet visible_rects(screen_rect);
Gfx::DisjointRectSet visible_rects;
visible_rects.add_many(Screen::rects());
bool have_transparent = false;
WindowManager::the().window_stack().for_each_visible_window_from_front_to_back([&](Window& w) {
w.transparency_wallpaper_rects().clear();
auto& visible_opaque = w.opaque_rects();
visible_opaque.clear();
auto& transparency_rects = w.transparency_rects();
if (w.is_minimized()) {
visible_opaque.clear();
transparency_rects.clear();
transparency_rects.clear();
w.screens().clear_with_capacity();
if (w.is_minimized())
return IterationDecision::Continue;
}
auto transparent_render_rects = w.frame().transparent_render_rects().intersected(screen_rect);
auto opaque_render_rects = w.frame().opaque_render_rects().intersected(screen_rect);
if (transparent_render_rects.is_empty() && opaque_render_rects.is_empty()) {
visible_opaque.clear();
transparency_rects.clear();
auto transparent_frame_render_rects = w.frame().transparent_render_rects();
auto opaque_frame_render_rects = w.frame().opaque_render_rects();
Gfx::DisjointRectSet visible_opaque_rects;
Screen::for_each([&](auto& screen) {
auto screen_rect = screen.rect();
if (auto transparent_render_rects = transparent_frame_render_rects.intersected(screen_rect); !transparent_render_rects.is_empty()) {
if (transparency_rects.is_empty())
transparency_rects = move(transparent_render_rects);
else
transparency_rects.add(transparent_render_rects);
}
if (auto opaque_render_rects = opaque_frame_render_rects.intersected(screen_rect); !opaque_render_rects.is_empty()) {
if (visible_opaque_rects.is_empty())
visible_opaque_rects = move(opaque_render_rects);
else
visible_opaque_rects.add(opaque_render_rects);
}
return IterationDecision::Continue;
}
visible_opaque = visible_rects.intersected(opaque_render_rects);
transparency_rects = move(transparent_render_rects);
});
visible_opaque = visible_rects.intersected(visible_opaque_rects);
auto render_rect = w.frame().render_rect();
@ -967,8 +1116,31 @@ void Compositor::recompute_occlusions()
return IterationDecision::Continue;
});
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;
});
}
VERIFY(!visible_opaque.intersects(transparency_rects));
@ -1008,12 +1180,14 @@ void Compositor::recompute_occlusions()
}
wm.window_stack().for_each_visible_window_from_back_to_front([&](Window& w) {
auto window_frame_rect = w.frame().render_rect().intersected(screen_rect);
if (w.is_minimized() || window_frame_rect.is_empty())
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: {}", w.title(), window_frame_rect);
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())