From 3d74d88806d09f3a899be5d8f1df8ec20ed9a14d Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Mon, 2 Jan 2017 19:49:52 +0100 Subject: [PATCH] Render on a temporary surface when needed Some platforms draw directly to the screen, which means that updates will flicker if we draw multiple layers. Prevent this by first composing the update on a hidden surface. --- tests/fbperf.cxx | 39 ++++++++++++++++++--- vncviewer/DesktopWindow.cxx | 54 ++++++++++++++++++++---------- vncviewer/DesktopWindow.h | 2 ++ vncviewer/Surface.h | 1 + vncviewer/Surface_OSX.cxx | 67 +++++++++++++++++++++++++++++-------- vncviewer/Surface_Win32.cxx | 19 +++++++++++ vncviewer/Surface_X11.cxx | 6 ++++ vncviewer/Viewport.cxx | 13 +++++++ vncviewer/Viewport.h | 3 ++ 9 files changed, 167 insertions(+), 37 deletions(-) diff --git a/tests/fbperf.cxx b/tests/fbperf.cxx index 8c71d966..7f1e2c1d 100644 --- a/tests/fbperf.cxx +++ b/tests/fbperf.cxx @@ -72,6 +72,7 @@ public: protected: Surface* overlay; + Surface* offscreen; }; TestWindow::TestWindow() : @@ -202,7 +203,7 @@ void PartialTestWindow::changefb() } OverlayTestWindow::OverlayTestWindow() : - overlay(NULL) + overlay(NULL), offscreen(NULL) { } @@ -212,12 +213,21 @@ void OverlayTestWindow::start(int width, int height) overlay = new Surface(400, 200); overlay->clear(0xff, 0x80, 0x00, 0xcc); + + // X11 needs an off screen buffer for compositing to avoid flicker +#if !defined(WIN32) && !defined(__APPLE__) + offscreen = new Surface(w(), h()); +#else + offscreen = NULL; +#endif } void OverlayTestWindow::stop() { PartialTestWindow::stop(); + delete offscreen; + offscreen = NULL; delete overlay; overlay = NULL; } @@ -227,13 +237,15 @@ void OverlayTestWindow::draw() int ox, oy, ow, oh; int X, Y, W, H; + // We cannot update the damage region from inside the draw function, + // so delegate this to an idle function + Fl::add_idle(timer, this); + // Check what actually needs updating fl_clip_box(0, 0, w(), h(), X, Y, W, H); if ((W == 0) || (H == 0)) return; - PartialTestWindow::draw(); - // We might get a redraw before we are fully ready if (!overlay) return; @@ -243,16 +255,33 @@ void OverlayTestWindow::draw() fl_push_no_clip(); fl_push_clip(X, Y, W, H); + if (offscreen) + fb->draw(offscreen, X, Y, X, Y, W, H); + else + fb->draw(X, Y, X, Y, W, H); + + pixels += W*H; + frames++; + ox = (w() - overlay->width()) / 2; oy = h() / 4 - overlay->height() / 2; ow = overlay->width(); oh = overlay->height(); fl_clip_box(ox, oy, ow, oh, X, Y, W, H); - if ((W != 0) && (H != 0)) - overlay->draw(X - ox, Y - oy, X, Y, W, H); + if ((W != 0) && (H != 0)) { + if (offscreen) + overlay->draw(offscreen, X - ox, Y - oy, X, Y, W, H); + else + overlay->draw(X - ox, Y - oy, X, Y, W, H); + } fl_pop_clip(); fl_pop_clip(); + + if (offscreen) { + fl_clip_box(0, 0, w(), h(), X, Y, W, H); + offscreen->draw(X, Y, X, Y, W, H); + } } static void dosubtest(TestWindow* win, int width, int height, diff --git a/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx index 707628ee..3da25054 100644 --- a/vncviewer/DesktopWindow.cxx +++ b/vncviewer/DesktopWindow.cxx @@ -34,6 +34,7 @@ #include "parameters.h" #include "vncviewer.h" #include "CConn.h" +#include "Surface.h" #include "Viewport.h" #include @@ -59,7 +60,7 @@ static rfb::LogWriter vlog("DesktopWindow"); DesktopWindow::DesktopWindow(int w, int h, const char *name, const rfb::PixelFormat& serverPF, CConn* cc_) - : Fl_Window(w, h), cc(cc_), firstUpdate(true), + : Fl_Window(w, h), cc(cc_), offscreen(NULL), firstUpdate(true), delayedFullscreen(false), delayedDesktopSize(false) { Fl_Group* group; @@ -183,6 +184,8 @@ DesktopWindow::~DesktopWindow() OptionsDialog::removeCallback(handleOptions); + delete offscreen; + // FLTK automatically deletes all child widgets, so we shouldn't touch // them ourselves here } @@ -263,7 +266,19 @@ void DesktopWindow::draw() { bool redraw; - int W, H; + int X, Y, W, H; + + // X11 needs an off screen buffer for compositing to avoid flicker +#if !defined(WIN32) && !defined(__APPLE__) + + // Adjust offscreen surface dimensions + if ((offscreen == NULL) || + (offscreen->width() != w()) || (offscreen->height() != h())) { + delete offscreen; + offscreen = new Surface(w(), h()); + } + +#endif // Active area inside scrollbars W = w() - (vscroll->visible() ? vscroll->w() : 0); @@ -274,30 +289,33 @@ void DesktopWindow::draw() // Redraw background only on full redraws if (redraw) { - if (viewport->h() < h()) { - fl_rectf(0, 0, W, viewport->y(), 40, 40, 40); - fl_rectf(0, viewport->y() + viewport->h(), W, - h() - (viewport->y() + viewport->h()), - 40, 40, 40); - } - if (viewport->w() < w()) { - fl_rectf(0, 0, viewport->x(), H, 40, 40, 40); - fl_rectf(viewport->x() + viewport->w(), 0, - w() - (viewport->x() + viewport->w()), - H, 40, 40, 40); - } + if (offscreen) + offscreen->clear(40, 40, 40); + else + fl_rectf(0, 0, W, H, 40, 40, 40); } // Make sure the viewport isn't trampling on the scrollbars fl_push_clip(0, 0, W, H); - if (redraw) - draw_child(*viewport); - else - update_child(*viewport); + if (offscreen) { + viewport->draw(offscreen); + viewport->clear_damage(); + } else { + if (redraw) + draw_child(*viewport); + else + update_child(*viewport); + } fl_pop_clip(); + // Flush offscreen surface to screen + if (offscreen) { + fl_clip_box(0, 0, W, H, X, Y, W, H); + offscreen->draw(X, Y, X, Y, W, H); + } + // Finally the scrollbars if (redraw) { diff --git a/vncviewer/DesktopWindow.h b/vncviewer/DesktopWindow.h index d0e2eae4..073be661 100644 --- a/vncviewer/DesktopWindow.h +++ b/vncviewer/DesktopWindow.h @@ -30,6 +30,7 @@ namespace rfb { class ModifiablePixelBuffer; } class CConn; +class Surface; class Viewport; class Fl_Scrollbar; @@ -95,6 +96,7 @@ private: CConn* cc; Fl_Scrollbar *hscroll, *vscroll; Viewport *viewport; + Surface *offscreen; bool firstUpdate; bool delayedFullscreen; diff --git a/vncviewer/Surface.h b/vncviewer/Surface.h index 02917e31..9b1788a5 100644 --- a/vncviewer/Surface.h +++ b/vncviewer/Surface.h @@ -42,6 +42,7 @@ public: void clear(unsigned char r, unsigned char g, unsigned char b, unsigned char a=255); void draw(int src_x, int src_y, int x, int y, int w, int h); + void draw(Surface* dst, int src_x, int src_y, int x, int y, int w, int h); protected: void alloc(); diff --git a/vncviewer/Surface_OSX.cxx b/vncviewer/Surface_OSX.cxx index c51e47f5..dbf0c418 100644 --- a/vncviewer/Surface_OSX.cxx +++ b/vncviewer/Surface_OSX.cxx @@ -28,6 +28,32 @@ #include "Surface.h" +static void render(CGContextRef gc, CGImageRef image, + int src_x, int src_y, int src_w, int src_h, + int x, int y, int w, int h) +{ + CGRect rect; + + CGContextSaveGState(gc); + + // We have to use clipping to partially display an image + rect.origin.x = x; + rect.origin.y = y; + rect.size.width = w; + rect.size.height = h; + + CGContextClipToRect(gc, rect); + + rect.origin.x = x - src_x; + rect.origin.y = y - src_y; + rect.size.width = src_w; + rect.size.height = src_h; + + CGContextDrawImage(gc, rect, image); + + CGContextRestoreGState(gc); +} + void Surface::clear(unsigned char r, unsigned char g, unsigned char b, unsigned char a) { unsigned char* out; @@ -50,8 +76,6 @@ void Surface::clear(unsigned char r, unsigned char g, unsigned char b, unsigned void Surface::draw(int src_x, int src_y, int x, int y, int w, int h) { - CGRect rect; - CGContextSaveGState(fl_gc); // Reset the transformation matrix back to the default identity @@ -62,22 +86,37 @@ void Surface::draw(int src_x, int src_y, int x, int y, int w, int h) src_y = height() - (src_y + h); y = Fl_Window::current()->h() - (y + h); - // We have to use clipping to partially display an image - rect.origin.x = x; - rect.origin.y = y; - rect.size.width = w; - rect.size.height = h; + render(fl_gc, image, src_x, src_y, width(), height(), x, y, w, h); - CGContextClipToRect(fl_gc, rect); + CGContextRestoreGState(fl_gc); +} - rect.origin.x = x - src_x; - rect.origin.y = y - src_y; - rect.size.width = width(); - rect.size.height = height(); +void Surface::draw(Surface* dst, int src_x, int src_y, int x, int y, int w, int h) +{ + CGColorSpaceRef lut; + CGContextRef bitmap; - CGContextDrawImage(fl_gc, rect, image); + lut = CGDisplayCopyColorSpace(kCGDirectMainDisplay); + if (!lut) { + lut = CGColorSpaceCreateDeviceRGB(); + if (!lut) + throw rdr::Exception("CGColorSpaceCreateDeviceRGB"); + } - CGContextRestoreGState(fl_gc); + bitmap = CGBitmapContextCreate(dst->data, dst->width(), + dst->height(), 8, dst->width()*4, lut, + kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little); + CGColorSpaceRelease(lut); + if (!bitmap) + throw rdr::Exception("CGBitmapContextCreate"); + + // macOS Coordinates are from bottom left, not top left + src_y = height() - (src_y + h); + y = dst->height() - (y + h); + + render(bitmap, image, src_x, src_y, width(), height(), x, y, w, h); + + CGContextRelease(bitmap); } void Surface::alloc() diff --git a/vncviewer/Surface_Win32.cxx b/vncviewer/Surface_Win32.cxx index 5a9a6546..5eea2d1d 100644 --- a/vncviewer/Surface_Win32.cxx +++ b/vncviewer/Surface_Win32.cxx @@ -70,6 +70,25 @@ void Surface::draw(int src_x, int src_y, int x, int y, int w, int h) DeleteDC(dc); } +void Surface::draw(Surface* dst, int src_x, int src_y, int x, int y, int w, int h) +{ + HDC origdc, dstdc; + + dstdc = CreateCompatibleDC(NULL); + if (!dstdc) + throw rdr::SystemException("CreateCompatibleDC", GetLastError()); + + if (!SelectObject(dstdc, dst->bitmap)) + throw rdr::SystemException("SelectObject", GetLastError()); + + origdc = fl_gc; + fl_gc = dstdc; + draw(src_x, src_y, x, y, w, h); + fl_gc = origdc; + + DeleteDC(dstdc); +} + void Surface::alloc() { BITMAPINFOHEADER bih; diff --git a/vncviewer/Surface_X11.cxx b/vncviewer/Surface_X11.cxx index c7e3778e..e7616143 100644 --- a/vncviewer/Surface_X11.cxx +++ b/vncviewer/Surface_X11.cxx @@ -48,6 +48,12 @@ void Surface::draw(int src_x, int src_y, int x, int y, int w, int h) XRenderFreePicture(fl_display, winPict); } +void Surface::draw(Surface* dst, int src_x, int src_y, int x, int y, int w, int h) +{ + XRenderComposite(fl_display, PictOpSrc, picture, None, dst->picture, + src_x, src_y, 0, 0, x, y, w, h); +} + void Surface::alloc() { XRenderPictFormat* format; diff --git a/vncviewer/Viewport.cxx b/vncviewer/Viewport.cxx index 7b3487c4..b5c516fa 100644 --- a/vncviewer/Viewport.cxx +++ b/vncviewer/Viewport.cxx @@ -244,6 +244,19 @@ void Viewport::setCursor(int width, int height, const Point& hotspot, } +void Viewport::draw(Surface* dst) +{ + int X, Y, W, H; + + // Check what actually needs updating + fl_clip_box(x(), y(), w(), h(), X, Y, W, H); + if ((W == 0) || (H == 0)) + return; + + frameBuffer->draw(dst, X - x(), Y - y(), X, Y, W, H); +} + + void Viewport::draw() { int X, Y, W, H; diff --git a/vncviewer/Viewport.h b/vncviewer/Viewport.h index ac1ec338..0967fcbb 100644 --- a/vncviewer/Viewport.h +++ b/vncviewer/Viewport.h @@ -29,6 +29,7 @@ class Fl_RGB_Image; class CConn; class PlatformPixelBuffer; +class Surface; class Viewport : public Fl_Widget { public: @@ -46,6 +47,8 @@ public: void setCursor(int width, int height, const rfb::Point& hotspot, void* data, void* mask); + void draw(Surface* dst); + // Fl_Widget callback methods void draw(); -- 2.39.5