WSCompositor.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. #include "WSCompositor.h"
  2. #include "WSEvent.h"
  3. #include "WSEventLoop.h"
  4. #include "WSScreen.h"
  5. #include "WSWindow.h"
  6. #include "WSWindowManager.h"
  7. #include <SharedGraphics/Font.h>
  8. #include <SharedGraphics/PNGLoader.h>
  9. #include <SharedGraphics/Painter.h>
  10. // #define COMPOSITOR_DEBUG
  11. WSCompositor& WSCompositor::the()
  12. {
  13. static WSCompositor s_the;
  14. return s_the;
  15. }
  16. WallpaperMode mode_to_enum(const String& name)
  17. {
  18. if (name == "simple")
  19. return WallpaperMode::Simple;
  20. if (name == "tile")
  21. return WallpaperMode::Tile;
  22. if (name == "center")
  23. return WallpaperMode::Center;
  24. if (name == "scaled")
  25. return WallpaperMode::Scaled;
  26. return WallpaperMode::Simple;
  27. }
  28. WSCompositor::WSCompositor()
  29. {
  30. auto size = WSScreen::the().size();
  31. m_front_bitmap = GraphicsBitmap::create_wrapper(GraphicsBitmap::Format::RGB32, size, WSScreen::the().scanline(0));
  32. m_back_bitmap = GraphicsBitmap::create_wrapper(GraphicsBitmap::Format::RGB32, size, WSScreen::the().scanline(size.height()));
  33. m_front_painter = make<Painter>(*m_front_bitmap);
  34. m_back_painter = make<Painter>(*m_back_bitmap);
  35. m_wallpaper_path = "/res/wallpapers/retro.rgb";
  36. m_wallpaper = GraphicsBitmap::load_from_file(GraphicsBitmap::Format::RGBA32, m_wallpaper_path, { 1024, 768 });
  37. m_compose_timer.on_timeout = [=]() {
  38. #if defined(COMPOSITOR_DEBUG)
  39. dbgprintf("WSCompositor: delayed frame callback: %d rects\n", m_dirty_rects.size());
  40. #endif
  41. compose();
  42. };
  43. m_compose_timer.set_single_shot(true);
  44. m_compose_timer.set_interval(1000 / 60);
  45. m_immediate_compose_timer.on_timeout = [=]() {
  46. #if defined(COMPOSITOR_DEBUG)
  47. dbgprintf("WSCompositor: immediate frame callback: %d rects\n", m_dirty_rects.size());
  48. #endif
  49. compose();
  50. };
  51. m_immediate_compose_timer.set_single_shot(true);
  52. m_immediate_compose_timer.set_interval(0);
  53. }
  54. void WSCompositor::compose()
  55. {
  56. auto& wm = WSWindowManager::the();
  57. if (m_wallpaper_mode == WallpaperMode::Unchecked)
  58. m_wallpaper_mode = mode_to_enum(wm.wm_config()->read_entry("Background", "Mode", "simple"));
  59. auto& ws = WSScreen::the();
  60. auto dirty_rects = move(m_dirty_rects);
  61. if (dirty_rects.size() == 0) {
  62. // nothing dirtied since the last compose pass.
  63. return;
  64. }
  65. dirty_rects.add(Rect::intersection(m_last_geometry_label_rect, WSScreen::the().rect()));
  66. dirty_rects.add(Rect::intersection(m_last_cursor_rect, WSScreen::the().rect()));
  67. dirty_rects.add(Rect::intersection(current_cursor_rect(), WSScreen::the().rect()));
  68. #ifdef DEBUG_COUNTERS
  69. dbgprintf("[WM] compose #%u (%u rects)\n", ++m_compose_count, dirty_rects.rects().size());
  70. #endif
  71. auto any_dirty_rect_intersects_window = [&dirty_rects](const WSWindow& window) {
  72. auto window_frame_rect = window.frame().rect();
  73. for (auto& dirty_rect : dirty_rects.rects()) {
  74. if (dirty_rect.intersects(window_frame_rect))
  75. return true;
  76. }
  77. return false;
  78. };
  79. for (auto& dirty_rect : dirty_rects.rects()) {
  80. if (wm.any_opaque_window_contains_rect(dirty_rect))
  81. continue;
  82. m_back_painter->fill_rect(dirty_rect, wm.m_background_color);
  83. if (m_wallpaper) {
  84. if (m_wallpaper_mode == WallpaperMode::Simple) {
  85. m_back_painter->blit(dirty_rect.location(), *m_wallpaper, dirty_rect);
  86. } else if (m_wallpaper_mode == WallpaperMode::Center) {
  87. Point offset { ws.size().width() / 2 - m_wallpaper->size().width() / 2,
  88. ws.size().height() / 2 - m_wallpaper->size().height() / 2 };
  89. m_back_painter->blit_offset(dirty_rect.location(), *m_wallpaper,
  90. dirty_rect, offset);
  91. } else if (m_wallpaper_mode == WallpaperMode::Tile) {
  92. m_back_painter->blit_tiled(dirty_rect.location(), *m_wallpaper, dirty_rect);
  93. } else {
  94. // FIXME: Does not work: offset rect creates trails.
  95. m_back_painter->draw_scaled_bitmap(dirty_rect, *m_wallpaper,
  96. { dirty_rect.location(),
  97. m_wallpaper->size() });
  98. }
  99. }
  100. }
  101. auto compose_window = [&](WSWindow& window) -> IterationDecision {
  102. if (!any_dirty_rect_intersects_window(window))
  103. return IterationDecision::Continue;
  104. PainterStateSaver saver(*m_back_painter);
  105. m_back_painter->add_clip_rect(window.frame().rect());
  106. RetainPtr<GraphicsBitmap> backing_store = window.backing_store();
  107. for (auto& dirty_rect : dirty_rects.rects()) {
  108. if (wm.any_opaque_window_above_this_one_contains_rect(window, dirty_rect))
  109. continue;
  110. PainterStateSaver saver(*m_back_painter);
  111. m_back_painter->add_clip_rect(dirty_rect);
  112. if (!backing_store)
  113. m_back_painter->fill_rect(dirty_rect, window.background_color());
  114. if (!window.is_fullscreen())
  115. window.frame().paint(*m_back_painter);
  116. if (!backing_store)
  117. continue;
  118. Rect dirty_rect_in_window_coordinates = Rect::intersection(dirty_rect, window.rect());
  119. if (dirty_rect_in_window_coordinates.is_empty())
  120. continue;
  121. dirty_rect_in_window_coordinates.move_by(-window.position());
  122. auto dst = window.position();
  123. dst.move_by(dirty_rect_in_window_coordinates.location());
  124. m_back_painter->blit(dst, *backing_store, dirty_rect_in_window_coordinates, window.opacity());
  125. if (backing_store->width() < window.width()) {
  126. Rect right_fill_rect { window.x() + backing_store->width(), window.y(), window.width() - backing_store->width(), window.height() };
  127. m_back_painter->fill_rect(right_fill_rect, window.background_color());
  128. }
  129. if (backing_store->height() < window.height()) {
  130. Rect bottom_fill_rect { window.x(), window.y() + backing_store->height(), window.width(), window.height() - backing_store->height() };
  131. m_back_painter->fill_rect(bottom_fill_rect, window.background_color());
  132. }
  133. }
  134. return IterationDecision::Continue;
  135. };
  136. if (auto* fullscreen_window = wm.active_fullscreen_window()) {
  137. compose_window(*fullscreen_window);
  138. } else {
  139. wm.for_each_visible_window_from_back_to_front([&](WSWindow& window) {
  140. return compose_window(window);
  141. });
  142. draw_geometry_label();
  143. draw_menubar();
  144. }
  145. draw_cursor();
  146. if (m_flash_flush) {
  147. for (auto& rect : dirty_rects.rects())
  148. m_front_painter->fill_rect(rect, Color::Yellow);
  149. }
  150. flip_buffers();
  151. for (auto& r : dirty_rects.rects())
  152. flush(r);
  153. }
  154. void WSCompositor::flush(const Rect& a_rect)
  155. {
  156. auto rect = Rect::intersection(a_rect, WSScreen::the().rect());
  157. #ifdef DEBUG_COUNTERS
  158. dbgprintf("[WM] flush #%u (%d,%d %dx%d)\n", ++m_flush_count, rect.x(), rect.y(), rect.width(), rect.height());
  159. #endif
  160. const RGBA32* front_ptr = m_front_bitmap->scanline(rect.y()) + rect.x();
  161. RGBA32* back_ptr = m_back_bitmap->scanline(rect.y()) + rect.x();
  162. size_t pitch = m_back_bitmap->pitch();
  163. for (int y = 0; y < rect.height(); ++y) {
  164. fast_dword_copy(back_ptr, front_ptr, rect.width());
  165. front_ptr = (const RGBA32*)((const byte*)front_ptr + pitch);
  166. back_ptr = (RGBA32*)((byte*)back_ptr + pitch);
  167. }
  168. }
  169. void WSCompositor::invalidate()
  170. {
  171. m_dirty_rects.clear_with_capacity();
  172. invalidate(WSScreen::the().rect());
  173. }
  174. void WSCompositor::invalidate(const Rect& a_rect)
  175. {
  176. auto rect = Rect::intersection(a_rect, WSScreen::the().rect());
  177. if (rect.is_empty())
  178. return;
  179. m_dirty_rects.add(rect);
  180. // We delay composition by a timer interval, but to not affect latency too
  181. // much, if a pending compose is not already scheduled, we also schedule an
  182. // immediate compose the next spin of the event loop.
  183. if (!m_compose_timer.is_active()) {
  184. #if defined(COMPOSITOR_DEBUG)
  185. dbgprintf("Invalidated (starting immediate frame): %dx%d %dx%d\n", a_rect.x(), a_rect.y(), a_rect.width(), a_rect.height());
  186. #endif
  187. m_compose_timer.start();
  188. m_immediate_compose_timer.start();
  189. } else {
  190. #if defined(COMPOSITOR_DEBUG)
  191. dbgprintf("Invalidated (frame callback pending): %dx%d %dx%d\n", a_rect.x(), a_rect.y(), a_rect.width(), a_rect.height());
  192. #endif
  193. }
  194. }
  195. bool WSCompositor::set_wallpaper(const String& path, Function<void(bool)>&& callback)
  196. {
  197. struct Context {
  198. String path;
  199. RetainPtr<GraphicsBitmap> bitmap;
  200. Function<void(bool)> callback;
  201. };
  202. auto context = make<Context>();
  203. context->path = path;
  204. context->callback = move(callback);
  205. int rc = create_thread([](void* ctx) -> int {
  206. OwnPtr<Context> context((Context*)ctx);
  207. context->bitmap = load_png(context->path);
  208. if (!context->bitmap) {
  209. context->callback(false);
  210. exit_thread(0);
  211. return 0;
  212. }
  213. the().deferred_invoke([context = move(context)](auto&) {
  214. the().finish_setting_wallpaper(context->path, *context->bitmap);
  215. context->callback(true);
  216. });
  217. exit_thread(0);
  218. return 0;
  219. },
  220. context.leak_ptr());
  221. ASSERT(rc == 0);
  222. return true;
  223. }
  224. void WSCompositor::finish_setting_wallpaper(const String& path, Retained<GraphicsBitmap>&& bitmap)
  225. {
  226. m_wallpaper_path = path;
  227. m_wallpaper = move(bitmap);
  228. invalidate();
  229. }
  230. void WSCompositor::flip_buffers()
  231. {
  232. swap(m_front_bitmap, m_back_bitmap);
  233. swap(m_front_painter, m_back_painter);
  234. int new_y_offset = m_buffers_are_flipped ? 0 : WSScreen::the().height();
  235. WSScreen::the().set_y_offset(new_y_offset);
  236. m_buffers_are_flipped = !m_buffers_are_flipped;
  237. }
  238. void WSCompositor::set_resolution(int width, int height)
  239. {
  240. auto screen_rect = WSScreen::the().rect();
  241. if (screen_rect.width() == width && screen_rect.height() == height)
  242. return;
  243. m_wallpaper_path = {};
  244. m_wallpaper = nullptr;
  245. WSScreen::the().set_resolution(width, height);
  246. m_front_bitmap = GraphicsBitmap::create_wrapper(GraphicsBitmap::Format::RGB32, { width, height }, WSScreen::the().scanline(0));
  247. m_back_bitmap = GraphicsBitmap::create_wrapper(GraphicsBitmap::Format::RGB32, { width, height }, WSScreen::the().scanline(height));
  248. m_front_painter = make<Painter>(*m_front_bitmap);
  249. m_back_painter = make<Painter>(*m_back_bitmap);
  250. m_buffers_are_flipped = false;
  251. invalidate();
  252. compose();
  253. }
  254. Rect WSCompositor::current_cursor_rect() const
  255. {
  256. auto& wm = WSWindowManager::the();
  257. return { WSScreen::the().cursor_location().translated(-wm.active_cursor().hotspot()), wm.active_cursor().size() };
  258. }
  259. void WSCompositor::invalidate_cursor()
  260. {
  261. invalidate(current_cursor_rect());
  262. }
  263. void WSCompositor::draw_geometry_label()
  264. {
  265. auto& wm = WSWindowManager::the();
  266. auto* window_being_moved_or_resized = wm.m_drag_window ? wm.m_drag_window.ptr() : (wm.m_resize_window ? wm.m_resize_window.ptr() : nullptr);
  267. if (!window_being_moved_or_resized) {
  268. m_last_geometry_label_rect = {};
  269. return;
  270. }
  271. auto geometry_string = window_being_moved_or_resized->rect().to_string();
  272. if (!window_being_moved_or_resized->size_increment().is_null()) {
  273. int width_steps = (window_being_moved_or_resized->width() - window_being_moved_or_resized->base_size().width()) / window_being_moved_or_resized->size_increment().width();
  274. 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();
  275. geometry_string = String::format("%s (%dx%d)", geometry_string.characters(), width_steps, height_steps);
  276. }
  277. auto geometry_label_rect = Rect { 0, 0, wm.font().width(geometry_string) + 16, wm.font().glyph_height() + 10 };
  278. geometry_label_rect.center_within(window_being_moved_or_resized->rect());
  279. m_back_painter->fill_rect(geometry_label_rect, Color::LightGray);
  280. m_back_painter->draw_rect(geometry_label_rect, Color::DarkGray);
  281. m_back_painter->draw_text(geometry_label_rect, geometry_string, TextAlignment::Center);
  282. m_last_geometry_label_rect = geometry_label_rect;
  283. }
  284. void WSCompositor::draw_cursor()
  285. {
  286. auto& wm = WSWindowManager::the();
  287. Rect cursor_rect = current_cursor_rect();
  288. Color inner_color = Color::White;
  289. Color outer_color = Color::Black;
  290. if (WSScreen::the().mouse_button_state() & (unsigned)MouseButton::Left)
  291. swap(inner_color, outer_color);
  292. m_back_painter->blit(cursor_rect.location(), wm.active_cursor().bitmap(), wm.active_cursor().rect());
  293. m_last_cursor_rect = cursor_rect;
  294. }
  295. void WSCompositor::draw_menubar()
  296. {
  297. auto& wm = WSWindowManager::the();
  298. auto menubar_rect = wm.menubar_rect();
  299. m_back_painter->fill_rect(menubar_rect, Color::LightGray);
  300. m_back_painter->draw_line({ 0, menubar_rect.bottom() }, { menubar_rect.right(), menubar_rect.bottom() }, Color::MidGray);
  301. int index = 0;
  302. wm.for_each_active_menubar_menu([&](WSMenu& menu) {
  303. Color text_color = Color::Black;
  304. if (&menu == wm.current_menu()) {
  305. m_back_painter->fill_rect(menu.rect_in_menubar(), wm.menu_selection_color());
  306. text_color = Color::White;
  307. }
  308. m_back_painter->draw_text(
  309. menu.text_rect_in_menubar(),
  310. menu.name(),
  311. index == 1 ? wm.app_menu_font() : wm.menu_font(),
  312. TextAlignment::CenterLeft,
  313. text_color);
  314. ++index;
  315. return true;
  316. });
  317. int username_width = Font::default_bold_font().width(wm.m_username);
  318. Rect username_rect {
  319. menubar_rect.right() - wm.menubar_menu_margin() / 2 - Font::default_bold_font().width(wm.m_username),
  320. menubar_rect.y(),
  321. username_width,
  322. menubar_rect.height()
  323. };
  324. m_back_painter->draw_text(username_rect, wm.m_username, Font::default_bold_font(), TextAlignment::CenterRight, Color::Black);
  325. time_t now = time(nullptr);
  326. auto* tm = localtime(&now);
  327. auto time_text = String::format("%4u-%02u-%02u %02u:%02u:%02u",
  328. tm->tm_year + 1900,
  329. tm->tm_mon + 1,
  330. tm->tm_mday,
  331. tm->tm_hour,
  332. tm->tm_min,
  333. tm->tm_sec);
  334. int time_width = wm.font().width(time_text);
  335. Rect time_rect {
  336. username_rect.left() - wm.menubar_menu_margin() / 2 - time_width,
  337. menubar_rect.y(),
  338. time_width,
  339. menubar_rect.height()
  340. };
  341. m_back_painter->draw_text(time_rect, time_text, wm.font(), TextAlignment::CenterRight, Color::Black);
  342. Rect cpu_rect { time_rect.right() - wm.font().width(time_text) - wm.m_cpu_monitor.capacity() - 10, time_rect.y() + 1, wm.m_cpu_monitor.capacity(), time_rect.height() - 2 };
  343. wm.m_cpu_monitor.paint(*m_back_painter, cpu_rect);
  344. }