diff --git a/modules/gfx/pymod/export_scene.cc b/modules/gfx/pymod/export_scene.cc
index f67e90ccef4820c3cb08f515fe8d8a6d8d155f07..746c958f7c8ee041252b79687e2cfb0c4a029342 100644
--- a/modules/gfx/pymod/export_scene.cc
+++ b/modules/gfx/pymod/export_scene.cc
@@ -108,8 +108,6 @@ void export_Scene()
   void (Scene::*remove2)(const String&) = &Scene::Remove;
   void (Scene::*center_on1)(const String&) = &Scene::CenterOn;
   void (Scene::*center_on2)(const GfxObjP&) = &Scene::CenterOn;
-  bool (Scene::*start_offscreen_mode1)(unsigned int, unsigned int) = &Scene::StartOffscreenMode;  
-  bool (Scene::*start_offscreen_mode2)(unsigned int, unsigned int, int) = &Scene::StartOffscreenMode;  
   class_<Viewport>("Viewport", init<>())
     .def_readwrite("x", &Viewport::x)
     .def_readwrite("y", &Viewport::y)
@@ -249,9 +247,6 @@ void export_Scene()
     .add_property("ao_quality",&Scene::GetAmbientOcclusionQuality,&Scene::SetAmbientOcclusionQuality)
     .add_property("ao_size",&Scene::GetAmbientOcclusionSize,&Scene::SetAmbientOcclusionSize)
     .def("AttachObserver",&Scene::AttachObserver)
-    .def("StartOffscreenMode",start_offscreen_mode1)
-    .def("StartOffscreenMode",start_offscreen_mode2)
-    .def("StopOffscreenMode",&Scene::StopOffscreenMode)
     .def("SetShadingMode",&Scene::SetShadingMode)
     .def("SetBeacon",&Scene::SetBeacon)
     .add_property("root_node", &Scene::GetRootNode)
diff --git a/modules/gfx/src/gfx_object.cc b/modules/gfx/src/gfx_object.cc
index b265db7315ecb6010a519c90df68d8b508f63988..97e235f418f2e50dee56a33d55f285919d138175 100644
--- a/modules/gfx/src/gfx_object.cc
+++ b/modules/gfx/src/gfx_object.cc
@@ -151,13 +151,8 @@ void GfxObj::RenderGL(RenderPass pass)
     glMatrixMode(GL_MODELVIEW);
     glPushMatrix();
     glMultMatrix(transform_.GetTransposedMatrix().Data());
-    if(Scene::Instance().InOffscreenMode()) {
-      LOG_TRACE("applying material");
-      mat_.RenderGL();
-    } else {
-      LOG_TRACE("applying material display list");
-      glCallList(mat_dlist_);
-    }
+    LOG_TRACE("applying material display list");
+    glCallList(mat_dlist_);
     LOG_TRACE("calling custom render gl pass " << pass);
 
     /*
diff --git a/modules/gfx/src/glwin_base.hh b/modules/gfx/src/glwin_base.hh
index 7486540d5a8e8807ec4cacfd63366fd50a1d0372..77d30ee76ea8cedb30a80d615b74e968439b65a8 100644
--- a/modules/gfx/src/glwin_base.hh
+++ b/modules/gfx/src/glwin_base.hh
@@ -26,6 +26,7 @@
 */
 
 #include <ost/gfx/module_config.hh>
+#include <ost/message.hh>
 
 namespace ost { namespace gfx {
 
@@ -42,6 +43,25 @@ public:
   virtual bool HasStereo() const = 0;
 
   virtual bool HasMultisample() const = 0;
+
+  // The GLWin is supposed to setup the OpenGL resources. To avoid implementing
+  // our own offscreen buffers, image export from OpenGL also happens here
+  virtual void Export(const String& fname, unsigned int width,
+                      unsigned int height, bool transparent) {
+  	throw ost::Error("Image export not implemented for this GLWin");
+  }
+
+  virtual void Export(const String& fname, unsigned int width,
+                      unsigned int height, int max_samples, 
+                      bool transparent) {
+  	throw ost::Error("Image export with multi-sampling not implemented for "
+  		             "this GLWin");
+  }
+
+  virtual void Export(const String& fname, bool transparent) {
+  	throw ost::Error("Image export not implemented for this GLWin");
+  }
+
 };
 
 }}
