Add test to measure framebuffer performance
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index bfd69dc..164660a 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -16,3 +16,20 @@
 
 add_executable(hostport hostport.cxx)
 target_link_libraries(hostport rfb)
+
+set(FBPERF_SOURCES
+  fbperf.cxx
+  ../vncviewer/FLTKPixelBuffer.cxx
+  ../vncviewer/PlatformPixelBuffer.cxx)
+if(WIN32)
+  set(FBPERF_SOURCES ${FBPERF_SOURCES} ../vncviewer/Win32PixelBuffer.cxx)
+elseif(APPLE)
+  set(FBPERF_SOURCES ${FBPERF_SOURCES} ../vncviewer/OSXPixelBuffer.cxx)
+else()
+  set(FBPERF_SOURCES ${FBPERF_SOURCES} ../vncviewer/X11PixelBuffer.cxx)
+endif()
+add_executable(fbperf ${FBPERF_SOURCES})
+target_link_libraries(fbperf test_util rfb ${FLTK_LIBRARIES} ${GETTEXT_LIBRARIES})
+if(APPLE)
+  target_link_libraries(fbperf "-framework Cocoa" "-framework Carbon")
+endif()
diff --git a/tests/fbperf.cxx b/tests/fbperf.cxx
new file mode 100644
index 0000000..53334a6
--- /dev/null
+++ b/tests/fbperf.cxx
@@ -0,0 +1,225 @@
+/* Copyright 2016 Pierre Ossman <ossman@cendio.se> for Cendio AB
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+#include <sys/time.h>
+
+#include <FL/Fl.H>
+#include <FL/Fl_Window.H>
+#include <FL/fl_draw.H>
+
+#include <rdr/Exception.h>
+#include <rfb/util.h>
+
+#include "../vncviewer/PlatformPixelBuffer.h"
+#include "../vncviewer/FLTKPixelBuffer.h"
+
+#if defined(WIN32)
+#include "../vncviewer/Win32PixelBuffer.h"
+#elif defined(__APPLE__)
+#include "../vncviewer/OSXPixelBuffer.h"
+#else
+#include "../vncviewer/X11PixelBuffer.h"
+#endif
+
+#include "util.h"
+
+class TestWindow: public Fl_Window {
+public:
+  TestWindow();
+  ~TestWindow();
+
+  virtual void draw();
+
+protected:
+  virtual void flush();
+
+  void update();
+  virtual void changefb();
+
+  static void timer(void* data);
+
+public:
+  unsigned long long pixels, frames;
+  double time;
+
+protected:
+  PlatformPixelBuffer* fb;
+};
+
+class PartialTestWindow: public TestWindow {
+protected:
+  virtual void changefb();
+};
+
+TestWindow::TestWindow() :
+  Fl_Window(1024, 768, "Framebuffer Performance Test")
+{
+  rdr::U32 pixel;
+
+  pixels = 0;
+  frames = 0;
+  time = 0;
+
+  try {
+#if defined(WIN32)
+    fb = new Win32PixelBuffer(w(), h());
+#elif defined(__APPLE__)
+    fb = new OSXPixelBuffer(w(), h());
+#else
+    fb = new X11PixelBuffer(w(), h());
+#endif
+  } catch (rdr::Exception& e) {
+    fb = new FLTKPixelBuffer(w(), h());
+  }
+
+  pixel = 0;
+  fb->fillRect(fb->getRect(), &pixel);
+}
+
+TestWindow::~TestWindow()
+{
+  delete fb;
+
+  Fl::remove_idle(timer, this);
+}
+
+void TestWindow::draw()
+{
+  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;
+
+  fb->draw(X, Y, X, Y, W, H);
+
+  pixels += W*H;
+  frames++;
+}
+
+void TestWindow::flush()
+{
+  startTimeCounter();
+  Fl_Window::flush();
+#if !defined(WIN32) && !defined(__APPLE__)
+  // Make sure we measure any work we queue up
+  XSync(fl_display, False);
+#endif
+  endTimeCounter();
+
+  time += getTimeCounter();
+}
+
+void TestWindow::update()
+{
+  rfb::Rect r;
+
+  startTimeCounter();
+
+  while (fb->isRendering())
+    Fl::wait();
+
+  changefb();
+
+  r = fb->getDamage();
+  damage(FL_DAMAGE_USER1, r.tl.x, r.tl.y, r.width(), r.height());
+
+#if !defined(WIN32) && !defined(__APPLE__)
+  // Make sure we measure any work we queue up
+  XSync(fl_display, False);
+#endif
+
+  endTimeCounter();
+
+  time += getTimeCounter();
+}
+
+void TestWindow::changefb()
+{
+  rdr::U32 pixel;
+
+  pixel = rand();
+  fb->fillRect(fb->getRect(), &pixel);
+}
+
+void TestWindow::timer(void* data)
+{
+  TestWindow* self;
+
+  Fl::remove_idle(timer, data);
+
+  self = (TestWindow*)data;
+  self->update();
+}
+
+void PartialTestWindow::changefb()
+{
+  rfb::Rect r;
+  rdr::U32 pixel;
+
+  r = fb->getRect();
+  r.tl.x += w() / 4;
+  r.tl.y += h() / 4;
+  r.br.x -= w() / 4;
+  r.br.y -= h() / 4;
+
+  pixel = rand();
+  fb->fillRect(r, &pixel);
+}
+
+static void dotest(TestWindow* win)
+{
+  struct timeval start;
+  char s[1024];
+
+  win->show();
+
+  gettimeofday(&start, NULL);
+  while (rfb::msSince(&start) < 10000)
+    Fl::wait();
+
+  fprintf(stderr, "Rendering time: %g ms/frame\n",
+          win->time * 1000.0 / win->frames);
+  rfb::siPrefix(win->pixels / win->time,
+                "pixels/s", s, sizeof(s));
+  fprintf(stderr, "Rendering rate: %s\n", s);
+}
+
+int main(int argc, char** argv)
+{
+  TestWindow* win;
+
+  fprintf(stderr, "Full window update:\n\n");
+  win = new TestWindow();
+  dotest(win);
+  delete win;
+  fprintf(stderr, "\n");
+
+  fprintf(stderr, "Partial window update:\n\n");
+  win = new PartialTestWindow();
+  dotest(win);
+  delete win;
+  fprintf(stderr, "\n");
+
+  return 0;
+}
diff --git a/tests/util.cxx b/tests/util.cxx
index 4683d35..17a8369 100644
--- a/tests/util.cxx
+++ b/tests/util.cxx
@@ -24,6 +24,7 @@
 #include <windows.h>
 #else
 #include <sys/resource.h>
+#include <sys/time.h>
 #endif
 
 #include "util.h"
@@ -132,3 +133,46 @@
 
   return sysSeconds + userSeconds;
 }
+
+#ifdef WIN32
+static LARGE_INTEGER timeStart, timeEnd;
+#else
+static struct timeval timeStart, timeEnd;
+#endif
+
+void startTimeCounter(void)
+{
+#ifdef WIN32
+  QueryPerformanceCounter(&timeStart);
+#else
+  gettimeofday(&timeStart, NULL);
+#endif
+}
+
+void endTimeCounter(void)
+{
+#ifdef WIN32
+  QueryPerformanceCounter(&timeEnd);
+#else
+  gettimeofday(&timeEnd, NULL);
+#endif
+}
+
+double getTimeCounter(void)
+{
+  double time;
+
+#ifdef WIN32
+  LARGE_INTEGER freq;
+
+  QueryPerformanceFrequency(&freq);
+
+  time = timeEnd.QuadPart - timeStart.QuadPart;
+  time = time / freq.QuadPart;
+#else
+  time = (double)timeEnd.tv_sec - timeStart.tv_sec;
+  time += (double)(timeEnd.tv_usec - timeStart.tv_usec) / 1000000.0;
+#endif
+
+  return time;
+}
diff --git a/tests/util.h b/tests/util.h
index 16f2ba2..2b8ab4a 100644
--- a/tests/util.h
+++ b/tests/util.h
@@ -34,4 +34,9 @@
 
 double getCpuCounter(cpucounter_t c);
 
+void startTimeCounter(void);
+void endTimeCounter(void);
+
+double getTimeCounter(void);
+
 #endif