From 4f1ebef4a307011acfd30cfdf9168cbebda8dcbc Mon Sep 17 00:00:00 2001
From: Gabriel Studer <gabriel.studer@unibas.ch>
Date: Wed, 15 May 2019 11:31:17 +0200
Subject: [PATCH] Image export using QT

Image export has been performed using platform dependent offscreen buffers
that are setup manually. Segfaults etc. were observed on several occasions.
This commit enables offscreen rendering through the GLCanvas using QT
functionality.
Pro:
- QT takes care of platform dependent things
- Other image formats than just png come for free
Con:
- The gfx module didn't depend on QT and image export could be scripted
  Scripting is still possible, but a GLCanvas must be created.
- A offscreen solution could be implemented but it would still depend on QT
---
 modules/gfx/pymod/export_scene.cc  |   5 -
 modules/gfx/src/gfx_object.cc      |   9 +-
 modules/gfx/src/glwin_base.hh      |  20 +++
 modules/gfx/src/scene.cc           | 223 +++++------------------------
 modules/gfx/src/scene.hh           |  43 +-----
 modules/gfx/src/vertex_array.cc    |   9 +-
 modules/gfx/tests/test_gfx_node.cc |  24 ----
 modules/gui/src/gl_canvas.cc       | 135 ++++++++++++++++-
 modules/gui/src/gl_canvas.hh       |  20 ++-
 9 files changed, 221 insertions(+), 267 deletions(-)

diff --git a/modules/gfx/pymod/export_scene.cc b/modules/gfx/pymod/export_scene.cc
index f67e90cce..746c958f7 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 b265db731..97e235f41 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 7486540d5..77d30ee76 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 4f3cf2a1c..1ce2a44eb 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 adacb4ca9..c10bec64d 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 1ada7b7c6..89dbaa728 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 8a7059f8d..827e7ae17 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 449f7bb3b..8bec7a907 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 7f7c89e08..36d74b2a7 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
-- 
GitLab