Add client-side double buffering of window backing stores.

This prevents flicker and looks rather good. The main downside is that
resizing gets even more sluggish. That's the price we pay for now.
This commit is contained in:
Andreas Kling 2019-03-17 04:23:54 +01:00
parent 2ac4f54724
commit 4e451c1e92
12 changed files with 95 additions and 39 deletions

View file

@ -88,6 +88,7 @@ int main(int argc, char** argv)
make_shell(ptm_fd);
auto* window = new GWindow;
window->set_double_buffering_enabled(false);
window->set_should_exit_app_on_close(true);
Terminal terminal(ptm_fd);

View file

@ -39,7 +39,7 @@ public:
WSAPI_ServerMessage sync_request(const WSAPI_ClientMessage& request, WSAPI_ServerMessage::Type response_type);
pid_t server_pid() const { return s_server_pid; }
static pid_t server_pid() { return s_server_pid; }
private:
void wait_for_event();

View file

@ -3,6 +3,7 @@
#include "GEventLoop.h"
#include "GWidget.h"
#include <SharedGraphics/GraphicsBitmap.h>
#include <SharedGraphics/Painter.h>
#include <LibC/stdio.h>
#include <LibC/stdlib.h>
#include <LibC/unistd.h>
@ -166,33 +167,19 @@ void GWindow::event(GEvent& event)
return;
auto& paint_event = static_cast<GPaintEvent&>(event);
auto rect = paint_event.rect();
bool created_new_backing_store = !m_backing;
if (!m_backing) {
ASSERT(GEventLoop::main().server_pid());
ASSERT(!paint_event.window_size().is_empty());
Size new_backing_store_size = paint_event.window_size();
size_t size_in_bytes = new_backing_store_size.area() * sizeof(RGBA32);
auto shared_buffer = SharedBuffer::create(GEventLoop::main().server_pid(), size_in_bytes);
ASSERT(shared_buffer);
m_backing = GraphicsBitmap::create_with_shared_buffer(
m_has_alpha_channel ? GraphicsBitmap::Format::RGBA32 : GraphicsBitmap::Format::RGB32,
*shared_buffer,
new_backing_store_size);
}
bool created_new_backing_store = !m_back_bitmap;
if (!m_back_bitmap)
m_back_bitmap = create_backing_bitmap(paint_event.window_size());
if (rect.is_empty() || created_new_backing_store)
rect = m_main_widget->rect();
m_main_widget->event(*make<GPaintEvent>(rect));
if (created_new_backing_store) {
WSAPI_ClientMessage message;
message.type = WSAPI_ClientMessage::Type::SetWindowBackingStore;
message.window_id = m_window_id;
message.backing.bpp = 32;
message.backing.pitch = m_backing->pitch();
message.backing.shared_buffer_id = m_backing->shared_buffer_id();
message.backing.has_alpha_channel = m_backing->has_alpha_channel();
message.backing.size = m_backing->size();
GEventLoop::main().post_message_to_server(message);
}
if (m_double_buffering_enabled)
flip(rect);
else if (created_new_backing_store)
set_current_backing_bitmap(*m_back_bitmap, true);
if (m_window_id) {
WSAPI_ClientMessage message;
message.type = WSAPI_ClientMessage::Type::DidFinishPainting;
@ -232,8 +219,8 @@ void GWindow::event(GEvent& event)
if (event.type() == GEvent::Resize) {
auto new_size = static_cast<GResizeEvent&>(event).size();
if (m_backing && m_backing->size() != new_size)
m_backing = nullptr;
if (m_back_bitmap && m_back_bitmap->size() != new_size)
m_back_bitmap = nullptr;
m_pending_paint_event_rects.clear();
m_rect_when_windowless = { { }, new_size };
m_main_widget->set_relative_rect({ { }, new_size });
@ -328,6 +315,12 @@ void GWindow::set_has_alpha_channel(bool value)
m_has_alpha_channel = value;
}
void GWindow::set_double_buffering_enabled(bool value)
{
ASSERT(!m_window_id);
m_double_buffering_enabled = value;
}
void GWindow::set_opacity(float opacity)
{
m_opacity_when_windowless = opacity;
@ -354,3 +347,45 @@ void GWindow::set_hovered_widget(GWidget* widget)
if (m_hovered_widget)
GEventLoop::main().post_event(*m_hovered_widget, make<GEvent>(GEvent::Enter));
}
void GWindow::set_current_backing_bitmap(GraphicsBitmap& bitmap, bool flush_immediately)
{
WSAPI_ClientMessage message;
message.type = WSAPI_ClientMessage::Type::SetWindowBackingStore;
message.window_id = m_window_id;
message.backing.bpp = 32;
message.backing.pitch = bitmap.pitch();
message.backing.shared_buffer_id = bitmap.shared_buffer_id();
message.backing.has_alpha_channel = bitmap.has_alpha_channel();
message.backing.size = bitmap.size();
message.backing.flush_immediately = flush_immediately;
GEventLoop::main().sync_request(message, WSAPI_ServerMessage::Type::DidSetWindowBackingStore);
}
void GWindow::flip(const Rect& dirty_rect)
{
swap(m_front_bitmap, m_back_bitmap);
set_current_backing_bitmap(*m_front_bitmap);
if (!m_back_bitmap || m_back_bitmap->size() != m_front_bitmap->size()) {
m_back_bitmap = create_backing_bitmap(m_front_bitmap->size());
memcpy(m_back_bitmap->scanline(0), m_front_bitmap->scanline(0), m_front_bitmap->size().area() * sizeof(RGBA32));
return;
}
// Copy whatever was painted from the front to the back.
Painter painter(*m_back_bitmap);
painter.blit(dirty_rect.location(), *m_front_bitmap, dirty_rect);
}
Retained<GraphicsBitmap> GWindow::create_backing_bitmap(const Size& size)
{
ASSERT(GEventLoop::server_pid());
ASSERT(!size.is_empty());
size_t size_in_bytes = size.area() * sizeof(RGBA32);
auto shared_buffer = SharedBuffer::create(GEventLoop::server_pid(), size_in_bytes);
ASSERT(shared_buffer);
auto format = m_has_alpha_channel ? GraphicsBitmap::Format::RGBA32 : GraphicsBitmap::Format::RGB32;
return GraphicsBitmap::create_with_shared_buffer(format, *shared_buffer, size);
}

View file

@ -15,6 +15,7 @@ public:
static GWindow* from_window_id(int);
void set_double_buffering_enabled(bool);
void set_has_alpha_channel(bool);
void set_opacity(float);
@ -68,7 +69,8 @@ public:
const GWidget* hovered_widget() const { return m_hovered_widget.ptr(); }
void set_hovered_widget(GWidget*);
GraphicsBitmap* backing() { return m_backing.ptr(); }
GraphicsBitmap* front_bitmap() { return m_front_bitmap.ptr(); }
GraphicsBitmap* back_bitmap() { return m_back_bitmap.ptr(); }
Size size_increment() const { return m_size_increment; }
void set_size_increment(const Size& increment) { m_size_increment = increment; }
@ -78,7 +80,12 @@ public:
private:
virtual const char* class_name() const override { return "GWindow"; }
RetainPtr<GraphicsBitmap> m_backing;
Retained<GraphicsBitmap> create_backing_bitmap(const Size&);
void set_current_backing_bitmap(GraphicsBitmap&, bool flush_immediately = false);
void flip(const Rect& dirty_rect);
RetainPtr<GraphicsBitmap> m_front_bitmap;
RetainPtr<GraphicsBitmap> m_back_bitmap;
int m_window_id { 0 };
float m_opacity_when_windowless { 1.0f };
GWidget* m_main_widget { nullptr };
@ -93,5 +100,6 @@ private:
bool m_is_active { false };
bool m_should_exit_app_on_close { false };
bool m_has_alpha_channel { false };
bool m_double_buffering_enabled { true };
};

