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