# HG changeset patch
# User Pantxo Diribarne <pantxo.diribarne@gmail.com>
# Date 1519384654 -3600
#      Fri Feb 23 12:17:34 2018 +0100
# Node ID 2c4397ce4fc75430d18897a4c428618808089bec
# Parent  30e1d0bf7ade11f308bdc915e3b2d509166c8dc7
Allow getframe and print to work without osmesa (bug #53186)

* __opengl_print__.m: allow invisible figures to be printed with qt if
  QOFFSCREENSURFACE is present

* getframe.m: allow using __get_frame__ on invisible qt figures  if
  QOFFSCREENSURFACE is present

* Backenc.cc (Backend::get_pixels): don't restrict to visible figures

* Canvas.h/cc (Canvas::do_print): new pure virtual function
  (Canvas::print): call do_print

* GLCanvas.h/cc (GLCanvas::begin_rendering): new methods to make sure a valid
  opengl context is made current before rendering.
  (GLCanvas::end_rendering): call doneCurrent
  (GLCanvas::getPixels): make use of begin/end_rendering. If the figure is not
  visible or its size os frozen, draw on a framebuffer object of suitable size
  (GLCanvas::do_print): move code for opengl printing here. Make use of
  begin/end_rendering

* acinclude.m4: add test for presence and usablility of QOffscreenSurface

* configure.ac: allow building the manual with qt if QOffscreenSurface is OK

diff -r 30e1d0bf7ade -r 2c4397ce4fc7 configure.ac
--- a/configure.ac	Thu Feb 08 04:06:03 2018 -0500
+++ b/configure.ac	Fri Feb 23 12:17:34 2018 +0100
@@ -2788,7 +2788,7 @@
      OCTAVE_CONFIGURE_WARNING([warn_docs])
    fi], [])
 if test $ENABLE_DOCS = yes; then
-  if test $opengl_graphics = no || test -n "$warn_OSMesa"; then
+  if test $opengl_graphics = no || test -n "$warn_OSMesa" || test -n "$have_qt_opengl_offscreen"; then
     if test -n "$warn_gnuplot"; then
       ENABLE_DOCS=no
       warn_docs_graphics="building documentation disabled because no suitable graphics toolkit is available; make dist will fail."
diff -r 30e1d0bf7ade -r 2c4397ce4fc7 libgui/graphics/Backend.cc
--- a/libgui/graphics/Backend.cc	Thu Feb 08 04:06:03 2018 -0500
+++ b/libgui/graphics/Backend.cc	Fri Feb 23 12:17:34 2018 +0100
@@ -197,7 +197,7 @@
   {
     uint8NDArray retval;
 
-    if (go.get_properties ().is_visible () && go.isa ("figure"))
+    if (go.isa ("figure"))
       {
         ObjectProxy *proxy = toolkitObjectProxy (go);
 
diff -r 30e1d0bf7ade -r 2c4397ce4fc7 libgui/graphics/Canvas.cc
--- a/libgui/graphics/Canvas.cc	Thu Feb 08 04:06:03 2018 -0500
+++ b/libgui/graphics/Canvas.cc	Fri Feb 23 12:17:34 2018 +0100
@@ -41,7 +41,6 @@
 
 #include "annotation-dialog.h"
 
-#include "gl2ps-print.h"
 #include "oct-opengl.h"
 #include "octave-qt-link.h"
 
@@ -101,27 +100,6 @@
       }
   }
 