View file

@ -58,7 +58,7 @@ GraphicsBitmap::GraphicsBitmap(Format format, const Size& size, RGBA32* data)
{
}
RetainPtr<GraphicsBitmap> GraphicsBitmap::create_with_shared_buffer(Format format, Retained<SharedBuffer>&& shared_buffer, const Size& size)
Retained<GraphicsBitmap> GraphicsBitmap::create_with_shared_buffer(Format format, Retained<SharedBuffer>&& shared_buffer, const Size& size)
{
return adopt(*new GraphicsBitmap(format, move(shared_buffer), size));
}

View file

@ -15,7 +15,7 @@ public:
static Retained<GraphicsBitmap> create(Format, const Size&);
static Retained<GraphicsBitmap> create_wrapper(Format, const Size&, RGBA32*);
static RetainPtr<GraphicsBitmap> load_from_file(Format, const String& path, const Size&);
static RetainPtr<GraphicsBitmap> create_with_shared_buffer(Format, Retained<SharedBuffer>&&, const Size&);
static Retained<GraphicsBitmap> create_with_shared_buffer(Format, Retained<SharedBuffer>&&, const Size&);
~GraphicsBitmap();
RGBA32* scanline(int y);

View file

@ -28,7 +28,7 @@ Painter::Painter(GraphicsBitmap& bitmap)
#ifdef LIBGUI
Painter::Painter(GWidget& widget)
: m_window(widget.window())
, m_target(*m_window->backing())
, m_target(*m_window->back_bitmap())
{
m_state_stack.append(State());
state().font = &widget.font();

View file

@ -87,6 +87,7 @@ struct WSAPI_ServerMessage {
Greeting,
DidGetClipboardContents,
DidSetClipboardContents,
DidSetWindowBackingStore,
};
Type type { Invalid };
int window_id { -1 };
@ -191,6 +192,7 @@ struct WSAPI_ClientMessage {
size_t pitch;
int shared_buffer_id;
bool has_alpha_channel;
bool flush_immediately;
} backing;
struct {
int shared_buffer_id;

View file

@ -357,6 +357,7 @@ void WSClientConnection::handle_request(WSAPICreateWindowRequest& request)
window->set_opacity(request.opacity());
window->set_size_increment(request.size_increment());
window->set_base_size(request.base_size());
window->invalidate();
m_windows.set(window_id, move(window));
WSAPI_ServerMessage response;
response.type = WSAPI_ServerMessage::Type::DidCreateWindow;
@ -451,10 +452,16 @@ void WSClientConnection::handle_request(WSAPISetWindowBackingStoreRequest& reque
request.has_alpha_channel() ? GraphicsBitmap::Format::RGBA32 : GraphicsBitmap::Format::RGB32,
*shared_buffer,
request.size());
if (!backing_store)
return;
window.set_backing_store(move(backing_store));
window.invalidate();
if (request.flush_immediately())
window.invalidate();
WSAPI_ServerMessage response;
response.type = WSAPI_ServerMessage::Type::DidSetWindowBackingStore;
response.window_id = window_id;
response.backing.shared_buffer_id = request.shared_buffer_id();
post_message(response);
}
void WSClientConnection::handle_request(WSAPISetGlobalCursorTrackingRequest& request)

View file

@ -320,7 +320,7 @@ private:
class WSAPISetWindowBackingStoreRequest final : public WSAPIClientRequest {
public:
explicit WSAPISetWindowBackingStoreRequest(int client_id, int window_id, int shared_buffer_id, const Size& size, size_t bpp, size_t pitch, bool has_alpha_channel)
explicit WSAPISetWindowBackingStoreRequest(int client_id, int window_id, int shared_buffer_id, const Size& size, size_t bpp, size_t pitch, bool has_alpha_channel, bool flush_immediately)
: WSAPIClientRequest(WSMessage::APISetWindowBackingStoreRequest, client_id)
, m_client_id(client_id)
, m_window_id(window_id)
@ -329,6 +329,7 @@ public:
, m_bpp(bpp)
, m_pitch(pitch)
, m_has_alpha_channel(has_alpha_channel)
, m_flush_immediately(flush_immediately)
{
}
@ -339,6 +340,7 @@ public:
size_t bpp() const { return m_bpp; }
size_t pitch() const { return m_pitch; }
bool has_alpha_channel() const { return m_has_alpha_channel; }
bool flush_immediately() const { return m_flush_immediately; }
private:
int m_client_id { 0 };
@ -348,6 +350,7 @@ private:
size_t m_bpp;
size_t m_pitch;
bool m_has_alpha_channel;
bool m_flush_immediately;
};
class WSAPISetWindowRectRequest final : public WSAPIClientRequest {

View file

@ -321,7 +321,7 @@ void WSMessageLoop::on_receive_from_client(int client_id, const WSAPI_ClientMess
post_message(client, make<WSAPIGetWindowBackingStoreRequest>(client_id, message.window_id));
break;
case WSAPI_ClientMessage::Type::SetWindowBackingStore:
post_message(client, make<WSAPISetWindowBackingStoreRequest>(client_id, message.window_id, message.backing.shared_buffer_id, message.backing.size, message.backing.bpp, message.backing.pitch, message.backing.has_alpha_channel));
post_message(client, make<WSAPISetWindowBackingStoreRequest>(client_id, message.window_id, message.backing.shared_buffer_id, message.backing.size, message.backing.bpp, message.backing.pitch, message.backing.has_alpha_channel, message.backing.flush_immediately));
break;
case WSAPI_ClientMessage::Type::SetGlobalCursorTracking:
post_message(client, make<WSAPISetGlobalCursorTrackingRequest>(client_id, message.window_id, message.value));

View file

@ -916,8 +916,6 @@ void WSWindowManager::compose()
for_each_visible_window_from_back_to_front([&] (WSWindow& window) {
RetainPtr<GraphicsBitmap> backing_store = window.backing_store();
if (!backing_store)
return IterationDecision::Continue;
if (!any_dirty_rect_intersects_window(window))
return IterationDecision::Continue;
PainterStateSaver saver(*m_back_painter);
@ -928,6 +926,8 @@ void WSWindowManager::compose()
PainterStateSaver saver(*m_back_painter);
m_back_painter->set_clip_rect(dirty_rect);
paint_window_frame(window);
if (!backing_store)
continue;
Rect dirty_rect_in_window_coordinates = Rect::intersection(dirty_rect, window.rect());
if (dirty_rect_in_window_coordinates.is_empty())
continue;