changeset 24798:45470049a43f

Allow getframe and print to work without osmesa (bug #53186). * NEWS: Announce support for printing invisible qt figures without osmesa. * __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 is 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 usability of QOffscreenSurface. * configure.ac: Allow building the manual with qt if QOffscreenSurface is OK. * geometryimages.m, interpimages.m, plotimages.m, sparseimages.m, splineimages.m: Allow using qt toolkit to generate images for the manual when QOFFSCREENSURFACE is present.
author Pantxo Diribarne <pantxo.diribarne@gmail.com>
date Fri, 23 Feb 2018 14:33:55 +0100
parents b901d3123745
children 74a596fd6bab
files NEWS configure.ac doc/interpreter/geometryimages.m doc/interpreter/interpimages.m doc/interpreter/plotimages.m doc/interpreter/sparseimages.m doc/interpreter/splineimages.m libgui/graphics/Backend.cc libgui/graphics/Canvas.cc libgui/graphics/Canvas.h libgui/graphics/GLCanvas.cc libgui/graphics/GLCanvas.h m4/acinclude.m4 scripts/image/getframe.m scripts/plot/util/private/__opengl_print__.m
diffstat 15 files changed, 208 insertions(+), 34 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Mon Feb 26 15:43:28 2018 -0800
+++ b/NEWS	Fri Feb 23 14:33:55 2018 +0100
@@ -63,6 +63,9 @@
     explicitly instructed to perform an economy factorization by using a
     final argument of 0.
 
+ ** The Qt graphics toolkit now supports offscreen printing without osmesa
+    if Octave was built with Qt >= 5.1.
+
  ** The FLTK toolkit is no longer prioritized for development.  The
     number of Octave Maintainers is too small to support three different
     graphic toolkits.  New development will target the Qt toolkit.
--- a/configure.ac	Mon Feb 26 15:43:28 2018 -0800
+++ b/configure.ac	Fri Feb 23 14:33:55 2018 +0100
@@ -2791,7 +2791,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."
--- a/doc/interpreter/geometryimages.m	Mon Feb 26 15:43:28 2018 -0800
+++ b/doc/interpreter/geometryimages.m	Fri Feb 23 14:33:55 2018 +0100
@@ -142,6 +142,9 @@
 function set_graphics_toolkit ()
   if (isempty (available_graphics_toolkits ()))
     error ("no graphics toolkit available for plotting");
+  elseif (strcmp ("qt", graphics_toolkit ())
+          && __have_feature__ ("QOFFSCREENSURFACE"))
+    ## Use qt with QOffscreenSurface for plot
   elseif (! strcmp ("gnuplot", graphics_toolkit ()) ...
           && ! __have_feature__ ("OSMESA"))
     if (! any (strcmp ("gnuplot", available_graphics_toolkits ())))
--- a/doc/interpreter/interpimages.m	Mon Feb 26 15:43:28 2018 -0800
+++ b/doc/interpreter/interpimages.m	Fri Feb 23 14:33:55 2018 +0100
@@ -85,6 +85,9 @@
 function set_graphics_toolkit ()
   if (isempty (available_graphics_toolkits ()))
     error ("no graphics toolkit available for plotting");
+  elseif (strcmp ("qt", graphics_toolkit ())
+          && __have_feature__ ("QOFFSCREENSURFACE"))
+    ## Use qt with QOffscreenSurface for plot
   elseif (! strcmp ("gnuplot", graphics_toolkit ())
           && ! __have_feature__ ("OSMESA"))
     if (! any (strcmp ("gnuplot", available_graphics_toolkits ())))
--- a/doc/interpreter/plotimages.m	Mon Feb 26 15:43:28 2018 -0800
+++ b/doc/interpreter/plotimages.m	Fri Feb 23 14:33:55 2018 +0100
@@ -123,6 +123,9 @@
 function set_graphics_toolkit ()
   if (isempty (available_graphics_toolkits ()))
     error ("no graphics toolkit available for plotting");
+  elseif (strcmp ("qt", graphics_toolkit ())
+          && __have_feature__ ("QOFFSCREENSURFACE"))
+    ## Use qt with QOffscreenSurface for plot
   elseif (! strcmp ("gnuplot", graphics_toolkit ()) ...
           && ! __have_feature__ ("OSMESA"))
     if (! any (strcmp ("gnuplot", available_graphics_toolkits ())))
--- a/doc/interpreter/sparseimages.m	Mon Feb 26 15:43:28 2018 -0800
+++ b/doc/interpreter/sparseimages.m	Fri Feb 23 14:33:55 2018 +0100
@@ -244,6 +244,9 @@
 function set_graphics_toolkit ()
   if (isempty (available_graphics_toolkits ()))
     error ("no graphics toolkit available for plotting");
+  elseif (strcmp ("qt", graphics_toolkit ())
+          && __have_feature__ ("QOFFSCREENSURFACE"))
+    ## Use qt with QOffscreenSurface for plot
   elseif (! strcmp ("gnuplot", graphics_toolkit ())
           && ! __have_feature__ ("OSMESA"))
     if (! any (strcmp ("gnuplot", available_graphics_toolkits ())))
--- a/doc/interpreter/splineimages.m	Mon Feb 26 15:43:28 2018 -0800
+++ b/doc/interpreter/splineimages.m	Fri Feb 23 14:33:55 2018 +0100
@@ -175,6 +175,9 @@
 function set_graphics_toolkit ()
   if (isempty (available_graphics_toolkits ()))
     error ("no graphics toolkit available for plotting");
+  elseif (strcmp ("qt", graphics_toolkit ())
+          && __have_feature__ ("QOFFSCREENSURFACE"))
+    ## Use qt with QOffscreenSurface for plot
   elseif (! strcmp ("gnuplot", graphics_toolkit ())
           && ! __have_feature__ ("OSMESA"))
     if (! any (strcmp ("gnuplot", available_graphics_toolkits ())))
--- a/libgui/graphics/Backend.cc	Mon Feb 26 15:43:28 2018 -0800
+++ b/libgui/graphics/Backend.cc	Fri Feb 23 14:33:55 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);
 
