|
@@ -32,11 +32,15 @@
|
|
|
#include "Window.h"
|
|
|
#include "WindowManager.h"
|
|
|
#include <AK/Memory.h>
|
|
|
+#include <AK/ScopeGuard.h>
|
|
|
#include <LibCore/Timer.h>
|
|
|
#include <LibGfx/Font.h>
|
|
|
#include <LibGfx/Painter.h>
|
|
|
#include <LibThread/BackgroundAction.h>
|
|
|
|
|
|
+//#define COMPOSE_DEBUG
|
|
|
+//#define OCCLUSIONS_DEBUG
|
|
|
+
|
|
|
namespace WindowServer {
|
|
|
|
|
|
Compositor& Compositor::the()
|
|
@@ -96,12 +100,15 @@ void Compositor::init_bitmaps()
|
|
|
else
|
|
|
m_back_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::RGB32, size);
|
|
|
|
|
|
+ m_temp_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::RGB32, size);
|
|
|
+
|
|
|
m_front_painter = make<Gfx::Painter>(*m_front_bitmap);
|
|
|
m_back_painter = make<Gfx::Painter>(*m_back_bitmap);
|
|
|
+ m_temp_painter = make<Gfx::Painter>(*m_temp_bitmap);
|
|
|
|
|
|
m_buffers_are_flipped = false;
|
|
|
|
|
|
- invalidate();
|
|
|
+ invalidate_screen();
|
|
|
}
|
|
|
|
|
|
void Compositor::compose()
|
|
@@ -111,26 +118,64 @@ void Compositor::compose()
|
|
|
m_wallpaper_mode = mode_to_enum(wm.config()->read_entry("Background", "Mode", "simple"));
|
|
|
auto& ws = Screen::the();
|
|
|
|
|
|
- auto dirty_rects = move(m_dirty_rects);
|
|
|
-
|
|
|
- if (dirty_rects.size() == 0) {
|
|
|
+ if (!m_invalidated_any) {
|
|
|
// nothing dirtied since the last compose pass.
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- dirty_rects.add(Gfx::IntRect::intersection(m_last_geometry_label_rect, Screen::the().rect()));
|
|
|
- dirty_rects.add(Gfx::IntRect::intersection(m_last_cursor_rect, Screen::the().rect()));
|
|
|
- dirty_rects.add(Gfx::IntRect::intersection(m_last_dnd_rect, Screen::the().rect()));
|
|
|
- dirty_rects.add(Gfx::IntRect::intersection(current_cursor_rect(), Screen::the().rect()));
|
|
|
+ if (m_occlusions_dirty) {
|
|
|
+ m_occlusions_dirty = false;
|
|
|
+ recompute_occlusions();
|
|
|
+ }
|
|
|
+
|
|
|
+ auto dirty_screen_rects = move(m_dirty_screen_rects);
|
|
|
+ dirty_screen_rects.add(m_last_geometry_label_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 any_dirty_rect_intersects_window = [&dirty_rects](const Window& window) {
|
|
|
- auto window_frame_rect = window.frame().rect();
|
|
|
- for (auto& dirty_rect : dirty_rects.rects()) {
|
|
|
- if (dirty_rect.intersects(window_frame_rect))
|
|
|
- return true;
|
|
|
+ // Mark window regions as dirty that need to be re-rendered
|
|
|
+ wm.for_each_visible_window_from_back_to_front([&](Window& window) {
|
|
|
+ auto frame_rect = window.frame().rect();
|
|
|
+ for (auto& dirty_rect : dirty_screen_rects.rects()) {
|
|
|
+ auto invalidate_rect = dirty_rect.intersected(frame_rect);
|
|
|
+ if (!invalidate_rect.is_empty()) {
|
|
|
+ auto inner_rect_offset = window.rect().location() - frame_rect.location();
|
|
|
+ invalidate_rect.move_by(-(frame_rect.location() + inner_rect_offset));
|
|
|
+ window.invalidate_no_notify(invalidate_rect);
|
|
|
+ m_invalidated_window = true;
|
|
|
+ }
|
|
|
}
|
|
|
- return false;
|
|
|
- };
|
|
|
+ window.prepare_dirty_rects();
|
|
|
+ return IterationDecision::Continue;
|
|
|
+ });
|
|
|
+
|
|
|
+ // Any windows above or below a given window that need to be re-rendered
|
|
|
+ // also require us to re-render that window's intersecting area, regardless
|
|
|
+ // of whether that window has any dirty rectangles
|
|
|
+ wm.for_each_visible_window_from_back_to_front([&](Window& window) {
|
|
|
+ auto& transparency_rects = window.transparency_rects();
|
|
|
+ if (transparency_rects.is_empty())
|
|
|
+ return IterationDecision::Continue;
|
|
|
+
|
|
|
+ auto frame_rect = window.frame().rect();
|
|
|
+ auto& dirty_rects = window.dirty_rects();
|
|
|
+ wm.for_each_visible_window_from_back_to_front([&](Window& w) {
|
|
|
+ if (&w == &window)
|
|
|
+ return IterationDecision::Continue;
|
|
|
+ auto frame_rect2 = w.frame().rect();
|
|
|
+ if (!frame_rect2.intersects(frame_rect))
|
|
|
+ return IterationDecision::Continue;
|
|
|
+ transparency_rects.for_each_intersected(w.dirty_rects(), [&](const Gfx::IntRect& intersected_dirty) {
|
|
|
+ dirty_rects.add(intersected_dirty);
|
|
|
+ return IterationDecision::Continue;
|
|
|
+ });
|
|
|
+ return IterationDecision::Continue;
|
|
|
+ });
|
|
|
+ return IterationDecision::Continue;
|
|
|
+ });
|
|
|
|
|
|
Color background_color = wm.palette().desktop_background();
|
|
|
String background_color_entry = wm.config()->read_entry("Background", "Color", "");
|
|
@@ -138,50 +183,127 @@ void Compositor::compose()
|
|
|
background_color = Color::from_string(background_color_entry).value_or(background_color);
|
|
|
}
|
|
|
|
|
|
- // Paint the wallpaper.
|
|
|
- for (auto& dirty_rect : dirty_rects.rects()) {
|
|
|
- if (any_opaque_window_contains_rect(dirty_rect))
|
|
|
- continue;
|
|
|
+#ifdef COMPOSE_DEBUG
|
|
|
+ dbg() << "COMPOSE: invalidated: window:" << m_invalidated_window << " cursor:" << m_invalidated_cursor << " any: " << m_invalidated_any;
|
|
|
+ for (auto& r : dirty_screen_rects.rects())
|
|
|
+ dbg() << "dirty screen: " << r;
|
|
|
+#endif
|
|
|
+ Gfx::DisjointRectSet flush_rects;
|
|
|
+ Gfx::DisjointRectSet flush_transparent_rects;
|
|
|
+ Gfx::DisjointRectSet flush_special_rects;
|
|
|
+ auto cursor_rect = current_cursor_rect();
|
|
|
+ bool need_to_draw_cursor = false;
|
|
|
+
|
|
|
+ auto check_restore_cursor_back = [&](const Gfx::IntRect& rect) {
|
|
|
+ if (!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 prepare_rect = [&](const Gfx::IntRect& rect) {
|
|
|
+#ifdef COMPOSE_DEBUG
|
|
|
+ dbg() << " -> flush opaque: " << rect;
|
|
|
+#endif
|
|
|
+ ASSERT(!flush_rects.intersects(rect));
|
|
|
+ ASSERT(!flush_transparent_rects.intersects(rect));
|
|
|
+ flush_rects.add(rect);
|
|
|
+ check_restore_cursor_back(rect);
|
|
|
+ };
|
|
|
+
|
|
|
+ auto prepare_transparency_rect = [&](const Gfx::IntRect& rect) {
|
|
|
+ // This function may be called multiple times with the same
|
|
|
+ // rect as we walk the window stack from back to front. However,
|
|
|
+ // there should be no overlaps with flush_rects
|
|
|
+#ifdef COMPOSE_DEBUG
|
|
|
+ dbg() << " -> flush transparent: " << rect;
|
|
|
+#endif
|
|
|
+ ASSERT(!flush_rects.intersects(rect));
|
|
|
+ bool have_rect = false;
|
|
|
+ for (auto& r : flush_transparent_rects.rects()) {
|
|
|
+ if (r == rect) {
|
|
|
+ have_rect = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!have_rect) {
|
|
|
+ flush_transparent_rects.add(rect);
|
|
|
+ check_restore_cursor_back(rect);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ if (!m_cursor_back_bitmap || m_invalidated_cursor)
|
|
|
+ check_restore_cursor_back(cursor_rect);
|
|
|
+
|
|
|
+ auto back_painter = *m_back_painter;
|
|
|
+ auto temp_painter = *m_temp_painter;
|
|
|
+
|
|
|
+ auto paint_wallpaper = [&](Gfx::Painter& painter, const Gfx::IntRect& rect) {
|
|
|
// FIXME: If the wallpaper is opaque, no need to fill with color!
|
|
|
- m_back_painter->fill_rect(dirty_rect, background_color);
|
|
|
+ painter.fill_rect(rect, background_color);
|
|
|
if (m_wallpaper) {
|
|
|
if (m_wallpaper_mode == WallpaperMode::Simple) {
|
|
|
- m_back_painter->blit(dirty_rect.location(), *m_wallpaper, dirty_rect);
|
|
|
+ painter.blit(rect.location(), *m_wallpaper, rect);
|
|
|
} else if (m_wallpaper_mode == WallpaperMode::Center) {
|
|
|
Gfx::IntPoint offset { ws.size().width() / 2 - m_wallpaper->size().width() / 2,
|
|
|
ws.size().height() / 2 - m_wallpaper->size().height() / 2 };
|
|
|
- m_back_painter->blit_offset(dirty_rect.location(), *m_wallpaper,
|
|
|
- dirty_rect, offset);
|
|
|
+ painter.blit_offset(rect.location(), *m_wallpaper,
|
|
|
+ rect, offset);
|
|
|
} else if (m_wallpaper_mode == WallpaperMode::Tile) {
|
|
|
- m_back_painter->draw_tiled_bitmap(dirty_rect, *m_wallpaper);
|
|
|
+ painter.draw_tiled_bitmap(rect, *m_wallpaper);
|
|
|
} else if (m_wallpaper_mode == WallpaperMode::Scaled) {
|
|
|
float hscale = (float)m_wallpaper->size().width() / (float)ws.size().width();
|
|
|
float vscale = (float)m_wallpaper->size().height() / (float)ws.size().height();
|
|
|
|
|
|
- m_back_painter->blit_scaled(dirty_rect, *m_wallpaper, dirty_rect, hscale, vscale);
|
|
|
+ // TODO: this may look ugly, we should scale to a backing bitmap and then blit
|
|
|
+ painter.blit_scaled(rect, *m_wallpaper, rect, hscale, vscale);
|
|
|
} else {
|
|
|
ASSERT_NOT_REACHED();
|
|
|
}
|
|
|
}
|
|
|
- }
|
|
|
+ };
|
|
|
+
|
|
|
+ m_opaque_wallpaper_rects.for_each_intersected(dirty_screen_rects, [&](const Gfx::IntRect& render_rect) {
|
|
|
+#ifdef COMPOSE_DEBUG
|
|
|
+ dbg() << " render wallpaper opaque: " << render_rect;
|
|
|
+#endif
|
|
|
+ prepare_rect(render_rect);
|
|
|
+ paint_wallpaper(back_painter, render_rect);
|
|
|
+ return IterationDecision::Continue;
|
|
|
+ });
|
|
|
|
|
|
auto compose_window = [&](Window& window) -> IterationDecision {
|
|
|
- if (!any_dirty_rect_intersects_window(window))
|
|
|
+ auto frame_rect = window.frame().rect();
|
|
|
+ if (!frame_rect.intersects(ws.rect()))
|
|
|
return IterationDecision::Continue;
|
|
|
- Gfx::PainterStateSaver saver(*m_back_painter);
|
|
|
- m_back_painter->add_clip_rect(window.frame().rect());
|
|
|
+ auto frame_rects = frame_rect.shatter(window.rect());
|
|
|
+
|
|
|
+#ifdef COMPOSE_DEBUG
|
|
|
+ dbg() << " window " << window.title() << " frame rect: " << frame_rect;
|
|
|
+#endif
|
|
|
+
|
|
|
RefPtr<Gfx::Bitmap> backing_store = window.backing_store();
|
|
|
- for (auto& dirty_rect : dirty_rects.rects()) {
|
|
|
- if (!window.is_fullscreen() && any_opaque_window_above_this_one_contains_rect(window, dirty_rect))
|
|
|
- continue;
|
|
|
- Gfx::PainterStateSaver saver(*m_back_painter);
|
|
|
- m_back_painter->add_clip_rect(dirty_rect);
|
|
|
- if (!backing_store)
|
|
|
- m_back_painter->fill_rect(dirty_rect, wm.palette().window());
|
|
|
- if (!window.is_fullscreen())
|
|
|
- window.frame().paint(*m_back_painter);
|
|
|
- if (!backing_store)
|
|
|
- continue;
|
|
|
+ auto compose_window_rect = [&](Gfx::Painter& painter, const Gfx::IntRect& rect) {
|
|
|
+ if (!window.is_fullscreen()) {
|
|
|
+ rect.for_each_intersected(frame_rects, [&](const Gfx::IntRect& intersected_rect) {
|
|
|
+ // TODO: Should optimize this to use a backing buffer
|
|
|
+ Gfx::PainterStateSaver saver(painter);
|
|
|
+ painter.add_clip_rect(intersected_rect);
|
|
|
+#ifdef COMPOSE_DEBUG
|
|
|
+ dbg() << " render frame: " << intersected_rect;
|
|
|
+#endif
|
|
|
+ window.frame().paint(painter);
|
|
|
+ return IterationDecision::Continue;
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!backing_store) {
|
|
|
+ if (window.is_opaque())
|
|
|
+ painter.fill_rect(window.rect().intersected(rect), wm.palette().window());
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
// Decide where we would paint this window's backing store.
|
|
|
// This is subtly different from widow.rect(), because window
|
|
@@ -216,54 +338,178 @@ void Compositor::compose()
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
- Gfx::IntRect dirty_rect_in_backing_coordinates = dirty_rect
|
|
|
- .intersected(window.rect())
|
|
|
+ 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())
|
|
|
- continue;
|
|
|
+ return;
|
|
|
auto dst = backing_rect.location().translated(dirty_rect_in_backing_coordinates.location());
|
|
|
|
|
|
if (window.client() && window.client()->is_unresponsive()) {
|
|
|
- m_back_painter->blit_filtered(dst, *backing_store, dirty_rect_in_backing_coordinates, [](Color src) {
|
|
|
+ painter.blit_filtered(dst, *backing_store, dirty_rect_in_backing_coordinates, [](Color src) {
|
|
|
return src.to_grayscale().darkened(0.75f);
|
|
|
});
|
|
|
} else {
|
|
|
- m_back_painter->blit(dst, *backing_store, dirty_rect_in_backing_coordinates, window.opacity());
|
|
|
+ painter.blit(dst, *backing_store, dirty_rect_in_backing_coordinates, window.opacity());
|
|
|
}
|
|
|
|
|
|
for (auto background_rect : window.rect().shatter(backing_rect))
|
|
|
- m_back_painter->fill_rect(background_rect, wm.palette().window());
|
|
|
+ painter.fill_rect(background_rect, wm.palette().window());
|
|
|
+ };
|
|
|
+
|
|
|
+ auto& dirty_rects = window.dirty_rects();
|
|
|
+#ifdef COMPOSE_DEBUG
|
|
|
+ for (auto& dirty_rect : dirty_rects.rects())
|
|
|
+ dbg() << " dirty: " << dirty_rect;
|
|
|
+ for (auto& r : window.opaque_rects().rects())
|
|
|
+ dbg() << " opaque: " << r;
|
|
|
+ for (auto& r : window.transparency_rects().rects())
|
|
|
+ dbg() << " transparent: " << r;
|
|
|
+#endif
|
|
|
+
|
|
|
+ // 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) {
|
|
|
+#ifdef COMPOSE_DEBUG
|
|
|
+ dbg() << " render opaque: " << render_rect;
|
|
|
+#endif
|
|
|
+ prepare_rect(render_rect);
|
|
|
+ Gfx::PainterStateSaver saver(back_painter);
|
|
|
+ back_painter.add_clip_rect(render_rect);
|
|
|
+ compose_window_rect(back_painter, 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) {
|
|
|
+#ifdef COMPOSE_DEBUG
|
|
|
+ dbg() << " render wallpaper: " << render_rect;
|
|
|
+#endif
|
|
|
+ prepare_transparency_rect(render_rect);
|
|
|
+ paint_wallpaper(temp_painter, render_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) {
|
|
|
+#ifdef COMPOSE_DEBUG
|
|
|
+ dbg() << " render transparent: " << render_rect;
|
|
|
+#endif
|
|
|
+ prepare_transparency_rect(render_rect);
|
|
|
+ Gfx::PainterStateSaver saver(temp_painter);
|
|
|
+ temp_painter.add_clip_rect(render_rect);
|
|
|
+ compose_window_rect(temp_painter, render_rect);
|
|
|
+ return IterationDecision::Continue;
|
|
|
+ });
|
|
|
}
|
|
|
return IterationDecision::Continue;
|
|
|
};
|
|
|
|
|
|
// Paint the window stack.
|
|
|
- if (auto* fullscreen_window = wm.active_fullscreen_window()) {
|
|
|
- compose_window(*fullscreen_window);
|
|
|
- } else {
|
|
|
- wm.for_each_visible_window_from_back_to_front([&](Window& window) {
|
|
|
- return compose_window(window);
|
|
|
- });
|
|
|
+ if (m_invalidated_window) {
|
|
|
+ if (auto* fullscreen_window = wm.active_fullscreen_window()) {
|
|
|
+ compose_window(*fullscreen_window);
|
|
|
+ } 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
|
|
|
+ ASSERT(![&]() {
|
|
|
+ for (auto& rect_transparent : flush_transparent_rects.rects()) {
|
|
|
+ for (auto& rect_opaque : flush_rects.rects()) {
|
|
|
+ if (rect_opaque.intersects(rect_transparent)) {
|
|
|
+ dbg() << "Transparent rect " << rect_transparent << " overlaps opaque rect: " << rect_opaque << ": " << rect_opaque.intersected(rect_transparent);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }());
|
|
|
+
|
|
|
+ // 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);
|
|
|
+
|
|
|
+ Gfx::IntRect geometry_label_rect;
|
|
|
+ if (draw_geometry_label(geometry_label_rect))
|
|
|
+ flush_special_rects.add(geometry_label_rect);
|
|
|
+ }
|
|
|
+
|
|
|
+ m_invalidated_any = false;
|
|
|
+ m_invalidated_window = false;
|
|
|
+ m_invalidated_cursor = false;
|
|
|
+
|
|
|
+ if (wm.dnd_client()) {
|
|
|
+ auto dnd_rect = wm.dnd_rect();
|
|
|
|
|
|
- draw_geometry_label();
|
|
|
+ // 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));
|
|
|
+ if (!wm.dnd_text().is_empty()) {
|
|
|
+ auto text_rect = dnd_rect;
|
|
|
+ if (wm.dnd_bitmap())
|
|
|
+ text_rect.move_by(wm.dnd_bitmap()->width(), 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(), *wm.dnd_bitmap(), wm.dnd_bitmap()->rect());
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ 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();
|
|
|
+ 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);
|
|
|
+ m_last_dnd_rect = {};
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- run_animations();
|
|
|
+ run_animations(flush_special_rects);
|
|
|
|
|
|
- draw_cursor();
|
|
|
+ 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);
|
|
|
+ }
|
|
|
|
|
|
if (m_flash_flush) {
|
|
|
- for (auto& rect : dirty_rects.rects())
|
|
|
+ for (auto& rect : flush_rects.rects())
|
|
|
m_front_painter->fill_rect(rect, Color::Yellow);
|
|
|
}
|
|
|
|
|
|
if (m_screen_can_set_buffer)
|
|
|
flip_buffers();
|
|
|
|
|
|
- for (auto& r : dirty_rects.rects())
|
|
|
- flush(r);
|
|
|
+ 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);
|
|
|
}
|
|
|
|
|
|
void Compositor::flush(const Gfx::IntRect& a_rect)
|
|
@@ -301,20 +547,35 @@ void Compositor::flush(const Gfx::IntRect& a_rect)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-void Compositor::invalidate()
|
|
|
+void Compositor::invalidate_screen()
|
|
|
{
|
|
|
- m_dirty_rects.clear_with_capacity();
|
|
|
- invalidate(Screen::the().rect());
|
|
|
+ invalidate_screen(Screen::the().rect());
|
|
|
}
|
|
|
|
|
|
-void Compositor::invalidate(const Gfx::IntRect& a_rect)
|
|
|
+void Compositor::invalidate_screen(const Gfx::IntRect& screen_rect)
|
|
|
{
|
|
|
- auto rect = Gfx::IntRect::intersection(a_rect, Screen::the().rect());
|
|
|
- if (rect.is_empty())
|
|
|
+ m_dirty_screen_rects.add(screen_rect.intersected(Screen::the().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;
|
|
|
|
|
|
- m_dirty_rects.add(rect);
|
|
|
+ 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.
|
|
@@ -331,7 +592,7 @@ bool Compositor::set_background_color(const String& background_color)
|
|
|
bool ret_val = wm.config()->sync();
|
|
|
|
|
|
if (ret_val)
|
|
|
- Compositor::invalidate();
|
|
|
+ Compositor::invalidate_screen();
|
|
|
|
|
|
return ret_val;
|
|
|
}
|
|
@@ -344,7 +605,7 @@ bool Compositor::set_wallpaper_mode(const String& mode)
|
|
|
|
|
|
if (ret_val) {
|
|
|
m_wallpaper_mode = mode_to_enum(mode);
|
|
|
- Compositor::invalidate();
|
|
|
+ Compositor::invalidate_screen();
|
|
|
}
|
|
|
|
|
|
return ret_val;
|
|
@@ -360,7 +621,7 @@ bool Compositor::set_wallpaper(const String& path, Function<void(bool)>&& callba
|
|
|
[this, path, callback = move(callback)](RefPtr<Gfx::Bitmap> bitmap) {
|
|
|
m_wallpaper_path = path;
|
|
|
m_wallpaper = move(bitmap);
|
|
|
- invalidate();
|
|
|
+ invalidate_screen();
|
|
|
callback(true);
|
|
|
});
|
|
|
return true;
|
|
@@ -375,7 +636,7 @@ void Compositor::flip_buffers()
|
|
|
m_buffers_are_flipped = !m_buffers_are_flipped;
|
|
|
}
|
|
|
|
|
|
-void Compositor::run_animations()
|
|
|
+void Compositor::run_animations(Gfx::DisjointRectSet& flush_rects)
|
|
|
{
|
|
|
static const int minimize_animation_steps = 10;
|
|
|
|
|
@@ -403,12 +664,12 @@ void Compositor::run_animations()
|
|
|
#endif
|
|
|
|
|
|
m_back_painter->draw_rect(rect, Color::White);
|
|
|
+ flush_rects.add(rect);
|
|
|
+ invalidate_screen(rect);
|
|
|
|
|
|
window.step_minimize_animation();
|
|
|
if (window.minimize_animation_index() >= minimize_animation_steps)
|
|
|
window.end_minimize_animation();
|
|
|
-
|
|
|
- invalidate(rect);
|
|
|
}
|
|
|
return IterationDecision::Continue;
|
|
|
});
|
|
@@ -427,6 +688,7 @@ bool Compositor::set_resolution(int desired_width, int desired_height)
|
|
|
}
|
|
|
bool success = Screen::the().set_resolution(desired_width, desired_height);
|
|
|
init_bitmaps();
|
|
|
+ invalidate_occlusions();
|
|
|
compose();
|
|
|
return success;
|
|
|
}
|
|
@@ -439,19 +701,21 @@ Gfx::IntRect Compositor::current_cursor_rect() const
|
|
|
|
|
|
void Compositor::invalidate_cursor()
|
|
|
{
|
|
|
- auto& wm = WindowManager::the();
|
|
|
- if (wm.dnd_client())
|
|
|
- invalidate(wm.dnd_rect());
|
|
|
- invalidate(current_cursor_rect());
|
|
|
+ if (m_invalidated_cursor)
|
|
|
+ return;
|
|
|
+ m_invalidated_cursor = true;
|
|
|
+ m_invalidated_any = true;
|
|
|
+
|
|
|
+ start_compose_async_timer();
|
|
|
}
|
|
|
|
|
|
-void Compositor::draw_geometry_label()
|
|
|
+bool Compositor::draw_geometry_label(Gfx::IntRect& geometry_label_rect)
|
|
|
{
|
|
|
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_rect = {};
|
|
|
- return;
|
|
|
+ return false;
|
|
|
}
|
|
|
auto geometry_string = window_being_moved_or_resized->rect().to_string();
|
|
|
if (!window_being_moved_or_resized->size_increment().is_null()) {
|
|
@@ -459,39 +723,40 @@ void Compositor::draw_geometry_label()
|
|
|
int height_steps = (window_being_moved_or_resized->height() - window_being_moved_or_resized->base_size().height()) / window_being_moved_or_resized->size_increment().height();
|
|
|
geometry_string = String::format("%s (%dx%d)", geometry_string.characters(), width_steps, height_steps);
|
|
|
}
|
|
|
- auto geometry_label_rect = Gfx::IntRect { 0, 0, wm.font().width(geometry_string) + 16, wm.font().glyph_height() + 10 };
|
|
|
+ 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());
|
|
|
- m_back_painter->fill_rect(geometry_label_rect, wm.palette().window());
|
|
|
- m_back_painter->draw_rect(geometry_label_rect, wm.palette().threed_shadow2());
|
|
|
- m_back_painter->draw_text(geometry_label_rect, geometry_string, Gfx::TextAlignment::Center, wm.palette().window_text());
|
|
|
+ auto& back_painter = *m_back_painter;
|
|
|
+ back_painter.fill_rect(geometry_label_rect, wm.palette().window());
|
|
|
+ back_painter.draw_rect(geometry_label_rect, wm.palette().threed_shadow2());
|
|
|
+ back_painter.draw_text(geometry_label_rect, geometry_string, Gfx::TextAlignment::Center, wm.palette().window_text());
|
|
|
m_last_geometry_label_rect = geometry_label_rect;
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
-void Compositor::draw_cursor()
|
|
|
+void Compositor::draw_cursor(const Gfx::IntRect& cursor_rect)
|
|
|
{
|
|
|
auto& wm = WindowManager::the();
|
|
|
- Gfx::IntRect cursor_rect = current_cursor_rect();
|
|
|
- m_back_painter->blit(cursor_rect.location(), wm.active_cursor().bitmap(), wm.active_cursor().rect());
|
|
|
-
|
|
|
- if (wm.dnd_client()) {
|
|
|
- auto dnd_rect = wm.dnd_rect();
|
|
|
- m_back_painter->fill_rect(dnd_rect, wm.palette().selection().with_alpha(200));
|
|
|
- if (!wm.dnd_text().is_empty()) {
|
|
|
- auto text_rect = dnd_rect;
|
|
|
- if (wm.dnd_bitmap())
|
|
|
- text_rect.move_by(wm.dnd_bitmap()->width(), 0);
|
|
|
- m_back_painter->draw_text(text_rect, wm.dnd_text(), Gfx::TextAlignment::CenterLeft, wm.palette().selection_text());
|
|
|
- }
|
|
|
- if (wm.dnd_bitmap()) {
|
|
|
- m_back_painter->blit(dnd_rect.top_left(), *wm.dnd_bitmap(), wm.dnd_bitmap()->rect());
|
|
|
- }
|
|
|
- m_last_dnd_rect = dnd_rect;
|
|
|
- } else {
|
|
|
- m_last_dnd_rect = {};
|
|
|
+
|
|
|
+ if (!m_cursor_back_bitmap || m_cursor_back_bitmap->size() != cursor_rect.size()) {
|
|
|
+ m_cursor_back_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::RGB32, cursor_rect.size());
|
|
|
+ m_cursor_back_painter = make<Gfx::Painter>(*m_cursor_back_bitmap);
|
|
|
}
|
|
|
+
|
|
|
+ m_cursor_back_painter->blit({0, 0}, *m_back_bitmap, wm.active_cursor().rect().translated(cursor_rect.location()).intersected(Screen::the().rect()));
|
|
|
+ auto& back_painter = *m_back_painter;
|
|
|
+ back_painter.blit(cursor_rect.location(), wm.active_cursor().bitmap(), wm.active_cursor().rect());
|
|
|
+
|
|
|
m_last_cursor_rect = cursor_rect;
|
|
|
}
|
|
|
|
|
|
+void Compositor::restore_cursor_back()
|
|
|
+{
|
|
|
+ if (!m_cursor_back_bitmap)
|
|
|
+ return;
|
|
|
+
|
|
|
+ m_back_painter->blit(m_last_cursor_rect.location().constrained(Screen::the().rect()), *m_cursor_back_bitmap, { { 0, 0 }, m_last_cursor_rect.intersected(Screen::the().rect()).size() });
|
|
|
+}
|
|
|
+
|
|
|
void Compositor::notify_display_links()
|
|
|
{
|
|
|
ClientConnection::for_each_client([](auto& client) {
|
|
@@ -514,28 +779,6 @@ void Compositor::decrement_display_link_count(Badge<ClientConnection>)
|
|
|
m_display_link_notify_timer->stop();
|
|
|
}
|
|
|
|
|
|
-bool Compositor::any_opaque_window_contains_rect(const Gfx::IntRect& rect)
|
|
|
-{
|
|
|
- bool found_containing_window = false;
|
|
|
- WindowManager::the().for_each_visible_window_from_back_to_front([&](Window& window) {
|
|
|
- if (window.is_minimized())
|
|
|
- return IterationDecision::Continue;
|
|
|
- if (window.opacity() < 1.0f)
|
|
|
- return IterationDecision::Continue;
|
|
|
- if (window.has_alpha_channel()) {
|
|
|
- // FIXME: Just because the window has an alpha channel doesn't mean it's not opaque.
|
|
|
- // Maybe there's some way we could know this?
|
|
|
- return IterationDecision::Continue;
|
|
|
- }
|
|
|
- if (window.frame().rect().contains(rect)) {
|
|
|
- found_containing_window = true;
|
|
|
- return IterationDecision::Break;
|
|
|
- }
|
|
|
- return IterationDecision::Continue;
|
|
|
- });
|
|
|
- return found_containing_window;
|
|
|
-};
|
|
|
-
|
|
|
bool Compositor::any_opaque_window_above_this_one_contains_rect(const Window& a_window, const Gfx::IntRect& rect)
|
|
|
{
|
|
|
bool found_containing_window = false;
|
|
@@ -551,9 +794,7 @@ bool Compositor::any_opaque_window_above_this_one_contains_rect(const Window& a_
|
|
|
return IterationDecision::Continue;
|
|
|
if (window.is_minimized())
|
|
|
return IterationDecision::Continue;
|
|
|
- if (window.opacity() < 1.0f)
|
|
|
- return IterationDecision::Continue;
|
|
|
- if (window.has_alpha_channel())
|
|
|
+ if (!window.is_opaque())
|
|
|
return IterationDecision::Continue;
|
|
|
if (window.frame().rect().contains(rect)) {
|
|
|
found_containing_window = true;
|
|
@@ -578,6 +819,169 @@ void Compositor::recompute_occlusions()
|
|
|
}
|
|
|
return IterationDecision::Continue;
|
|
|
});
|
|
|
+
|
|
|
+#ifdef OCCLUSIONS_DEBUG
|
|
|
+ dbg() << "OCCLUSIONS:";
|
|
|
+#endif
|
|
|
+
|
|
|
+ auto screen_rect = Screen::the().rect();
|
|
|
+
|
|
|
+ if (auto* fullscreen_window = wm.active_fullscreen_window()) {
|
|
|
+ WindowManager::the().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();
|
|
|
+ if (&w == fullscreen_window) {
|
|
|
+ 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();
|
|
|
+ } else {
|
|
|
+ Gfx::DisjointRectSet visible_rects(screen_rect);
|
|
|
+ bool have_transparent = false;
|
|
|
+ WindowManager::the().for_each_visible_window_from_front_to_back([&](Window& w) {
|
|
|
+ auto window_frame_rect = w.frame().rect().intersected(screen_rect);
|
|
|
+ w.transparency_wallpaper_rects().clear();
|
|
|
+ auto& visible_opaque = w.opaque_rects();
|
|
|
+ auto& transparency_rects = w.transparency_rects();
|
|
|
+ if (w.is_minimized() || window_frame_rect.is_empty()) {
|
|
|
+ visible_opaque.clear();
|
|
|
+ transparency_rects.clear();
|
|
|
+ return IterationDecision::Continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ Gfx::DisjointRectSet opaque_covering;
|
|
|
+ if (w.is_opaque()) {
|
|
|
+ visible_opaque = visible_rects.intersected(window_frame_rect);
|
|
|
+ transparency_rects.clear();
|
|
|
+ } else {
|
|
|
+ visible_opaque.clear();
|
|
|
+ transparency_rects = visible_rects.intersected(window_frame_rect);
|
|
|
+ }
|
|
|
+
|
|
|
+ bool found_this_window = false;
|
|
|
+ WindowManager::the().for_each_visible_window_from_back_to_front([&](Window& w2) {
|
|
|
+ if (!found_this_window) {
|
|
|
+ if (&w == &w2)
|
|
|
+ found_this_window = true;
|
|
|
+ return IterationDecision::Continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (w2.is_minimized())
|
|
|
+ return IterationDecision::Continue;
|
|
|
+ auto window_frame_rect2 = w2.frame().rect().intersected(screen_rect);
|
|
|
+ auto covering_rect = window_frame_rect2.intersected(window_frame_rect);
|
|
|
+ if (covering_rect.is_empty())
|
|
|
+ return IterationDecision::Continue;
|
|
|
+
|
|
|
+ if (w2.is_opaque()) {
|
|
|
+ opaque_covering.add(covering_rect);
|
|
|
+ if (opaque_covering.contains(window_frame_rect)) {
|
|
|
+ // This window is entirely covered by another opaque window
|
|
|
+ visible_opaque.clear();
|
|
|
+ transparency_rects.clear();
|
|
|
+ return IterationDecision::Break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!visible_opaque.is_empty()) {
|
|
|
+ auto uncovered_opaque = visible_opaque.shatter(covering_rect);
|
|
|
+ visible_opaque = move(uncovered_opaque);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!transparency_rects.is_empty()) {
|
|
|
+ auto uncovered_transparency = transparency_rects.shatter(covering_rect);
|
|
|
+ transparency_rects = move(uncovered_transparency);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ visible_rects.for_each_intersected(covering_rect, [&](const Gfx::IntRect& intersected) {
|
|
|
+ transparency_rects.add(intersected);
|
|
|
+ if (!visible_opaque.is_empty()) {
|
|
|
+ auto uncovered_opaque = visible_opaque.shatter(intersected);
|
|
|
+ visible_opaque = move(uncovered_opaque);
|
|
|
+ }
|
|
|
+ return IterationDecision::Continue;
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ return IterationDecision::Continue;
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!transparency_rects.is_empty())
|
|
|
+ have_transparent = true;
|
|
|
+
|
|
|
+ ASSERT(!visible_opaque.intersects(transparency_rects));
|
|
|
+
|
|
|
+ if (w.is_opaque()) {
|
|
|
+ // Determine visible area for the window below
|
|
|
+ auto visible_rects_below_window = visible_rects.shatter(window_frame_rect);
|
|
|
+ visible_rects = move(visible_rects_below_window);
|
|
|
+ }
|
|
|
+ return IterationDecision::Continue;
|
|
|
+ });
|
|
|
+
|
|
|
+ if (have_transparent) {
|
|
|
+ // Determine what transparent window areas need to render the wallpaper first
|
|
|
+ WindowManager::the().for_each_visible_window_from_back_to_front([&](Window& w) {
|
|
|
+ auto& transparency_wallpaper_rects = w.transparency_wallpaper_rects();
|
|
|
+ if (w.is_opaque() || w.is_minimized()) {
|
|
|
+ transparency_wallpaper_rects.clear();
|
|
|
+ return IterationDecision::Continue;
|
|
|
+ }
|
|
|
+ Gfx::DisjointRectSet& transparency_rects = w.transparency_rects();
|
|
|
+ if (transparency_rects.is_empty()) {
|
|
|
+ transparency_wallpaper_rects.clear();
|
|
|
+ return IterationDecision::Continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ transparency_wallpaper_rects = visible_rects.intersected(transparency_rects);
|
|
|
+
|
|
|
+ auto remaining_visible = visible_rects.shatter(transparency_wallpaper_rects);
|
|
|
+ visible_rects = move(remaining_visible);
|
|
|
+ return IterationDecision::Continue;
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ m_opaque_wallpaper_rects = move(visible_rects);
|
|
|
+ }
|
|
|
+
|
|
|
+#ifdef OCCLUSIONS_DEBUG
|
|
|
+ for (auto& r : m_opaque_wallpaper_rects.rects())
|
|
|
+ dbg() << " wallpaper opaque: " << r;
|
|
|
+#endif
|
|
|
+
|
|
|
+ wm.for_each_visible_window_from_back_to_front([&](Window& w) {
|
|
|
+ auto window_frame_rect = w.frame().rect().intersected(screen_rect);
|
|
|
+ if (w.is_minimized() || window_frame_rect.is_empty())
|
|
|
+ return IterationDecision::Continue;
|
|
|
+
|
|
|
+#ifdef OCCLUSIONS_DEBUG
|
|
|
+ dbg() << " Window " << w.title() << " frame rect: " << window_frame_rect;
|
|
|
+ for (auto& r : w.opaque_rects().rects())
|
|
|
+ dbg() << " opaque: " << r;
|
|
|
+ for (auto& r : w.transparency_wallpaper_rects().rects())
|
|
|
+ dbg() << " transparent wallpaper: " << r;
|
|
|
+ for (auto& r : w.transparency_rects().rects())
|
|
|
+ dbg() << " transparent: " << r;
|
|
|
+#endif
|
|
|
+ ASSERT(!w.opaque_rects().intersects(m_opaque_wallpaper_rects));
|
|
|
+ ASSERT(!w.transparency_rects().intersects(m_opaque_wallpaper_rects));
|
|
|
+ ASSERT(!w.transparency_wallpaper_rects().intersects(m_opaque_wallpaper_rects));
|
|
|
+ return IterationDecision::Continue;
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
}
|