1 /*=========================================================================
2 
3   Program:   Visualization Toolkit
4   Module:    QQuickVTKRenderWindow.cxx
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 #include "QQuickVTKRenderWindow.h"
16 
17 // vtk includes
18 #include "QQuickVTKInteractorAdapter.h"
19 #include "QVTKInteractor.h"
20 #include "QVTKRenderWindowAdapter.h"
21 #include "vtkGenericOpenGLRenderWindow.h"
22 #include "vtkImageData.h"
23 #include "vtkInteractorStyleTrackballCamera.h"
24 #include "vtkOpenGLState.h"
25 #include "vtkRenderWindowInteractor.h"
26 #include "vtkRenderer.h"
27 #include "vtkWindowToImageFilter.h"
28 
29 // Qt includes
30 #include <QQuickWindow>
31 #include <QSGRendererInterface>
32 #include <QSurfaceFormat>
33 
34 //-------------------------------------------------------------------------------------------------
QQuickVTKRenderWindow(QQuickItem * parent)35 QQuickVTKRenderWindow::QQuickVTKRenderWindow(QQuickItem* parent)
36   : Superclass(parent)
37 {
38   vtkNew<vtkGenericOpenGLRenderWindow> renWin;
39   this->setRenderWindow(renWin);
40   this->m_interactorAdapter = new QQuickVTKInteractorAdapter(this);
41   QObject::connect(
42     this, &QQuickItem::windowChanged, this, &QQuickVTKRenderWindow::handleWindowChanged);
43 
44   // Set a standard object name
45   this->setObjectName("QQuickVTKRenderWindow");
46 }
47 
48 //-------------------------------------------------------------------------------------------------
setupGraphicsBackend()49 void QQuickVTKRenderWindow::setupGraphicsBackend()
50 {
51   QSurfaceFormat fmt = QVTKRenderWindowAdapter::defaultFormat(false);
52   // By default QtQuick sets the alpha buffer size to 0. We follow the same thing here to prevent a
53   // transparent background.
54   fmt.setAlphaBufferSize(0);
55   QSurfaceFormat::setDefaultFormat(fmt);
56 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
57   QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGLRhi);
58 #endif
59 }
60 
61 //-------------------------------------------------------------------------------------------------
~QQuickVTKRenderWindow()62 QQuickVTKRenderWindow::~QQuickVTKRenderWindow()
63 {
64   this->m_renderWindow = nullptr;
65 }
66 
67 //-------------------------------------------------------------------------------------------------
sync()68 void QQuickVTKRenderWindow::sync()
69 {
70   if (!this->isVisible())
71   {
72     return;
73   }
74 
75   if (!this->m_renderWindow)
76   {
77     return;
78   }
79 
80   QSize windowSize = window()->size() * window()->devicePixelRatio();
81   this->m_renderWindow->SetSize(windowSize.width(), windowSize.height());
82   if (auto iren = this->m_renderWindow->GetInteractor())
83   {
84     iren->SetSize(windowSize.width(), windowSize.height());
85     m_interactorAdapter->ProcessEvents(iren);
86   }
87 }
88 
89 //-------------------------------------------------------------------------------------------------
init()90 void QQuickVTKRenderWindow::init()
91 {
92   if (!this->isVisible())
93   {
94     return;
95   }
96 
97   if (!this->m_renderWindow)
98   {
99     // no render window set, just fill with white.
100     QOpenGLFunctions* f = QOpenGLContext::currentContext()->functions();
101     f->glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
102     f->glClear(GL_COLOR_BUFFER_BIT);
103     return;
104   }
105 
106   if (!this->checkGraphicsBackend())
107   {
108     return;
109   }
110 
111   auto iren = this->m_renderWindow->GetInteractor();
112   if (!this->m_initialized)
113   {
114     initializeOpenGLFunctions();
115     if (iren)
116     {
117       iren->Initialize();
118     }
119     this->m_renderWindow->SetMapped(true);
120     this->m_renderWindow->SetIsCurrent(true);
121 
122     // Since the context is being setup, call OpenGLInitContext
123     this->m_renderWindow->SetForceMaximumHardwareLineWidth(1);
124     this->m_renderWindow->SetOwnContext(false);
125     this->m_renderWindow->OpenGLInitContext();
126 
127     // Add a dummy renderer covering the whole size of the render window as a transparent viewport.
128     // Without this, the QtQuick rendering is stenciled out.
129     this->m_dummyRenderer->InteractiveOff();
130     this->m_dummyRenderer->SetLayer(1);
131     this->m_renderWindow->AddRenderer(this->m_dummyRenderer);
132     this->m_renderWindow->SetNumberOfLayers(2);
133 
134     m_initialized = true;
135   }
136 }
137 
138 //-------------------------------------------------------------------------------------------------
paint()139 void QQuickVTKRenderWindow::paint()
140 {
141   if (!this->isVisible())
142   {
143     return;
144   }
145 
146   if (!this->m_renderWindow)
147   {
148     // no render window set, just fill with white.
149     QOpenGLFunctions* f = QOpenGLContext::currentContext()->functions();
150     f->glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
151     f->glClear(GL_COLOR_BUFFER_BIT);
152     return;
153   }
154 
155 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
156   // Explicitly call init here if using an older Qt version with no
157   // beforeRenderPassRecording API available
158   this->init();
159 #endif
160 
161   if (!this->checkGraphicsBackend())
162   {
163     return;
164   }
165 
166 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
167   this->window()->beginExternalCommands();
168 #endif
169   auto iren = this->m_renderWindow->GetInteractor();
170   auto ostate = this->m_renderWindow->GetState();
171   ostate->Reset();
172   ostate->Push();
173   // By default, Qt sets the depth function to GL_LESS but VTK expects GL_LEQUAL
174   ostate->vtkglDepthFunc(GL_LEQUAL);
175 
176   // auto iren = this->m_renderWindow->GetInteractor();
177   this->m_renderWindow->SetReadyForRendering(true);
178   if (iren)
179   {
180     iren->Render();
181   }
182   else
183   {
184     this->m_renderWindow->Render();
185   }
186 
187   if (this->m_screenshotScheduled)
188   {
189     this->m_screenshotFilter->SetInput(this->m_renderWindow);
190     this->m_screenshotFilter->SetReadFrontBuffer(false);
191     this->m_screenshotFilter->SetInputBufferTypeToRGB();
192     this->m_screenshotFilter->Update();
193     this->m_screenshotScheduled = false;
194   }
195   this->m_renderWindow->SetReadyForRendering(false);
196 
197   ostate->Pop();
198 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
199   this->window()->endExternalCommands();
200 #endif
201 }
202 
203 //-------------------------------------------------------------------------------------------------
cleanup()204 void QQuickVTKRenderWindow::cleanup()
205 {
206   if (this->m_renderWindow)
207   {
208     this->m_renderWindow->ReleaseGraphicsResources(this->m_renderWindow);
209   }
210 }
211 
212 //-------------------------------------------------------------------------------------------------
handleWindowChanged(QQuickWindow * w)213 void QQuickVTKRenderWindow::handleWindowChanged(QQuickWindow* w)
214 {
215   this->m_interactorAdapter->setQQuickWindow(w);
216   if (w)
217   {
218 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
219     // Do not clear the scenegraph before the QML rendering
220     // to preserve the VTK render
221     w->setClearBeforeRendering(false);
222 #endif
223     // This allows the cleanup method to be called on the render thread
224     w->setPersistentSceneGraph(false);
225   }
226 }
227 
228 //-------------------------------------------------------------------------------------------------
setRenderWindow(vtkRenderWindow * renWin)229 void QQuickVTKRenderWindow::setRenderWindow(vtkRenderWindow* renWin)
230 {
231   auto gwin = vtkGenericOpenGLRenderWindow::SafeDownCast(renWin);
232   if (renWin != nullptr && gwin == nullptr)
233   {
234     qDebug() << "QQuickVTKRenderWindow requires a `vtkGenericOpenGLRenderWindow`. `"
235              << renWin->GetClassName() << "` is not supported.";
236   }
237   this->setRenderWindow(gwin);
238 }
239 
240 //-------------------------------------------------------------------------------------------------
setRenderWindow(vtkGenericOpenGLRenderWindow * renWin)241 void QQuickVTKRenderWindow::setRenderWindow(vtkGenericOpenGLRenderWindow* renWin)
242 {
243   if (this->m_renderWindow == renWin)
244   {
245     return;
246   }
247 
248   this->m_renderWindow = renWin;
249   this->m_initialized = false;
250 
251   if (this->m_renderWindow)
252   {
253     this->m_renderWindow->SetMultiSamples(0);
254     this->m_renderWindow->SetReadyForRendering(false);
255     this->m_renderWindow->SetFrameBlitModeToBlitToHardware();
256     vtkNew<QVTKInteractor> iren;
257     iren->SetRenderWindow(this->m_renderWindow);
258 
259     // now set the default style
260     vtkNew<vtkInteractorStyleTrackballCamera> style;
261     iren->SetInteractorStyle(style);
262 
263     this->m_renderWindow->SetReadyForRendering(false);
264   }
265 }
266 
267 //-------------------------------------------------------------------------------------------------
renderWindow() const268 vtkRenderWindow* QQuickVTKRenderWindow::renderWindow() const
269 {
270   return this->m_renderWindow;
271 }
272 
273 //-------------------------------------------------------------------------------------------------
mapToViewport(const QRectF & rect,double viewport[4])274 void QQuickVTKRenderWindow::mapToViewport(const QRectF& rect, double viewport[4])
275 {
276   viewport[0] = rect.topLeft().x();
277   viewport[1] = rect.topLeft().y();
278   viewport[2] = rect.bottomRight().x();
279   viewport[3] = rect.bottomRight().y();
280 
281   if (this->m_renderWindow)
282   {
283     int* windowSize = this->m_renderWindow->GetSize();
284     if (windowSize && windowSize[0] != 0 && windowSize[1] != 0)
285     {
286       viewport[0] = viewport[0] / (windowSize[0] - 1.0);
287       viewport[1] = viewport[1] / (windowSize[1] - 1.0);
288       viewport[2] = viewport[2] / (windowSize[0] - 1.0);
289       viewport[3] = viewport[3] / (windowSize[1] - 1.0);
290     }
291   }
292 
293   // Change to quadrant I (vtk) from IV (Qt)
294   double tmp = 1.0 - viewport[1];
295   viewport[1] = 1.0 - viewport[3];
296   viewport[3] = tmp;
297 
298   for (int i = 0; i < 3; ++i)
299   {
300     viewport[i] = viewport[i] > 0.0 ? viewport[i] : 0.0;
301     viewport[i] = viewport[i] > 1.0 ? 1.0 : viewport[i];
302   }
303 }
304 
305 //-------------------------------------------------------------------------------------------------
306 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
geometryChanged(const QRectF & newGeometry,const QRectF & oldGeometry)307 void QQuickVTKRenderWindow::geometryChanged(const QRectF& newGeometry, const QRectF& oldGeometry)
308 #else
309 void QQuickVTKRenderWindow::geometryChange(const QRectF& newGeometry, const QRectF& oldGeometry)
310 #endif
311 {
312   m_interactorAdapter->QueueGeometryChanged(newGeometry, oldGeometry);
313 
314 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
315   Superclass::geometryChanged(newGeometry, oldGeometry);
316 #else
317   Superclass::geometryChange(newGeometry, oldGeometry);
318 #endif
319 }
320 
321 //-------------------------------------------------------------------------------------------------
interactorAdapter() const322 QPointer<QQuickVTKInteractorAdapter> QQuickVTKRenderWindow::interactorAdapter() const
323 {
324   return this->m_interactorAdapter;
325 }
326 
327 //-------------------------------------------------------------------------------------------------
captureScreenshot()328 vtkSmartPointer<vtkImageData> QQuickVTKRenderWindow::captureScreenshot()
329 {
330   double viewport[4] = { 0, 0, 1, 1 };
331   return this->captureScreenshot(viewport);
332 }
333 
334 //-------------------------------------------------------------------------------------------------
captureScreenshot(double * viewport)335 vtkSmartPointer<vtkImageData> QQuickVTKRenderWindow::captureScreenshot(double* viewport)
336 {
337   if (!this->window())
338   {
339     return nullptr;
340   }
341   this->m_screenshotScheduled = true;
342   this->m_screenshotFilter->SetViewport(viewport);
343   this->renderNow();
344   return this->m_screenshotFilter->GetOutput();
345 }
346 
347 //-------------------------------------------------------------------------------------------------
renderNow()348 void QQuickVTKRenderWindow::renderNow()
349 {
350   if (!this->window())
351   {
352     return;
353   }
354   // Schedule a scenegraph update
355   this->window()->update();
356   // Wait for the update to complete
357   QEventLoop loop;
358   QObject::connect(this->window(), &QQuickWindow::afterRendering, &loop, &QEventLoop::quit);
359   loop.exec();
360 }
361 
362 //-------------------------------------------------------------------------------------------------
render()363 void QQuickVTKRenderWindow::render()
364 {
365   if (this->window())
366   {
367     this->window()->update();
368   }
369 }
370 
371 //-------------------------------------------------------------------------------------------------
isInitialized() const372 bool QQuickVTKRenderWindow::isInitialized() const
373 {
374   return this->m_initialized;
375 }
376 
377 //-------------------------------------------------------------------------------------------------
checkGraphicsBackend()378 bool QQuickVTKRenderWindow::checkGraphicsBackend()
379 {
380   // Enforce the use of OpenGL API
381   QSGRendererInterface* rif = this->window()->rendererInterface();
382   auto gApi = rif->graphicsApi();
383   if (!(gApi == QSGRendererInterface::OpenGL
384 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
385         || gApi == QSGRendererInterface::OpenGLRhi
386 #endif
387         ))
388   {
389     qCritical(R"***(Error: QtQuick scenegraph is using an unsupported graphics API: %d.
390 Set the QSG_INFO environment variable to get more information.
391 Use QQuickVTKRenderWindow::setupGraphicsBackend() to set the right backend.)***",
392       gApi);
393     return false;
394   }
395   return true;
396 }
397