--- a/libgui/graphics/Canvas.cc	Mon Feb 26 15:43:28 2018 -0800
+++ b/libgui/graphics/Canvas.cc	Fri Feb 23 14:33:55 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.
--- a/libgui/graphics/Canvas.h	Mon Feb 26 15:43:28 2018 -0800
+++ b/libgui/graphics/Canvas.h	Fri Feb 23 14:33:55 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)
--- a/libgui/graphics/GLCanvas.cc	Mon Feb 26 15:43:28 2018 -0800
+++ b/libgui/graphics/GLCanvas.cc	Fri Feb 23 14:33:55 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 ();
+
+        // Make sure we have a valid current context
+        if (! begin_rendering ())
+          return retval;
 
-        r.set_viewport (width (), height ());
-        r.draw (go);
-        retval = r.get_pixels (width (), height ());
+        // 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,40 @@
       OCTAVE_QT_OPENGL_WIDGET::keyReleaseEvent (xevent);
   }
 
+  bool
+  GLCanvas::begin_rendering (void)
+  {
+    bool retval = true;
+    
+    if (! isValid ())
+      {
+#  if defined (HAVE_QOFFSCREENSURFACE)
+        static bool os_ctx_ok = true;
+        if (os_ctx_ok && ! m_os_context.isValid ())
+          {
+            // Try to initialize offscreen context
+            m_os_surface.create ();
+            if (! m_os_context.create ())
+              {
+                os_ctx_ok = false;
+                return false;
+              }
+          }
+       
+        retval = m_os_context.makeCurrent (&m_os_surface);
+#  else   
+        retval = false;
+#  endif
+      }
+    else
+      makeCurrent ();
+    
+    return retval;
+  }
+  
+  void
+  GLCanvas::end_rendering (void)
+  {        
+    doneCurrent ();
+  }
 }
--- a/libgui/graphics/GLCanvas.h	Mon Feb 26 15:43:28 2018 -0800
+++ b/libgui/graphics/GLCanvas.h	Fri Feb 23 14:33:55 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
   };
 
 }
--- a/m4/acinclude.m4	Mon Feb 26 15:43:28 2018 -0800
+++ b/m4/acinclude.m4	Fri Feb 23 14:33:55 2018 +0100
@@ -1470,6 +1470,60 @@
   AM_CONDITIONAL([WIN32_TERMINAL], [test $win32_terminal = yes])
 ])
 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 works with full OpenGL support
 dnl
 AC_DEFUN([OCTAVE_CHECK_QT_OPENGL_OK], [
@@ -1532,7 +1586,7 @@
 ])
 dnl
 dnl Check whether Qt VERSION is present, supports QtOpenGL and
-dnl QScintilla and will work for Octave.
+dnl QScintilla, and will work for Octave.
 dnl
 dnl OCTAVE_CHECK_QT_VERSION(VERSION)
 dnl
@@ -1545,6 +1599,7 @@
 
   build_qt_gui=yes
   build_qt_graphics=no
+  have_qt_opengl_offscreen=no
   win32_terminal=no
 
   warn_qt_libraries=""
@@ -1725,6 +1780,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
--- a/scripts/image/getframe.m	Mon Feb 26 15:43:28 2018 -0800
+++ b/scripts/image/getframe.m	Fri Feb 23 14:33:55 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
--- a/scripts/plot/util/private/__opengl_print__.m	Mon Feb 26 15:43:28 2018 -0800
+++ b/scripts/plot/util/private/__opengl_print__.m	Fri Feb 23 14:33:55 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