diff --git a/modules/gfx/src/scene.cc b/modules/gfx/src/scene.cc
index 4f3cf2a1c7df2f0a05f8ac65bf51756e8efc6e65..1ce2a44ebfa553a035c5cb285fa9f97b5c493409 100644
--- a/modules/gfx/src/scene.cc
+++ b/modules/gfx/src/scene.cc
@@ -52,7 +52,6 @@
 #include "entity.hh"
 #include "exporter.hh"
 #include "povray.hh"
-#include "offscreen_buffer.hh"
 
 #if OST_SHADER_SUPPORT_ENABLED
 # include "shader.hh"
@@ -117,9 +116,6 @@ Scene::Scene():
   auto_autoslab_(true),
   do_autoslab_(true),
   autoslab_mode_(0),
-  offscreen_flag_(false),
-  main_offscreen_buffer_(0),
-  old_vp_(),
   def_shading_mode_("default"),
   selection_mode_(1),
   test_flag_(false),
@@ -592,7 +588,7 @@ void Scene::InitGL(bool full)
 
 void Scene::RequestRedraw()
 {
-  if (win_ && !offscreen_flag_) {
+  if (win_) {
     win_->DoRefresh();
   }
 }
@@ -602,19 +598,6 @@ void Scene::StatusMessage(const String& s)
   if(win_) win_->StatusMessage(s);
 }
 
-void Scene::SetBackground(const Color& c)
-{
-  background_=c;
-  bg_mode_=0;
-  if(gl_init_) {
-    this->ActivateGLContext();
-    //glClearColor(c.Red(),c.Green(),c.Blue(),c.Alpha());
-    glClearColor(c.Red(),c.Green(),c.Blue(),0.0);
-    SetFogColor(c);
-    RequestRedraw();
-  }
-}
-
 namespace {
   void c2d(const Color& c, unsigned char* d) {
     d[0]=static_cast<unsigned char>(c.GetRed()*255.0);
@@ -655,6 +638,19 @@ void Scene::set_bg()
   }
 }
 
+void Scene::SetBackground(const Color& c)
+{
+
+  background_=c;
+  bg_mode_=0;
+  if(gl_init_) {
+    this->ActivateGLContext();
+    glClearColor(c.Red(),c.Green(),c.Blue(),0.0);
+    SetFogColor(c);
+    RequestRedraw();
+  }
+}
+
 void Scene::SetBackground(const Gradient& g)
 {
   bg_grad_=g;
@@ -1629,178 +1625,33 @@ uint Scene::GetSelectionMode() const
   return selection_mode_;
 }
 
