Skip to content

Commit

Permalink
WSCompositor: Allow a compose to bypass the timer when it first happens
Browse files Browse the repository at this point in the history
d66fa60 introduced the use of a timer
to coalesce screen updates. This is OK, but it does introduce update
latency.

To help mitigate the impact of this, we now have a second (immediate)
timer. When a compose pass is first triggered, the immediate timer will
allow the compose to happen on the next spin of the event loop (so, only
coalescing updates across a single event loop pass). Any updates that
trigger while the delayed timer is running, though, will be delayed to
that (~60fps) timer.

This fixes SerenityOS#103.
  • Loading branch information
rburchell authored and awesomekling committed May 26, 2019
1 parent 8df3e25 commit 9b86eb9
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 4 deletions.
33 changes: 29 additions & 4 deletions Servers/WindowServer/WSCompositor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,33 @@ WSCompositor::WSCompositor()

m_compose_timer.on_timeout = [=]() {
#if defined(COMPOSITOR_DEBUG)
dbgprintf("WSCompositor: frame callback\n");
dbgprintf("WSCompositor: delayed frame callback: %d rects\n", m_dirty_rects.size());
#endif
compose();
};
m_compose_timer.set_single_shot(true);
m_compose_timer.set_interval(1000 / 60);
m_immediate_compose_timer.on_timeout = [=]() {
#if defined(COMPOSITOR_DEBUG)
dbgprintf("WSCompositor: immediate frame callback: %d rects\n", m_dirty_rects.size());
#endif
compose();
};
m_immediate_compose_timer.set_single_shot(true);
m_immediate_compose_timer.set_interval(0);
}

void WSCompositor::compose()
{
auto& wm = WSWindowManager::the();

auto dirty_rects = move(m_dirty_rects);

if (dirty_rects.size() == 0) {
// nothing dirtied since the last compose pass.
return;
}

dirty_rects.add(Rect::intersection(m_last_geometry_label_rect, WSScreen::the().rect()));
dirty_rects.add(Rect::intersection(m_last_cursor_rect, WSScreen::the().rect()));
dirty_rects.add(Rect::intersection(current_cursor_rect(), WSScreen::the().rect()));
Expand Down Expand Up @@ -161,11 +175,22 @@ void WSCompositor::invalidate(const Rect& a_rect)
if (rect.is_empty())
return;

m_dirty_rects.add(rect);

// 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.
if (!m_compose_timer.is_active()) {
#if defined(COMPOSITOR_DEBUG)
dbgprintf("Invalidated: %dx%d %dx%d\n", a_rect.x(), a_rect.y(), a_rect.width(), a_rect.height());
dbgprintf("Invalidated (starting immediate frame): %dx%d %dx%d\n", a_rect.x(), a_rect.y(), a_rect.width(), a_rect.height());
#endif
m_dirty_rects.add(rect);
m_compose_timer.start();
m_compose_timer.start();
m_immediate_compose_timer.start();
} else {
#if defined(COMPOSITOR_DEBUG)
dbgprintf("Invalidated (frame callback pending): %dx%d %dx%d\n", a_rect.x(), a_rect.y(), a_rect.width(), a_rect.height());
#endif
}
}

bool WSCompositor::set_wallpaper(const String& path, Function<void(bool)>&& callback)
Expand Down
1 change: 1 addition & 0 deletions Servers/WindowServer/WSCompositor.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class WSCompositor final : public CObject {
unsigned m_compose_count { 0 };
unsigned m_flush_count { 0 };
CTimer m_compose_timer;
CTimer m_immediate_compose_timer;
bool m_flash_flush { false };
bool m_buffers_are_flipped { false };

Expand Down
1 change: 1 addition & 0 deletions SharedGraphics/DisjointRectSet.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class DisjointRectSet {
void add(const Rect&);

bool is_empty() const { return m_rects.is_empty(); }
int size() const { return m_rects.size(); }

void clear() { m_rects.clear(); }
void clear_with_capacity() { m_rects.clear_with_capacity(); }
Expand Down

0 comments on commit 9b86eb9

Please sign in to comment.