-  void
-  Canvas::print (const QString& file_cmd, const QString& term)
-  {
-    gh_manager::auto_lock lock;
-    graphics_object obj = gh_manager::get_object (m_handle);
-
-    if (obj.valid_object ())
-      {
-        graphics_object figObj (obj.get_ancestor ("figure"));
-        try
-          {
-            octave::gl2ps_print (figObj, file_cmd.toStdString (),
-                                 term.toStdString ());
-          }
-        catch (octave::execution_exception e)
-          {
-            octave_link::post_exception (std::current_exception ());
-          }
-      }
-  }
-
   /*
      Two updateCurrentPoint() routines are required:
      1) Used for QMouseEvents where cursor position data is in callback from Qt.
diff -r 30e1d0bf7ade -r 2c4397ce4fc7 libgui/graphics/Canvas.h
--- a/libgui/graphics/Canvas.h	Thu Feb 08 04:06:03 2018 -0500
+++ b/libgui/graphics/Canvas.h	Fri Feb 23 12:17:34 2018 +0100
@@ -54,7 +54,10 @@
     void redraw (bool sync = false);
     void blockRedraw (bool block = true);
 
-    void print (const QString& file_cmd, const QString& term);
+    void print (const QString& file_cmd, const QString& term)
+    {
+      do_print (file_cmd, term, m_handle);
+    }
 
     void addEventMask (int m) { m_eventMask |= m; }
     void clearEventMask (int m) { m_eventMask &= (~m); }
@@ -80,6 +83,8 @@
     virtual graphics_object selectFromAxes (const graphics_object& ax,
                                             const QPoint& pt) = 0;
     virtual uint8NDArray do_getPixels (const graphics_handle& handle) = 0;
+    virtual void do_print (const QString& file_cmd, const QString& term,
+                           const graphics_handle& handle) = 0;
 
   protected:
     Canvas (const graphics_handle& handle)
diff -r 30e1d0bf7ade -r 2c4397ce4fc7 libgui/graphics/GLCanvas.cc
--- a/libgui/graphics/GLCanvas.cc	Thu Feb 08 04:06:03 2018 -0500
+++ b/libgui/graphics/GLCanvas.cc	Fri Feb 23 12:17:34 2018 +0100
@@ -25,7 +25,9 @@
 #endif
 
 #include "gl-render.h"
+#include "gl2ps-print.h"
 #include "graphics.h"
+#include "octave-link.h"
 
 #include "GLCanvas.h"
 #include "gl-select.h"
@@ -76,18 +78,73 @@
   {
     uint8NDArray retval;
     graphics_object go = gh_manager::get_object (gh);
-
-    if (go)
+    
+    if (go && go.isa ("figure"))
       {
-        octave::opengl_renderer r;
+        Matrix pos = go.get ("position").matrix_value ();
 
-        r.set_viewport (width (), height ());
-        r.draw (go);
-        retval = r.get_pixels (width (), height ());
+        // Make sure we have a valid current context
+        if (! begin_rendering ())
+          return retval;
+
+        // When the figure is not visible or its size is frozen for printing,
+        // we use a framebuffer object to make sure we are rendering on a
+        // suitably large frame.
+        if (go.get ("visible").string_value () == "off"
+            || go.get ("__printing__").string_value () == "on")
+          {
+            OCTAVE_QT_OPENGL_FBO
+              fbo (pos(2), pos(3),OCTAVE_QT_OPENGL_FBO::Attachment::Depth);
+            
+            fbo.bind ();
+            
+            octave::opengl_renderer r;
+            r.set_viewport (pos(2), pos(3));
+            r.draw (go);
+            retval = r.get_pixels (pos(2), pos(3));
+            
+            fbo.release ();
+          }
+        else
+          {
+            octave::opengl_renderer r;
+            r.set_viewport (pos(2), pos(3));
+            r.draw (go);
+            retval = r.get_pixels (pos(2), pos(3));            
+          }
+
+        end_rendering ();
       }
 
     return retval;
   }
+  
+  void
+  GLCanvas::do_print (const QString& file_cmd, const QString& term,
+                      const graphics_handle& handle)
+  {
+    gh_manager::auto_lock lock;
+    graphics_object obj = gh_manager::get_object (handle);
+
+    if (obj.valid_object ())
+      {
+        graphics_object figObj (obj.get_ancestor ("figure"));
+        try
+          {
+            // Make sure we have a valid current context
+            if (! begin_rendering ())
+              error ("print: no valid opengl offscreen context");
+            
+            octave::gl2ps_print (figObj, file_cmd.toStdString (),
+                                 term.toStdString ());
+          }
+        catch (octave::execution_exception e)
+          {
+            octave_link::post_exception (std::current_exception ());
+            end_rendering ();
+          }
+      }
+  }
 
   void
   GLCanvas::toggleAxes (const graphics_handle& gh)
@@ -219,4 +276,41 @@
       OCTAVE_QT_OPENGL_WIDGET::keyReleaseEvent (xevent);
   }
 
+  bool
+  GLCanvas::begin_rendering (void)
+  {
+    bool retval = true;
+    static bool ctx_ok = true;
+    
+    if (! isValid ())
+      {
+#  if defined (HAVE_QOFFSCREENSURFACE)
+       if (ctx_ok && ! m_os_context.isValid ())
+         {
+           // Try to initialize offscreen context
+           m_os_surface.create ();
+           if (! m_os_context.create ())
+             {
+               ctx_ok = false;
+               return false;
+             }
+         }
+       
+       retval = m_os_context.makeCurrent (&m_os_surface);
+#  else   
+        retval = false;
+        ctx_ok = false;
+#  endif
+      }
+    else
+      makeCurrent ();
+    
+    return retval;
+  }
+  
+  void
+  GLCanvas::end_rendering (void)
+  {        
+    doneCurrent ();
+  }
 }
diff -r 30e1d0bf7ade -r 2c4397ce4fc7 libgui/graphics/GLCanvas.h
--- a/libgui/graphics/GLCanvas.h	Thu Feb 08 04:06:03 2018 -0500
+++ b/libgui/graphics/GLCanvas.h	Fri Feb 23 12:17:34 2018 +0100
@@ -26,9 +26,17 @@
 #if defined (HAVE_QOPENGLWIDGET)
 #  include <QOpenGLWidget>
 #  define OCTAVE_QT_OPENGL_WIDGET QOpenGLWidget
+#  include <QOpenGLFramebufferObject>
+#  define OCTAVE_QT_OPENGL_FBO QOpenGLFramebufferObject
+#  if defined (HAVE_QOFFSCREENSURFACE)
+#    include <QOpenGLContext>
+#    include <QOffscreenSurface>
+#  endif  
 #elif defined (HAVE_QGLWIDGET)
 #  include <QGLWidget>
 #  define OCTAVE_QT_OPENGL_WIDGET QGLWidget
+#  include <QGLFramebufferObject>
+#  define OCTAVE_QT_OPENGL_FBO QGLFramebufferObject
 #else
 #  error "configuration error: must have <QOpenGLWidget> or <QGLWidget>."
 #endif
@@ -45,6 +53,8 @@
 
     void draw (const graphics_handle& handle);
     uint8NDArray  do_getPixels (const graphics_handle& handle);
+    void do_print (const QString& file_cmd, const QString& term,
+                   const graphics_handle& handle);
     void toggleAxes (const graphics_handle& handle);
     void toggleGrid (const graphics_handle& handle);
     void autoAxes (const graphics_handle& handle);
@@ -64,6 +74,16 @@
     void wheelEvent (QWheelEvent *event);
     void keyPressEvent (QKeyEvent *event);
     void keyReleaseEvent (QKeyEvent *event);
+    
+  private:
+    
+    bool begin_rendering (void);
+    void end_rendering (void);
+
+# if defined (HAVE_QOFFSCREENSURFACE)
+    QOpenGLContext m_os_context;    
+    QOffscreenSurface m_os_surface;
+# endif
   };
 
 }
diff -r 30e1d0bf7ade -r 2c4397ce4fc7 m4/acinclude.m4
--- a/m4/acinclude.m4	Thu Feb 08 04:06:03 2018 -0500
+++ b/m4/acinclude.m4	Fri Feb 23 12:17:34 2018 +0100
@@ -1469,6 +1469,7 @@
   AM_CONDITIONAL([AMCOND_HAVE_QSCINTILLA], [test $use_qscintilla = yes])
   AM_CONDITIONAL([WIN32_TERMINAL], [test $win32_terminal = yes])
 ])
+
 dnl
 dnl Check whether Qt works with full OpenGL support
 dnl
@@ -1530,6 +1531,62 @@
     :
   fi
 ])
+
+dnl
+dnl Check whether QOffscreenSurface is present.
+dnl
+AC_DEFUN([OCTAVE_CHECK_QT_OPENGL_OFFSCREEN_OK], [
+  dnl Normally the language and compiler flags would be set and restored
+  dnl inside of the AC_CACHE_CHECK body.  Because we also need to check for
+  dnl Qt header files associated with the compilation test, set and restore
+  dnl these values outside of the AC_CACHE_CHECK for this macro only.
+  AC_LANG_PUSH(C++)
+  ac_octave_save_CPPFLAGS="$CPPFLAGS"
+  ac_octave_save_CXXFLAGS="$CXXFLAGS"
+  CPPFLAGS="$QT_CPPFLAGS $CXXPICFLAG $CPPFLAGS"
+  CXXFLAGS="$CXXPICFLAG $CXXFLAGS"
+  AC_CHECK_HEADERS([QOffscreenSurface])
+  AC_CACHE_CHECK([whether Qt supports full offscreen OpenGL rendering],
+    [octave_cv_qt_opengl_os_ok],
+    [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+         #if HAVE_WINDOWS_H
+         #  include <windows.h>
+         #endif
+         #if defined (HAVE_GL_GL_H)
+         #  include <GL/gl.h>
+         #elif defined (HAVE_OPENGL_GL_H)
+         #  include <OpenGL/gl.h>
+         #endif
+         #if defined (HAVE_GL_GLU_H)
+         #  include <GL/glu.h>
+         #elif defined HAVE_OPENGL_GLU_H || defined HAVE_FRAMEWORK_OPENGL
+         #  include <OpenGL/glu.h>
+         #endif
+         #if defined (HAVE_QOPENGLWIDGET)
+         #  include <QOpenGLWidget>
+         #  include <QOpenGLContext>
+         #endif
+         #if defined (HAVE_QOFFSCREENSURFACE)
+         #  include <QOffscreenSurface>
+         #endif
+         QOpenGLContext ctx;    
+         QOffscreenSurface surf;
+       ]])],
+       octave_cv_qt_opengl_os_ok=yes,
+       octave_cv_qt_opengl_os_ok=no)
+  ])
+  CPPFLAGS="$ac_octave_save_CPPFLAGS"
+  CXXFLAGS="$ac_octave_save_CXXFLAGS"
+  AC_LANG_POP(C++)
+  if test $octave_cv_qt_opengl_os_ok = yes; then
+    $1
+    :
+  else
+    $2
+    :
+  fi
+])
+
 dnl
 dnl Check whether Qt VERSION is present, supports QtOpenGL and
 dnl QScintilla and will work for Octave.
@@ -1545,6 +1602,7 @@
 
   build_qt_gui=yes
   build_qt_graphics=no
+  have_qt_opengl_offscreen=no
   win32_terminal=no
 
   warn_qt_libraries=""
@@ -1725,6 +1783,7 @@
         [warn_qt_opengl="Qt does not work with the OpenGL libs (GL and GLU); disabling OpenGL graphics with Qt GUI"])
 
       if test $build_qt_graphics = yes; then
+        OCTAVE_CHECK_QT_OPENGL_OFFSCREEN_OK([have_qt_opengl_offscreen=yes])
         AC_DEFINE(HAVE_QT_GRAPHICS, 1, [Define to 1 if Qt works with OpenGL libs (GL and GLU)])
       fi
     fi
diff -r 30e1d0bf7ade -r 2c4397ce4fc7 scripts/image/getframe.m
--- a/scripts/image/getframe.m	Thu Feb 08 04:06:03 2018 -0500
+++ b/scripts/image/getframe.m	Fri Feb 23 12:17:34 2018 +0100
@@ -95,7 +95,10 @@
     pos = rect;
   endif
 
-  if (strcmp (get (hf, "visible"), "on"))
+  if (strcmp (get (hf, "visible"), "on")
+      || (strcmp (get (hf, "__graphics_toolkit__"), "qt")
+          && (strcmp (get (hf, "__gl_window__"), "on")
+              || __have_feature__ ("QOFFSCREENSURFACE"))))
     cdata = __get_frame__ (hf);
   else
     ## Use OpenGL offscreen rendering with OSMesa for non-visible figures
diff -r 30e1d0bf7ade -r 2c4397ce4fc7 scripts/plot/util/private/__opengl_print__.m
--- a/scripts/plot/util/private/__opengl_print__.m	Thu Feb 08 04:06:03 2018 -0500
+++ b/scripts/plot/util/private/__opengl_print__.m	Fri Feb 23 12:17:34 2018 +0100
@@ -152,7 +152,8 @@
 
     if (strcmp (get (opts.figure, "visible"), "on")
         || (strcmp (get (opts.figure, "__graphics_toolkit__"), "qt")
-            && strcmp (get (opts.figure, "__gl_window__"), "on")))
+            && (strcmp (get (opts.figure, "__gl_window__"), "on")
+                || __have_feature__ ("QOFFSCREENSURFACE"))))
       ## Use toolkits "print_figure" method
       drawnow (gl2ps_device{n}, ['|' pipeline{n}]);
     else
