1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-22 19:45:08 +00:00
serenity/WindowServer/WSWindowManager.cpp
Andreas Kling 61bba1b9dd WindowServer: Skip blitting windows into the back buffer if possible.
Windows that don't intersect any of the dirty rects don't need to be copied
into the back buffer since they won't be affected by the subsequent flushes.
2019-01-16 20:21:44 +01:00

424 lines
13 KiB
C++

#include "WSWindowManager.h"
#include "WSWindow.h"
#include "WSScreen.h"
#include "WSEventLoop.h"
#include "WSFrameBuffer.h"
#include "Process.h"
#include "MemoryManager.h"
#include <Widgets/Painter.h>
#include <Widgets/CharacterBitmap.h>
#include <AK/StdLibExtras.h>
//#define DEBUG_FLUSH_YELLOW
//#define DEBUG_COUNTERS
static const int windowTitleBarHeight = 16;
static inline Rect titleBarRectForWindow(const Rect& window)
{
return {
window.x() - 1,
window.y() - windowTitleBarHeight,
window.width() + 2,
windowTitleBarHeight
};
}
static inline Rect titleBarTitleRectForWindow(const Rect& window)
{
auto titleBarRect = titleBarRectForWindow(window);
return {
titleBarRect.x() + 2,
titleBarRect.y(),
titleBarRect.width() - 4,
titleBarRect.height()
};
}
static inline Rect borderRectForWindow(const Rect& window)
{
auto titleBarRect = titleBarRectForWindow(window);
return { titleBarRect.x() - 1,
titleBarRect.y() - 1,
titleBarRect.width() + 2,
windowTitleBarHeight + window.height() + 3
};
}
static inline Rect outerRectForWindow(const Rect& window)
{
auto rect = borderRectForWindow(window);
rect.inflate(2, 2);
return rect;
}
static WSWindowManager* s_the_window_manager;
WSWindowManager& WSWindowManager::the()
{
if (!s_the_window_manager)
s_the_window_manager = new WSWindowManager;
return *s_the_window_manager;
}
void WSWindowManager::initialize()
{
s_the_window_manager = nullptr;
}
static const char* cursor_bitmap_inner_ascii = {
" # "
" ## "
" ### "
" #### "
" ##### "
" ###### "
" ####### "
" ######## "
" ######### "
" ########## "
" ###### "
" ## ## "
" # ## "
" ## "
" ## "
" ## "
" "
};
static const char* cursor_bitmap_outer_ascii = {
"## "
"# # "
"# # "
"# # "
"# # "
"# # "
"# # "
"# # "
"# # "
"# # "
"# #### "
"# ## # "
"# # # # "
"## # # "
" # # "
" # # "
" ## "
};
WSWindowManager::WSWindowManager()
: m_framebuffer(WSFrameBuffer::the())
, m_screen_rect(m_framebuffer.rect())
{
#ifndef DEBUG_COUNTERS
(void)m_recompose_count;
(void)m_flush_count;
#endif
auto size = m_screen_rect.size();
m_front_bitmap = GraphicsBitmap::create_wrapper(size, m_framebuffer.scanline(0));
auto* region = current->allocate_region(LinearAddress(), size.width() * size.height() * sizeof(RGBA32), "BackBitmap", true, true, true);
m_back_bitmap = GraphicsBitmap::create_wrapper(m_screen_rect.size(), (RGBA32*)region->linearAddress.get());
m_front_painter = make<Painter>(*m_front_bitmap);
m_back_painter = make<Painter>(*m_back_bitmap);
m_activeWindowBorderColor = Color(0, 64, 192);
m_activeWindowTitleColor = Color::White;
m_inactiveWindowBorderColor = Color(64, 64, 64);
m_inactiveWindowTitleColor = Color::White;
m_cursor_bitmap_inner = CharacterBitmap::create_from_ascii(cursor_bitmap_inner_ascii, 12, 17);
m_cursor_bitmap_outer = CharacterBitmap::create_from_ascii(cursor_bitmap_outer_ascii, 12, 17);
invalidate();
compose();
}
WSWindowManager::~WSWindowManager()
{
}
void WSWindowManager::paintWindowFrame(WSWindow& window)
{
//printf("[WM] paintWindowFrame {%p}, rect: %d,%d %dx%d\n", &window, window.rect().x(), window.rect().y(), window.rect().width(), window.rect().height());
auto titleBarRect = titleBarRectForWindow(window.rect());
auto titleBarTitleRect = titleBarTitleRectForWindow(window.rect());
auto outerRect = outerRectForWindow(window.rect());
auto borderRect = borderRectForWindow(window.rect());
Rect inner_border_rect {
window.x() - 1,
window.y() - 1,
window.width() + 2,
window.height() + 2
};
auto titleColor = &window == activeWindow() ? m_activeWindowTitleColor : m_inactiveWindowTitleColor;
auto borderColor = &window == activeWindow() ? m_activeWindowBorderColor : m_inactiveWindowBorderColor;
m_back_painter->fill_rect(titleBarRect, borderColor);
m_back_painter->draw_rect(borderRect, Color::MidGray);
m_back_painter->draw_rect(outerRect, borderColor);
m_back_painter->draw_rect(inner_border_rect, borderColor);
m_back_painter->draw_text(titleBarTitleRect, window.title(), Painter::TextAlignment::CenterLeft, titleColor);
}
void WSWindowManager::addWindow(WSWindow& window)
{
m_windows.set(&window);
m_windows_in_order.append(&window);
if (!activeWindow())
setActiveWindow(&window);
}
void WSWindowManager::move_to_front(WSWindow& window)
{
m_windows_in_order.remove(&window);
m_windows_in_order.append(&window);
}
void WSWindowManager::removeWindow(WSWindow& window)
{
if (!m_windows.contains(&window))
return;
invalidate(window);
m_windows.remove(&window);
m_windows_in_order.remove(&window);
if (!activeWindow() && !m_windows.is_empty())
setActiveWindow(*m_windows.begin());
}
void WSWindowManager::notifyTitleChanged(WSWindow& window)
{
printf("[WM] WSWindow{%p} title set to '%s'\n", &window, window.title().characters());
}
void WSWindowManager::notifyRectChanged(WSWindow& window, const Rect& old_rect, const Rect& new_rect)
{
printf("[WM] WSWindow %p rect changed (%d,%d %dx%d) -> (%d,%d %dx%d)\n", &window, old_rect.x(), old_rect.y(), old_rect.width(), old_rect.height(), new_rect.x(), new_rect.y(), new_rect.width(), new_rect.height());
ASSERT_INTERRUPTS_ENABLED();
LOCKER(m_lock);
invalidate(outerRectForWindow(old_rect));
invalidate(outerRectForWindow(new_rect));
}
void WSWindowManager::handleTitleBarMouseEvent(WSWindow& window, MouseEvent& event)
{
if (event.type() == WSEvent::MouseDown && event.button() == MouseButton::Left) {
printf("[WM] Begin dragging WSWindow{%p}\n", &window);
m_dragWindow = window.makeWeakPtr();;
m_dragOrigin = event.position();
m_dragWindowOrigin = window.position();
m_dragStartRect = outerRectForWindow(window.rect());
window.set_is_being_dragged(true);
return;
}
}
void WSWindowManager::processMouseEvent(MouseEvent& event)
{
if (event.type() == WSEvent::MouseUp && event.button() == MouseButton::Left) {
if (m_dragWindow) {
printf("[WM] Finish dragging WSWindow{%p}\n", m_dragWindow.ptr());
invalidate(m_dragStartRect);
invalidate(*m_dragWindow);
m_dragWindow->set_is_being_dragged(false);
m_dragEndRect = outerRectForWindow(m_dragWindow->rect());
m_dragWindow = nullptr;
return;
}
}
if (event.type() == WSEvent::MouseMove) {
if (m_dragWindow) {
auto old_window_rect = m_dragWindow->rect();
Point pos = m_dragWindowOrigin;
printf("[WM] Dragging [origin: %d,%d] now: %d,%d\n", m_dragOrigin.x(), m_dragOrigin.y(), event.x(), event.y());
pos.move_by(event.x() - m_dragOrigin.x(), event.y() - m_dragOrigin.y());
m_dragWindow->set_position_without_repaint(pos);
invalidate(outerRectForWindow(old_window_rect));
invalidate(outerRectForWindow(m_dragWindow->rect()));
return;
}
}
for (auto* window = m_windows_in_order.tail(); window; window = window->prev()) {
if (titleBarRectForWindow(window->rect()).contains(event.position())) {
if (event.type() == WSEvent::MouseDown) {
move_to_front(*window);
setActiveWindow(window);
}
handleTitleBarMouseEvent(*window, event);
return;
}
if (window->rect().contains(event.position())) {
if (event.type() == WSEvent::MouseDown) {
move_to_front(*window);
setActiveWindow(window);
}
// FIXME: Re-use the existing event instead of crafting a new one?
auto localEvent = make<MouseEvent>(event.type(), event.x() - window->rect().x(), event.y() - window->rect().y(), event.button());
window->event(*localEvent);
return;
}
}
}
void WSWindowManager::compose()
{
auto invalidated_rects = move(m_invalidated_rects);
#ifdef DEBUG_COUNTERS
dbgprintf("[WM] compose #%u (%u rects)\n", ++m_recompose_count, invalidated_rects.size());
dbgprintf("kmalloc stats: alloc:%u free:%u eternal:%u\n", sum_alloc, sum_free, kmalloc_sum_eternal);
#endif
auto any_window_contains_rect = [this] (const Rect& r) {
for (auto* window = m_windows_in_order.head(); window; window = window->next()) {
if (outerRectForWindow(window->rect()).contains(r))
return true;
}
return false;
};
auto any_dirty_rect_intersects_window = [&invalidated_rects] (const WSWindow& window) {
auto window_rect = outerRectForWindow(window.rect());
for (auto& dirty_rect : invalidated_rects) {
if (dirty_rect.intersects(window_rect))
return true;
}
return false;
};
for (auto& r : invalidated_rects) {
if (any_window_contains_rect(r))
continue;
//dbgprintf("Repaint root %d,%d %dx%d\n", r.x(), r.y(), r.width(), r.height());
m_back_painter->fill_rect(r, Color(0, 72, 96));
}
for (auto* window = m_windows_in_order.head(); window; window = window->next()) {
if (!window->backing())
continue;
if (!any_dirty_rect_intersects_window(*window))
continue;
paintWindowFrame(*window);
m_back_painter->blit(window->position(), *window->backing());
}
for (auto& r : invalidated_rects)
flush(r);
draw_cursor();
}
void WSWindowManager::draw_cursor()
{
ASSERT_INTERRUPTS_ENABLED();
LOCKER(m_lock);
auto cursor_location = m_framebuffer.cursor_location();
Rect cursor_rect { cursor_location.x(), cursor_location.y(), (int)m_cursor_bitmap_inner->width(), (int)m_cursor_bitmap_inner->height() };
flush(m_last_cursor_rect.united(cursor_rect));
Color inner_color = Color::White;
Color outer_color = Color::Black;
if (m_framebuffer.left_mouse_button_pressed())
swap(inner_color, outer_color);
m_front_painter->draw_bitmap(cursor_location, *m_cursor_bitmap_inner, inner_color);
m_front_painter->draw_bitmap(cursor_location, *m_cursor_bitmap_outer, outer_color);
m_last_cursor_rect = cursor_rect;
}
void WSWindowManager::event(WSEvent& event)
{
ASSERT_INTERRUPTS_ENABLED();
LOCKER(m_lock);
if (event.isMouseEvent())
return processMouseEvent(static_cast<MouseEvent&>(event));
if (event.isKeyEvent()) {
// FIXME: This is a good place to hook key events globally. :)
if (m_activeWindow)
return m_activeWindow->event(event);
return;
}
if (event.type() == WSEvent::WM_Compose) {
m_pending_compose_event = false;
compose();
return;
}
}
void WSWindowManager::setActiveWindow(WSWindow* window)
{
if (window == m_activeWindow.ptr())
return;
if (auto* previously_active_window = m_activeWindow.ptr())
invalidate(*previously_active_window);
m_activeWindow = window->makeWeakPtr();
if (m_activeWindow)
invalidate(*m_activeWindow);
}
void WSWindowManager::invalidate()
{
m_invalidated_rects.clear_with_capacity();
m_invalidated_rects.append(m_screen_rect);
}
void WSWindowManager::invalidate(const Rect& a_rect)
{
auto rect = Rect::intersection(a_rect, m_screen_rect);
if (rect.is_empty())
return;
for (auto& r : m_invalidated_rects) {
if (r.contains(rect))
return;
if (r.intersects(rect)) {
// Unite with the existing dirty rect.
// FIXME: It would be much nicer to compute the exact rects needing repaint.
r = r.united(rect);
return;
}
}
m_invalidated_rects.append(rect);
if (!m_pending_compose_event) {
ASSERT_INTERRUPTS_ENABLED();
WSEventLoop::the().post_event(this, make<WSEvent>(WSEvent::WM_Compose));
m_pending_compose_event = true;
}
}
void WSWindowManager::invalidate(const WSWindow& window)
{
ASSERT_INTERRUPTS_ENABLED();
LOCKER(m_lock);
invalidate(outerRectForWindow(window.rect()));
}
void WSWindowManager::flush(const Rect& a_rect)
{
auto rect = Rect::intersection(a_rect, m_screen_rect);
#ifdef DEBUG_COUNTERS
dbgprintf("[WM] flush #%u (%d,%d %dx%d)\n", ++m_flush_count, rect.x(), rect.y(), rect.width(), rect.height());
#endif
RGBA32* front_ptr = m_front_bitmap->scanline(rect.y()) + rect.x();
const RGBA32* back_ptr = m_back_bitmap->scanline(rect.y()) + rect.x();
size_t pitch = m_back_bitmap->pitch();
#ifdef DEBUG_FLUSH_YELLOW
m_front_painter->fill_rect(rect, Color::Yellow);
#endif
for (int y = 0; y < rect.height(); ++y) {
fast_dword_copy(front_ptr, back_ptr, rect.width());
front_ptr = (RGBA32*)((byte*)front_ptr + pitch);
back_ptr = (const RGBA32*)((const byte*)back_ptr + pitch);
}
}