-bool Scene::StartOffscreenMode(unsigned int width, unsigned int height) {
-  return StartOffscreenMode(width,height,2);
-}
-
-bool Scene::StartOffscreenMode(unsigned int width, unsigned int height, int max_samples)
-{
-  LOG_DEBUG("Scene: starting offscreen rendering mode " << width << "x" << height);
-  if(main_offscreen_buffer_) return false;
-  OffscreenBufferFormat obf;
-  if(max_samples>0) {
-    obf.multisample=true;
-    obf.samples=max_samples;
-  } else {
-    obf.multisample=false;
-  }
-  main_offscreen_buffer_ = new OffscreenBuffer(width,height,obf,true);
-
-  if(!main_offscreen_buffer_->IsValid()) {
-    LOG_ERROR("Scene: error during offscreen buffer creation");
-    delete main_offscreen_buffer_;   
-    main_offscreen_buffer_=0;
-    return false;
-  }
-  old_vp_[0]=vp_width_;
-  old_vp_[1]=vp_height_;
-  this->ActivateGLContext();
-  offscreen_flag_=true;
-  root_node_->ContextSwitch();
-
-#if OST_SHADER_SUPPORT_ENABLED
-  String shader_name = !def_shading_mode_.empty() ? def_shading_mode_ : Shader::Instance().GetCurrentName();
-#endif
-
-  LOG_DEBUG("Scene: initializing GL");
-  if(gl_init_) {
-    this->InitGL(false);
-  } else {
-    this->InitGL(true);
-  }
-  LOG_DEBUG("Scene: setting viewport");
-  Resize(width,height);
-  LOG_DEBUG("Scene: updating fog settings");
-  update_fog();
-  glDrawBuffer(GL_FRONT);
-#if OST_SHADER_SUPPORT_ENABLED
-  LOG_DEBUG("Scene: activating shader " << shader_name);
-  Shader::Instance().Activate(shader_name);
-#endif
-  return true;
-}
-
-void Scene::StopOffscreenMode()
+void Scene::Export(const String& fname, bool transparent)
 {
-  if(main_offscreen_buffer_) {
-    delete main_offscreen_buffer_;
-    main_offscreen_buffer_=0;
-    this->ActivateGLContext();
-    Scene::Instance().SetViewport(old_vp_[0],old_vp_[1]);
-    offscreen_flag_=false;
-    root_node_->ContextSwitch();
-    glDrawBuffer(GL_BACK);
-    update_fog();
+  if(!win_) {
+    LOG_ERROR("Scene: Image export requires registered GLWin");
   }
+  
+  win_->Export(fname, transparent);
 }
 
 void Scene::Export(const String& fname, unsigned int width,
                    unsigned int height, bool transparent)
 {
-  Export(fname,width,height,0,transparent);
-}
-
-void Scene::Export(const String& fname, unsigned int width,
-                   unsigned int height, int max_samples, bool transparent)
-{
-  int d_index=fname.rfind('.');
-  if (d_index==-1) {
-    LOG_ERROR("Scene: no file extension specified");
-    return;
-  }
-  String ext = fname.substr(d_index+1);
-  if(ext!="png") {
-    LOG_ERROR("Scene::Export: unknown file format (" << ext << ")");
-    return;
-  }
-
-  bool of_flag = (main_offscreen_buffer_==0);
-
-  // only switch if offscreen mode is not active
-  if(of_flag) {
-    if(max_samples<0) {
-      int msamples=0;
-#if OST_SHADER_SUPPORT_ENABLED
-      if(OST_GL_VERSION_2_0) {
-        glGetIntegerv(GL_SAMPLES, &msamples);
-      }
-#endif
-      max_samples=msamples;
-    }
-    // proper GLContext is activated in StartOffscreenMode function
-    if(!StartOffscreenMode(width,height, max_samples)) {
-      return;
-    }
-  }
-  LOG_DEBUG("Scene: rendering into offscreen buffer");
-  this->RenderGL();
-  // make sure drawing operations are finished
-  glFlush();
-  glFinish();
-
-  boost::shared_array<uchar> img_data(new uchar[width*height*4]);
-      
-  LOG_DEBUG("Scene: setting background transparency");
-  if (transparent) {
-    glPixelTransferf(GL_ALPHA_BIAS, 0.0);
-  } else {
-    // shift alpha channel by one to make sure pixels are read out as opaque
-    glPixelTransferf(GL_ALPHA_BIAS, 1.0);
+  if(!win_) {
+    LOG_ERROR("Scene: Image export requires registered GLWin");
   }
   
-  LOG_DEBUG("Scene: reading framebuffer pixels");
-  glReadBuffer(GL_FRONT);
-  glReadPixels(0,0,width,height,GL_RGBA,GL_UNSIGNED_BYTE,img_data.get());
-  glReadBuffer(GL_BACK);
-
-  LOG_DEBUG("Scene: calling bitmap export");
-  ExportBitmap(fname,ext,width,height,img_data.get());
-
-  // only switch back if it was not on to begin with
-  if(of_flag) {
-    StopOffscreenMode();
-  }
+  win_->Export(fname, width, height, transparent);
 }
 
-void Scene::Export(const String& fname, bool transparent)
+void Scene::Export(const String& fname, unsigned int width,
+                   unsigned int height, int max_samples, bool transparent)
 {
-  if(!win_ && !main_offscreen_buffer_) {
-    LOG_ERROR("Scene: Export without dimensions either requires an interactive session \nor an active offscreen mode (scene.StartOffscreenMode(W,H))");
-    return;
+  if(!win_) {
+    LOG_ERROR("Scene: Image export requires registered GLWin");
   }
 
-  this->ActivateGLContext();
-
-  int d_index=fname.rfind('.');
-  if (d_index==-1) {
-    LOG_ERROR("Scene: no file extension specified");
-    return;
-  }
-  String ext = fname.substr(d_index+1);
-  if(ext!="png") {
-    LOG_ERROR("Scene: unknown file format (" << ext << ")");
-    return;
-  }
-  GLint vp[4];
-  glGetIntegerv(GL_VIEWPORT,vp);
-
-  if(main_offscreen_buffer_) {
-    this->RenderGL();
-    glFlush();
-    glFinish();
-  }
-
-  if (transparent) {
-    glPixelTransferf(GL_ALPHA_BIAS, 0.0);
-  } else {
-    // shift alpha channel by one to make sure pixels are read out as opaque    
-    glPixelTransferf(GL_ALPHA_BIAS, 1.0);    
-  }
-  boost::shared_array<uchar> img_data(new uchar[vp[2]*vp[3]*4]);
-  glReadPixels(0,0,vp[2],vp[3],GL_RGBA,GL_UNSIGNED_BYTE,img_data.get());
-  glFinish();
-  ExportBitmap(fname,ext,vp[2],vp[3],img_data.get());
-  glPixelTransferf(GL_ALPHA_BIAS, 0.0);  
+  win_->Export(fname, width, height, max_samples, transparent);
 }
 
 void Scene::ExportPov(const std::string& fname, const std::string& wdir)
@@ -1989,11 +1840,6 @@ GfxNodeP Scene::GetRootNode() const
   return root_node_;
 }
 
-bool Scene::InOffscreenMode() const
-{
-  return offscreen_flag_;
-}
-
 float Scene::ElapsedTime() const
 {
 #ifndef _MSC_VER
@@ -2680,11 +2526,16 @@ void Scene::ActivateGLContext() const{
     return;
   }
 
-  if(offscreen_flag_) {
-    main_offscreen_buffer_->MakeActive();
-  } else {
-    win_->MakeActive();
-  }
+  win_->MakeActive();
+}
+
+void Scene::SetAlphaBias(Real bias) {
+  this->ActivateGLContext();
+  glPixelTransferf(GL_ALPHA_BIAS, bias);
+}
+
+void Scene::ContextSwitch() {
+  root_node_->ContextSwitch();
 }
 
 }} // ns
