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.
diff --git a/tests/fbperf.cxx b/tests/fbperf.cxx
index 8c71d96..7f1e2c1 100644
--- a/tests/fbperf.cxx
+++ b/tests/fbperf.cxx
@@ -72,6 +72,7 @@
protected:
Surface* overlay;
+ Surface* offscreen;
};
TestWindow::TestWindow() :
@@ -202,7 +203,7 @@
}
OverlayTestWindow::OverlayTestWindow() :
- overlay(NULL)
+ overlay(NULL), offscreen(NULL)
{
}
@@ -212,12 +213,21 @@
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 @@
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 @@
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 707628e..3da2505 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 <FL/Fl.H>
@@ -59,7 +60,7 @@
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 @@
OptionsDialog::removeCallback(handleOptions);
+ delete offscreen;
+
// FLTK automatically deletes all child widgets, so we shouldn't touch
// them ourselves here
}
@@ -263,7 +266,19 @@
{
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 @@
// 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 d0e2eae..073be66 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 @@
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 02917e3..9b1788a 100644
--- a/vncviewer/Surface.h
+++ b/vncviewer/Surface.h
@@ -42,6 +42,7 @@
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 c51e47f..dbf0c41 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::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,24 +86,39 @@
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;
-
- CGContextClipToRect(fl_gc, rect);
-
- rect.origin.x = x - src_x;
- rect.origin.y = y - src_y;
- rect.size.width = width();
- rect.size.height = height();
-
- CGContextDrawImage(fl_gc, rect, image);
+ render(fl_gc, image, src_x, src_y, width(), height(), x, y, w, h);
CGContextRestoreGState(fl_gc);
}
+void Surface::draw(Surface* dst, int src_x, int src_y, int x, int y, int w, int h)
+{
+ CGColorSpaceRef lut;
+ CGContextRef bitmap;
+
+ lut = CGDisplayCopyColorSpace(kCGDirectMainDisplay);
+ if (!lut) {
+ lut = CGColorSpaceCreateDeviceRGB();
+ if (!lut)
+ throw rdr::Exception("CGColorSpaceCreateDeviceRGB");
+ }
+
+ 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()
{
CGColorSpaceRef lut;
diff --git a/vncviewer/Surface_Win32.cxx b/vncviewer/Surface_Win32.cxx
index 5a9a654..5eea2d1 100644
--- a/vncviewer/Surface_Win32.cxx
+++ b/vncviewer/Surface_Win32.cxx
@@ -70,6 +70,25 @@
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 c7e3778..e761614 100644
--- a/vncviewer/Surface_X11.cxx
+++ b/vncviewer/Surface_X11.cxx
@@ -48,6 +48,12 @@
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 7b3487c..b5c516f 100644
--- a/vncviewer/Viewport.cxx
+++ b/vncviewer/Viewport.cxx
@@ -244,6 +244,19 @@
}
+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 ac1ec33..0967fcb 100644
--- a/vncviewer/Viewport.h
+++ b/vncviewer/Viewport.h
@@ -29,6 +29,7 @@
class CConn;
class PlatformPixelBuffer;
+class Surface;
class Viewport : public Fl_Widget {
public:
@@ -46,6 +47,8 @@
void setCursor(int width, int height, const rfb::Point& hotspot,
void* data, void* mask);
+ void draw(Surface* dst);
+
// Fl_Widget callback methods
void draw();