1 /*=========================================================================
2 
3   Program:   Visualization Toolkit
4   Module:    QQuickVTKRenderWindow.h
5 
6   Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
7   All rights reserved.
8   See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
9 
10   This software is distributed WITHOUT ANY WARRANTY; without even
11   the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
12   PURPOSE.  See the above copyright notice for more information.
13 
14 =========================================================================*/
15 /**
16  * @class QQuickVTKRenderWindow
17  * @brief [QQuickItem] subclass that manages the vtkRenderWindow and, in
18  * turn, the OpenGL context of the QML application
19  *
20  * QQuickVTKRenderWindow extends [QQuickItem] in a way that allows for VTK to get a handle to, and
21  * draw inside of the QtQuick scenegraph, using OpenGL draw calls.
22  *
23  * This item is exported to the QML layer via the QQmlVTKPlugin under the module VTK. It is
24  * registered as a type \b VTKRenderWindow. Since, this class is intended to manage an OpenGL
25  * context in the window, a single instance would be needed for most QML applications.
26  *
27  * Typical usage for QQuickVTKRenderWindow in a Qml application is as follows:
28  *
29  * @code
30  *  // import related modules
31  *  import QtQuick 2.15
32  *  import QtQuick.Controls 2.15
33  *  import QtQuick.Window 2.15
34  *
35  *  // import the VTK module
36  *  import VTK 9.0
37  *
38  *  // window containing the application
39  *  ApplicationWindow {
40  *    // title of the application
41  *    title: qsTr("VTK QtQuick App")
42  *    width: 400
43  *    height: 400
44  *    color: palette.window
45  *
46  *    SystemPalette {
47  *      id: palette
48  *      colorGroup: SystemPalette.Active
49  *    }
50  *
51  *    // Instantiate the vtk render window
52  *    VTKRenderWindow {
53  *      id: vtkwindow
54  *      width: 400
55  *      height: 400
56  *    }
57  *
58  *    // add one or more vtk render items
59  *    VTKRenderItem {
60  *      objectName: "ConeView"
61  *      x: 200
62  *      y: 200
63  *      width: 200
64  *      height: 200
65  *      // Provide the handle to the render window
66  *      renderWindow: vtkwindow
67  *    }
68  *    VTKRenderItem {
69  *      objectName: "VolumeView"
70  *      x: 0
71  *      y: 0
72  *      width: 200
73  *      height: 200
74  *      // Provide the handle to the render window
75  *      renderWindow: vtkwindow
76  *    }
77  *  }
78  * @endcode
79  *
80  * To ensure that the graphics backend set up by QtQuick matches that expected by VTK, use the
81  * method QQuickVTKRenderWindow::setupGraphicsBackend() before a QApplication/QGuiApplication is
82  * instantiated in the main method of the application.
83  *
84  * @code
85  * int main(int argc, char* argv[])
86  * {
87  *   // Setup the graphics backend
88  *   QQuickVTKRenderWindow::setupGraphicsBackend();
89  *   QGuiApplication app(argc, argv);
90  *   ...
91  *   return EXIT_SUCCESS;
92  * }
93  * @endcode
94  *
95  * The VTK pipeline can be then set up for each \b VTKRenderItem in the C++ code.
96  *
97  * ## QtQuick scenegraph and threaded render loop
98  *
99  * QtQuick/QML scenegraph rendering is done via private API inside the [QQuickWindow] class. For
100  * details on QtQuick's render loop, see [QtQuick Scenegraph Rendering](
101  * https://doc.qt.io/qt-6/qtquick-visualcanvas-scenegraph.html#scene-graph-and-rendering).
102  * Qt automatically decides between a threaded and basic render loop for most applications.
103  * QQuickVTKRenderWindow and QQuickVTKRenderItem support both these variants of the QtQuick render
104  * loop.
105  *
106  * When the scenegraph render loop is threaded, i.e. there is a dedicated rendering thread, vtk
107  * sticks to doing all rendering on this render thread. This means that all the vtk classes,
108  * pipelines etc. can be set up on the main thread but vtkRenderWindow::Render should only be
109  * invoked on the render thread. Care must be taken not to call Render on the main thread because
110  * the OpenGL context would not be valid on the main thread.
111  *
112  * [QQuickItem]: https://doc.qt.io/qt-5/qquickitem.html
113  * [QQuickWindow]: https://doc.qt.io/qt-5/qquickwindow.html
114  */
115 
116 #ifndef QQuickVTKRenderWindow_h
117 #define QQuickVTKRenderWindow_h
118 
119 // vtk includes
120 #include "vtkSmartPointer.h" // For vtkSmartPointer
121 
122 // Qt includes
123 #include <QOpenGLFunctions> // For QOpenGLFunctions
124 #include <QPointer>         // For QPointer
125 #include <QQuickItem>
126 
127 #include "vtkGUISupportQtQuickModule.h" // for export macro
128 
129 // Forward declarations
130 class QEvent;
131 class QQuickVTKInteractorAdapter;
132 class QQuickWindow;
133 class QWheelEvent;
134 class vtkGenericOpenGLRenderWindow;
135 class vtkImageData;
136 class vtkRenderWindow;
137 class vtkRenderer;
138 class vtkWindowToImageFilter;
139 
140 class VTKGUISUPPORTQTQUICK_EXPORT QQuickVTKRenderWindow
141   : public QQuickItem
142   , protected QOpenGLFunctions
143 {
144   Q_OBJECT
145   typedef QQuickItem Superclass;
146 
147 public:
148   /**
149    * Constructor
150    * Creates a QQuickVTKRenderWindow with:
151    *  - a vtkGenericOpenGLRenderWindow to manage the OpenGL context
152    *  - an interactor adapter to forward Qt events to vtk's interactor
153    */
154   QQuickVTKRenderWindow(QQuickItem* parent = nullptr);
155 
156   /**
157    * Destructor
158    */
159   ~QQuickVTKRenderWindow();
160 
161   /**
162    * Set up the graphics surface format and api.
163    *
164    * This method sets the graphics API to OpenGLRhi and sets up the surface format for intermixed
165    * VTK and QtQuick rendering.
166    * Use this method before instantiating a QApplication/QGuiApplication in a QtQuick/QML app with
167    * a VTK render view like QQuickVTKRenderItem.
168    */
169   static void setupGraphicsBackend();
170 
171   ///@{
172   /**
173    * Set/Get the vtkRenderWindow for the view.
174    * Note that this render window should be of type vtkGenericOpenGLRenderWindow. This is necessary
175    * since that would allow vtk's opengl draw calls to work seamlessly inside the QtQuick created
176    * scenegraph and OpenGL context.
177    *
178    * By default, a vtkGenericOpenGLRenderWindow is created and set on this item at construction
179    * time.
180    */
181   void setRenderWindow(vtkRenderWindow* renWin);
182   void setRenderWindow(vtkGenericOpenGLRenderWindow* renWin);
183   vtkRenderWindow* renderWindow() const;
184   ///@}
185 
186   /**
187    * Map a Qt item rect to viewport coordinates
188    */
189   virtual void mapToViewport(const QRectF& rect, double viewport[4]);
190 
191   /**
192    * Get access to the interactor adapter
193    */
194   QPointer<QQuickVTKInteractorAdapter> interactorAdapter() const;
195 
196   ///@{
197   /**
198    * Capture a screenshot of the window
199    *
200    * \param viewport area to capture.
201    * \returns Image data containing the window capture.
202    * \note This triggers a scenegraph update to capture the render window view.
203    */
204   virtual vtkSmartPointer<vtkImageData> captureScreenshot();
205   virtual vtkSmartPointer<vtkImageData> captureScreenshot(double* viewport);
206   ///@}
207 
208   /**
209    * Get whether the render window is initialized
210    * Used internally to determine if the OpenGL context, QQuickWindow, children items and viewports
211    * have been initialized.
212    */
213   virtual bool isInitialized() const;
214 
215 public Q_SLOTS:
216   /**
217    * This is the function called on the QtQuick render thread before the scenegraph state
218    * is synchronized. This is where most of the pipeline updates, camera manipulations, etc. and
219    * other pre-render steps can be performed.
220    *
221    * \note At the time of this method execution, the GUI thread is blocked. Hence, it is safe to
222    * perform state synchronization between the GUI elements and the VTK classes here.
223    */
224   virtual void sync();
225 
226   /**
227    * Initialize the VTK render window for OpenGL based on the context created by QtQuick
228    *
229    * \note This method is called at the beforeRenderPassRecording stage of the QtQuick scenegraph.
230    * All the QtQuick element rendering is stacked visually above the vtk rendering.
231    */
232   virtual void init();
233 
234   /**
235    * This is the function called on the QtQuick render thread right before the scenegraph is
236    * rendered. This is the stage where all the vtk rendering is performed. Applications would rarely
237    * need to override this method.
238    *
239    * \note This method is called at the beforeRenderPassRecording stage of the QtQuick scenegraph.
240    * All the QtQuick element rendering is stacked visually above the vtk rendering.
241    */
242   virtual void paint();
243 
244   /**
245    * This is the function called on the QtQuick render thread when the scenegraph is invalidated.
246    * This is where all graphics resources allocated by vtk are released.
247    */
248   virtual void cleanup();
249 
250   /**
251    * Convenience method that schedules a scenegraph update and waits for the update.
252    * \sa render()
253    */
254   virtual void renderNow();
255 
256   /**
257    * Schedule a scenegraph update
258    *
259    * \note Since this schedules a scenegraph update, it does not guarantee that the scene will be
260    * updated after this call.
261    * \sa renderNow()
262    */
263   virtual void render();
264 
265 protected Q_SLOTS:
266   virtual void handleWindowChanged(QQuickWindow* w);
267 
268 protected:
269   QPointer<QQuickVTKInteractorAdapter> m_interactorAdapter;
270   vtkSmartPointer<vtkGenericOpenGLRenderWindow> m_renderWindow;
271   bool m_initialized = false;
272 
273   // Screenshot stuff
274   bool m_screenshotScheduled = false;
275   vtkNew<vtkWindowToImageFilter> m_screenshotFilter;
276   vtkNew<vtkRenderer> m_dummyRenderer;
277 
278   // Event handlers
279 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
280   void geometryChanged(const QRectF& newGeometry, const QRectF& oldGeometry) override;
281 #else
282   void geometryChange(const QRectF& newGeometry, const QRectF& oldGeometry) override;
283 #endif
284 
285   /**
286    * Check the scenegraph backend and graphics API being used.
287    */
288   bool checkGraphicsBackend();
289 
290 private:
291   QQuickVTKRenderWindow(const QQuickVTKRenderWindow&) = delete;
292   void operator=(const QQuickVTKRenderWindow) = delete;
293 };
294 
295 #endif // QQuickVTKRenderWindow_h
296