diff --git a/modules/gfx/src/scene.hh b/modules/gfx/src/scene.hh
index adacb4ca9e307ca1c96ee664d91da85f5b7e3b1f..c10bec64dd15d022017c1324c6c9bd3d76f59c12 100644
--- a/modules/gfx/src/scene.hh
+++ b/modules/gfx/src/scene.hh
@@ -49,7 +49,6 @@
 namespace ost { namespace gfx {
 
 class InputEvent;
-class OffscreenBuffer;
 
 typedef std::vector<SceneObserver*>  SceneObserverList;
 
@@ -290,11 +289,7 @@ class DLLEXPORT_OST_GFX Scene {
   void SetSelectionMode(uint m);
   uint GetSelectionMode() const;
 
-  /// \name Export
-  //@}
-  /// \brief export scene into a bitmap, rendering into offscreen of given size
-  /// if a main offscreen buffer is active (\sa StartOffscreenMode), then the
-  /// dimensions here are ignored
+
   void Export(const String& fname, unsigned int w,
               unsigned int h, bool transparent=false);
   /// \brief export into bitmap, using multisample anti-aliasing
@@ -459,8 +454,6 @@ class DLLEXPORT_OST_GFX Scene {
   void AttachObserver(SceneObserver* o);
   /// \brief observer interface (internal use)
   void DetachObserver(SceneObserver* o);
-  
-  bool InOffscreenMode() const;
 
   /// \brief switch into test mode (internal use)
   void SetTestMode(bool t);
@@ -469,31 +462,6 @@ class DLLEXPORT_OST_GFX Scene {
 
   Viewport GetViewport() const;
 
-  /*!
-    This method has two different tasks. 
-
-    During interactive rendering, it facilitates export 
-    into an offscreen buffer with Scene::Export(file,width,height)
-    by avoiding repeated initializations of the GL state, e.g.
-    during animation rendering.
-
-    During batch mode, this is the only way to get meaningful
-    functionality with the gfx module
-
-    Returns true upon success and false upon failure
-
-    You can ask for multisampling to be enabled by giving the
-    max_samples a value larger than zero; in this case, the framebuffer
-    with at most this many samplebuffers will be used. The recommended
-    value here is 4; going to 8 or 16 may give you higher export times
-    with usually no marked increase in quality.
-
-  */
-  bool StartOffscreenMode(unsigned int w, unsigned int h, int max_samples);
-  bool StartOffscreenMode(unsigned int w, unsigned int h);
-
-  /// \brief stops offline rendering in interactive mode
-  void StopOffscreenMode();
 
   /// \brief show center of rotation of true
   void SetShowCenter(bool f);
@@ -525,6 +493,11 @@ class DLLEXPORT_OST_GFX Scene {
   bool GetShowExportAspect() const {return show_export_aspect_;}
 
   bool HasMultisample() const {return ms_flag_;}
+
+  void SetAlphaBias(Real bias);
+
+  void ContextSwitch();
+  
 protected:
   friend class GfxObj; 
   friend class GfxNode;
@@ -538,7 +511,6 @@ protected:
   void NodeAdded(const GfxNodeP& node);
   void RenderModeChanged(const String& name);
 
-
 private:  
 
   template <typename ACTION>
@@ -593,9 +565,6 @@ private:
   bool do_autoslab_;   // run autoslab on next scene update
   int autoslab_mode_;  // 0: fast, 1:precise, 2: max
 
-  bool offscreen_flag_; // a simple indicator whether in offscreen mode or not
-  OffscreenBuffer* main_offscreen_buffer_; // not null if a main offscreen buffer is present
-  uint old_vp_[2]; // used by the offline rendering code
   std::string def_shading_mode_;
 
   uint selection_mode_;
diff --git a/modules/gfx/src/vertex_array.cc b/modules/gfx/src/vertex_array.cc
index 1ada7b7c6ddb3a4113a17edc180ae2aeaa4bbb33..89dbaa728405ab3b0adf50fb3a21a60ba7d195e3 100644
--- a/modules/gfx/src/vertex_array.cc
+++ b/modules/gfx/src/vertex_array.cc
@@ -316,9 +316,7 @@ void IndexedVertexArray::RenderGL()
   if(!initialized_) {
     LOG_DEBUG("initializing vertex array lists");
 #if OST_SHADER_SUPPORT_ENABLED
-    if(!Scene::Instance().InOffscreenMode()) {
-      glGenBuffers(7,buffer_id_);
-    }
+    glGenBuffers(7,buffer_id_);
 #endif
     outline_mat_dlist_=glGenLists(1);
     initialized_=true;
@@ -1025,7 +1023,6 @@ void IndexedVertexArray::copy(const IndexedVertexArray& va)
   
 bool IndexedVertexArray::prep_buff()
 {
-  if(Scene::Instance().InOffscreenMode()) return false;
 #if OST_SHADER_SUPPORT_ENABLED
   glEnableClientState(GL_VERTEX_ARRAY);
   glEnableClientState(GL_NORMAL_ARRAY);
@@ -1106,7 +1103,7 @@ void IndexedVertexArray::draw_ltq(bool use_buff)
     glStencilOp(GL_KEEP,GL_INVERT,GL_INVERT);
   }
 
-  if(use_buff && !Scene::Instance().InOffscreenMode()) {
+  if(use_buff) {
 #if OST_SHADER_SUPPORT_ENABLED
 #if 0
     /*
@@ -1198,7 +1195,7 @@ void IndexedVertexArray::draw_ltq(bool use_buff)
 
 void IndexedVertexArray::draw_p(bool use_buff)
 {
-  if(use_buff && !Scene::Instance().InOffscreenMode()) {
+  if(use_buff) {
 #if OST_SHADER_SUPPORT_ENABLED
     glBindBuffer(GL_ARRAY_BUFFER, buffer_id_[VA_VERTEX_BUFFER]);
     glVertexPointer(3, GL_FLOAT, sizeof(Entry), reinterpret_cast<void*>(sizeof(float)*9));
diff --git a/modules/gfx/tests/test_gfx_node.cc b/modules/gfx/tests/test_gfx_node.cc
index 8a7059f8db459e16d765f634799626fc4d41b214..827e7ae176ef13e2cf8f12e3aad101b04af1e400 100644
--- a/modules/gfx/tests/test_gfx_node.cc
+++ b/modules/gfx/tests/test_gfx_node.cc
@@ -33,28 +33,6 @@ using boost::unit_test_framework::test_suite;
 using namespace ost;
 using namespace ost::gfx;
 
-// small RAII class to setup environment for unit tests. even though we don't
-// use any of the rendering functionality, we still need to initialize an 
-// offscreen buffer on mac to avoid segfaults.
-struct GfxTestEnv {
-  GfxTestEnv()
-  {
-#if defined(__APPLE__)    
-    // we know OST_ROOT is set for unit tests
-    SetPrefixPath(getenv("OST_ROOT"));
-    Scene::Instance().StartOffscreenMode(100, 100);
-#endif
-  }
-  
-  ~GfxTestEnv()
-  {
-#if defined(__APPLE__)
-    Scene::Instance().StopOffscreenMode();
-#endif
-  }
-
-};
-
 
 struct Observer : public SceneObserver {
   Observer(): added_count(0),removed_count(0) {}
@@ -134,7 +112,6 @@ BOOST_AUTO_TEST_CASE(gfx_node_remove_all)
 
 BOOST_AUTO_TEST_CASE(is_attached_to_scene) 
 {
-  GfxTestEnv env;
   Scene::Instance().RemoveAll();  
   GfxNodeP n1(new GfxNode("1"));
   GfxNodeP n2(new GfxNode("2"));
@@ -152,7 +129,6 @@ BOOST_AUTO_TEST_CASE(is_attached_to_scene)
 
 BOOST_AUTO_TEST_CASE(observe_added_removed)
 {
-  GfxTestEnv env;
   Observer o1;
   Scene::Instance().RemoveAll();
   Scene::Instance().AttachObserver(&o1);
diff --git a/modules/gui/src/gl_canvas.cc b/modules/gui/src/gl_canvas.cc
index 449f7bb3ba2613aeae96824ff7ee8055d4a000c2..8bec7a90773e7e5bbd8851a661ab7d0da8b97057 100644
--- a/modules/gui/src/gl_canvas.cc
+++ b/modules/gui/src/gl_canvas.cc
@@ -29,6 +29,12 @@
 
 #include <QResizeEvent>
 #include <QMouseEvent>
+#include <QImage>
+#include <QString>
+#include <QOpenGLFramebufferObject>
+#include <QOffscreenSurface>
+#include <QOpenGLContext>
+#include <QOpenGLFunctions>
 
 
 namespace ost { namespace gui {
@@ -36,13 +42,36 @@ namespace ost { namespace gui {
 ost::gui::GLCanvas::GLCanvas(): QOpenGLWindow(),
                                 last_pos_(),
                                 show_beacon_(false),
-                                bench_flag_(false) { 
+                                bench_flag_(false),
+                                offscreen_flag_(false),
+                                offscreen_context_(NULL),
+                                offscreen_surface_(NULL),
+                                offscreen_fbo_(NULL) { 
   LOG_DEBUG("GLCanvas::registering with scene");
   gfx::Scene::Instance().Register(this);  
 }
 
 ost::gui::GLCanvas::~GLCanvas() {
   gfx::Scene::Instance().Unregister(this);
+  if(offscreen_fbo_ != NULL) {
+    // all other offscreen rendering objects are also != NULL
+    // cleanup done as in QT threadrenderer example, not sure whether
+    // offscreen context must be made current to delete offscreen buffer...
+    offscreen_context_->makeCurrent(offscreen_surface_);
+    delete offscreen_fbo_;
+    offscreen_context_->doneCurrent();
+    delete offscreen_context_;
+    delete offscreen_surface_;
+    offscreen_flag_ = false;
+  }
+}
+
+void GLCanvas::MakeActive() {
+  if(offscreen_flag_) {
+    offscreen_context_->makeCurrent(offscreen_surface_);
+  } else {
+    this->makeCurrent();
+  }
 }
 
 void GLCanvas::StatusMessage(const String& m) {
@@ -202,6 +231,110 @@ void GLCanvas::SetTestMode(bool f) {
   gfx::Scene::Instance().SetTestMode(f);
 }
 
+void GLCanvas::Export(const String& fname, unsigned int width, 
+                      unsigned int height, bool transparent) {
+  this->Export(fname, width, height, 0, transparent);
+}
+
+void GLCanvas::Export(const String& fname, unsigned int width, 
+                      unsigned int height, int max_samples, bool transparent) {
+
+  // setup of context, surface, fbo etc are implemented as in the QT
+  // threadrenderer example
+
+  gfx::Viewport old_vp = gfx::Scene::Instance().GetViewport();
+
+  if(old_vp.width == static_cast<int>(width) && 
+     old_vp.height == static_cast<int>(height) && max_samples <= 0) {
+    // just grab the framebuffer, no need for fancy offscreen rendering...
+    this->Export(fname, transparent);
+    return;
+  }
+
+  offscreen_flag_ = true;
+
+  if(offscreen_surface_ == NULL) {
+    offscreen_surface_ = new QOffscreenSurface();
+    QSurfaceFormat f = this->context()->format();
+    if(max_samples > 0) {
+      f.setSamples(max_samples);
+    } 
+    offscreen_surface_->setFormat(f);
+    offscreen_surface_->create();
+  }
+
+  if(offscreen_context_ == NULL) {
+    QOpenGLContext *current = this->context();
+    // Some GL implementations require that the currently bound context is
+    // made non-current before we set up sharing, so we doneCurrent here
+    // and makeCurrent down below while setting up our own context.
+    current->doneCurrent();
+    offscreen_context_ = new QOpenGLContext();
+    QSurfaceFormat f = this->context()->format();
+    if(max_samples > 0) {
+      f.setSamples(max_samples);
+    } 
+    offscreen_context_->setFormat(f);
+    offscreen_context_->setShareContext(current);
+    offscreen_context_->create();
+    offscreen_context_->makeCurrent(offscreen_surface_);
+    gfx::Scene::Instance().ContextSwitch();
+    gfx::Scene::Instance().InitGL(false);
+  } else {
+    offscreen_context_->makeCurrent(offscreen_surface_);
+    gfx::Scene::Instance().ContextSwitch();
+    // the following InitGL sets potentially changed glClearcolor etc
+    // could be made more efficient...
+    gfx::Scene::Instance().InitGL(false);
+  }
+
+  if(offscreen_fbo_ == NULL || 
+     offscreen_fbo_->width() != static_cast<int>(width) || 
+     offscreen_fbo_->height() != static_cast<int>(height)) {
+    if(offscreen_fbo_ != NULL) {
+      delete offscreen_fbo_;
+    }
+    QOpenGLFramebufferObjectFormat fbo_format;
+    // the following flag is required for OpenGL depth testing
+    fbo_format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
+    offscreen_fbo_ = new QOpenGLFramebufferObject(width, height, fbo_format);
+  }
+
+  offscreen_fbo_->bind();
+  gfx::Scene::Instance().Resize(width, height);
+  this->paintGL();
+
+  if(!transparent) {
+    gfx::Scene::Instance().SetAlphaBias(1.0);
+  }
+
+  offscreen_context_->functions()->glFlush();
+  QImage image = offscreen_fbo_->toImage();
+  offscreen_fbo_->release();
+
+  if(!transparent) {
+    gfx::Scene::Instance().SetAlphaBias(0.0);
+  }
+
+  image.save(QString(fname.c_str()));
+  offscreen_flag_ = false;
+  this->MakeActive();
+  gfx::Scene::Instance().Resize(old_vp.width, old_vp.height);
+  gfx::Scene::Instance().ContextSwitch();
+}
+
+void GLCanvas::Export(const String& fname, bool transparent) {
+
+  if(!transparent) {
+    gfx::Scene::Instance().SetAlphaBias(1.0);
+  }
+  QImage image = this->grabFramebuffer();
+  if(!transparent) {
+    gfx::Scene::Instance().SetAlphaBias(0.0);
+  }
+  image.save(QString(fname.c_str()));
+}
+
 bool GLCanvas::IsToolEvent(QInputEvent* event) const {
   return event->modifiers() & Qt::ControlModifier;
 }
diff --git a/modules/gui/src/gl_canvas.hh b/modules/gui/src/gl_canvas.hh
index 7f7c89e0808d1b224e0e5061bd80bcc7de0185a3..36d74b2a77bcb582e22dbf58c7a27b5ca241a254 100644
--- a/modules/gui/src/gl_canvas.hh
+++ b/modules/gui/src/gl_canvas.hh
@@ -29,6 +29,9 @@
 
 // forward declaration
 class QResizeEvent;
+class QOpenGLFramebufferObject;
+class QOpenGLContext;
+class QOffscreenSurface;
 
 namespace ost { namespace gui {
 
@@ -40,7 +43,7 @@ public:
   virtual ~GLCanvas();
 
   // gfx::GLWinBase interface
-  virtual void MakeActive() { this->makeCurrent(); }
+  virtual void MakeActive();
   virtual void DoRefresh() {this->update(); }
   virtual void StatusMessage(const String& m);
   virtual bool HasStereo() const {return format().stereo();};
@@ -56,6 +59,15 @@ public:
 
   void SetTestMode(bool f);
 
+  // Grab images from framebuffer and dump to disk
+  virtual void Export(const String& fname, unsigned int width, 
+                      unsigned int height, bool transparent);
+
+  virtual void Export(const String& fname, unsigned int width, 
+                      unsigned int height, int max_samples, bool transparent);
+
+  virtual void Export(const String& fname, bool transparent);
+
 signals:
   void CustomContextMenuRequested(const QPoint& point);
 
@@ -88,6 +100,12 @@ private:
   QPoint last_pos_;
   bool show_beacon_;  
   bool bench_flag_;
+
+  // for image export
+  bool offscreen_flag_;
+  QOpenGLContext* offscreen_context_;
+  QOffscreenSurface* offscreen_surface_;
+  QOpenGLFramebufferObject* offscreen_fbo_;
 };
 
 }} // ns