1 | // Copyright (C) 2016 Klaralvdalens Datakonsult AB (KDAB). |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "scene3drenderer_p.h" |
5 | |
6 | #include <Qt3DCore/qaspectengine.h> |
7 | #include <Qt3DRender/qrenderaspect.h> |
8 | #include <QtCore/qthread.h> |
9 | #include <qopenglcontext.h> |
10 | #include <qopenglframebufferobject.h> |
11 | #include <QtQuick/qquickwindow.h> |
12 | #include <rhi/qrhi.h> |
13 | |
14 | #include <Qt3DRender/private/qrenderaspect_p.h> |
15 | #include <Qt3DRender/private/abstractrenderer_p.h> |
16 | #include <Qt3DCore/private/qaspectengine_p.h> |
17 | #include <Qt3DCore/private/qaspectmanager_p.h> |
18 | #include <Qt3DCore/private/qchangearbiter_p.h> |
19 | #include <Qt3DCore/private/qservicelocator_p.h> |
20 | |
21 | #include <scene3ditem_p.h> |
22 | #include <scene3dlogging_p.h> |
23 | #include <scene3dsgnode_p.h> |
24 | |
25 | #include <QtQuick/private/qquickwindow_p.h> |
26 | |
27 | QT_BEGIN_NAMESPACE |
28 | |
29 | namespace Qt3DRender { |
30 | |
31 | class ContextSaver |
32 | { |
33 | public: |
34 | explicit ContextSaver(QOpenGLContext *context = QOpenGLContext::currentContext()) |
35 | : m_context(context), |
36 | m_surface(context ? context->surface() : nullptr) |
37 | { |
38 | } |
39 | |
40 | ~ContextSaver() |
41 | { |
42 | if (m_context && m_context->surface() != m_surface) |
43 | m_context->makeCurrent(surface: m_surface); |
44 | } |
45 | |
46 | QOpenGLContext *context() const { return m_context; } |
47 | QSurface *surface() const { return m_surface; } |
48 | |
49 | private: |
50 | QOpenGLContext * const m_context; |
51 | QSurface * const m_surface; |
52 | }; |
53 | |
54 | /*! |
55 | \class Qt3DRender::Scene3DRenderer |
56 | \internal |
57 | |
58 | \brief The Scene3DRenderer class takes care of rendering a Qt3D scene |
59 | within a Framebuffer object to be used by the QtQuick 2 renderer. |
60 | |
61 | The Scene3DRenderer class renders a Qt3D scene as provided by a Scene3DItem. |
62 | It owns the aspectEngine even though it doesn't instantiate it. |
63 | |
64 | The render loop goes as follows: |
65 | \list |
66 | \li The main thread runs, drives Animations, etc. and causes changes to be |
67 | reported to the Qt3D change arbiter. The first change reported will cause |
68 | the scene3drenderer to be marked dirty. |
69 | \li The QtQuick render thread starts a new frame, synchronizes the scene |
70 | graph and emits afterSynchronizing. This will trigger some preparational |
71 | steps for rendering and mark the QSGNode dirty if the Scene3DRenderer is |
72 | dirty. |
73 | \li The QtQuick render loop emits beforeRendering. If we're marked dirty or |
74 | if the renderPolicy is set to Always, we'll ask the Qt3D renderer aspect to |
75 | render. That call is blocking. If the aspect jobs are not done, yet, the |
76 | renderer will exit early and we skip a frame. |
77 | \endlist |
78 | |
79 | The shutdown procedure is a two steps process that goes as follow: |
80 | |
81 | \list |
82 | \li The window is closed |
83 | |
84 | \li This triggers the windowsChanged signal which the Scene3DRenderer |
85 | uses to perform the necessary cleanups in the QSGRenderThread (destroys |
86 | DebugLogger ...) with the shutdown slot (queued connection). |
87 | |
88 | \li The destroyed signal of the window is also connected to the |
89 | Scene3DRenderer. When triggered in the context of the main thread, the |
90 | cleanup slot is called. |
91 | \endlist |
92 | |
93 | There is an alternate shutdown procedure in case the QQuickItem is |
94 | destroyed with an active window which can happen in the case where the |
95 | Scene3D is used with a QtQuick Loader |
96 | |
97 | In that case the shutdown procedure goes the same except that the destroyed |
98 | signal of the window is not called. Therefore the cleanup method is invoked |
99 | to properly destroy the aspect engine. |
100 | */ |
101 | Scene3DRenderer::Scene3DRenderer() |
102 | : QObject() |
103 | , m_aspectEngine(nullptr) |
104 | , m_renderAspect(nullptr) |
105 | , m_node(nullptr) |
106 | , m_window(nullptr) |
107 | , m_needsShutdown(true) |
108 | , m_shouldRender(false) |
109 | , m_dirtyViews(false) |
110 | , m_skipFrame(false) |
111 | , m_allowRendering(0) |
112 | , m_compositingMode(Scene3DItem::FBO) |
113 | { |
114 | |
115 | } |
116 | |
117 | void Scene3DRenderer::init(Qt3DCore::QAspectEngine *aspectEngine, |
118 | QRenderAspect *renderAspect) |
119 | { |
120 | m_aspectEngine = aspectEngine; |
121 | m_renderAspect = renderAspect; |
122 | m_needsShutdown = true; |
123 | |
124 | // Detect which Rendering backend Qt3D is using |
125 | Qt3DRender::QRenderAspectPrivate *aspectPriv = static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(q: m_renderAspect)); |
126 | Qt3DRender::Render::AbstractRenderer *renderer = aspectPriv->m_renderer; |
127 | |
128 | const bool isRHI = renderer->api() == API::RHI; |
129 | if (isRHI) |
130 | m_quickRenderer = new Scene3DRenderer::RHIRenderer; |
131 | else |
132 | m_quickRenderer = new Scene3DRenderer::GLRenderer; |
133 | m_quickRenderer->initialize(scene3DRenderer: this, renderer); |
134 | } |
135 | |
136 | void Scene3DRenderer::setWindow(QQuickWindow *window) |
137 | { |
138 | if (window == m_window) |
139 | return; |
140 | |
141 | QObject::disconnect(receiver: m_window); |
142 | m_window = window; |
143 | |
144 | if (m_window) { |
145 | QObject::connect(sender: m_window, signal: &QQuickWindow::beforeRendering, context: this, |
146 | slot: [this] () { m_quickRenderer->beforeRendering(scene3DRenderer: this); }, type: Qt::DirectConnection); |
147 | QObject::connect(sender: m_window, signal: &QQuickWindow::beforeRenderPassRecording, context: this, |
148 | slot: [this] () { m_quickRenderer->beforeRenderPassRecording(scene3DRenderer: this); }, type: Qt::DirectConnection); |
149 | } else { |
150 | shutdown(); |
151 | } |
152 | } |
153 | |
154 | Scene3DRenderer::~Scene3DRenderer() |
155 | { |
156 | qCDebug(Scene3D) << Q_FUNC_INFO << QThread::currentThread(); |
157 | shutdown(); |
158 | } |
159 | |
160 | Scene3DSGNode *Scene3DRenderer::sgNode() const |
161 | { |
162 | return m_node; |
163 | } |
164 | |
165 | bool Scene3DRenderer::isYUp() const |
166 | { |
167 | return m_quickRenderer ? m_quickRenderer->isYUp() : true; |
168 | } |
169 | |
170 | // Executed in the QtQuick render thread (which may even be the gui/main with QQuickWidget / RenderControl). |
171 | void Scene3DRenderer::shutdown() |
172 | { |
173 | if (!m_needsShutdown) |
174 | return; |
175 | m_needsShutdown = false; |
176 | |
177 | m_quickRenderer->shutdown(sceneRenderer: this); |
178 | delete m_quickRenderer; |
179 | m_quickRenderer = nullptr; |
180 | } |
181 | |
182 | // Render Thread, GUI locked |
183 | void Scene3DRenderer::beforeSynchronize() |
184 | { |
185 | m_quickRenderer->beforeSynchronize(scene3DRenderer: this); |
186 | } |
187 | |
188 | void Scene3DRenderer::allowRender() |
189 | { |
190 | m_allowRendering.release(n: 1); |
191 | } |
192 | |
193 | void Scene3DRenderer::setCompositingMode(Scene3DItem::CompositingMode mode) |
194 | { |
195 | m_compositingMode = mode; |
196 | } |
197 | |
198 | void Scene3DRenderer::setSkipFrame(bool skip) |
199 | { |
200 | m_skipFrame = skip; |
201 | } |
202 | |
203 | void Scene3DRenderer::setMultisample(bool multisample) |
204 | { |
205 | m_multisample = multisample; |
206 | } |
207 | |
208 | void Scene3DRenderer::setBoundingSize(const QSize &size) |
209 | { |
210 | m_boundingRectSize = size; |
211 | } |
212 | |
213 | QOpenGLFramebufferObject *Scene3DRenderer::GLRenderer::createMultisampledFramebufferObject(const QSize &size) |
214 | { |
215 | QOpenGLFramebufferObjectFormat format; |
216 | format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); |
217 | int samples = QSurfaceFormat::defaultFormat().samples(); |
218 | if (samples == -1) |
219 | samples = 4; |
220 | format.setSamples(samples); |
221 | return new QOpenGLFramebufferObject(size, format); |
222 | } |
223 | |
224 | QOpenGLFramebufferObject *Scene3DRenderer::GLRenderer::createFramebufferObject(const QSize &size) |
225 | { |
226 | QOpenGLFramebufferObjectFormat format; |
227 | format.setAttachment(QOpenGLFramebufferObject::Depth); |
228 | return new QOpenGLFramebufferObject(size, format); |
229 | } |
230 | |
231 | void Scene3DRenderer::GLRenderer::initialize(Scene3DRenderer *scene3DRenderer, |
232 | Qt3DRender::Render::AbstractRenderer *renderer) |
233 | { |
234 | Q_UNUSED(scene3DRenderer); |
235 | Q_ASSERT(QOpenGLContext::currentContext()); |
236 | m_renderer = renderer; |
237 | ContextSaver saver; |
238 | Q_ASSERT(renderer->api() == API::OpenGL); |
239 | |
240 | m_renderer->setRenderDriver(Qt3DRender::Render::AbstractRenderer::Scene3D); |
241 | m_renderer->setOpenGLContext(saver.context()); |
242 | m_renderer->initialize(); |
243 | } |
244 | |
245 | void Scene3DRenderer::GLRenderer::beforeSynchronize(Scene3DRenderer *scene3DRenderer) |
246 | { |
247 | // Check size / multisampling |
248 | QQuickWindow *window = scene3DRenderer->m_window; |
249 | |
250 | if (!window) |
251 | return; |
252 | |
253 | // Only render if we are sure aspectManager->processFrame was called prior |
254 | // We could otherwise enter a deadlock state |
255 | if (!scene3DRenderer->m_allowRendering.tryAcquire(n: std::max(a: scene3DRenderer->m_allowRendering.available(), b: 1))) |
256 | return; |
257 | |
258 | // In the case of OnDemand rendering, we still need to get to this |
259 | // point to ensure we have processed jobs for all aspects. |
260 | // We also still need to call render() to allow proceeding with the |
261 | // next frame. However it won't be performing any 3d rendering at all |
262 | // so we do it here and return early. This prevents a costly QtQuick |
263 | // SceneGraph update for nothing |
264 | if (scene3DRenderer->m_skipFrame) { |
265 | scene3DRenderer->m_skipFrame = false; |
266 | ContextSaver saver; |
267 | m_renderer->render(swapBuffers: false); |
268 | return; |
269 | } |
270 | |
271 | scene3DRenderer->m_shouldRender = true; |
272 | |
273 | m_multisample = scene3DRenderer->multisample(); |
274 | const QSize boundingRectSize = scene3DRenderer->boundingSize(); |
275 | const QSize currentSize = boundingRectSize * window->effectiveDevicePixelRatio(); |
276 | const bool sizeHasChanged = currentSize != m_lastSize; |
277 | const bool multisampleHasChanged = m_multisample != m_lastMultisample; |
278 | const bool forceRecreate = sizeHasChanged || multisampleHasChanged; |
279 | // Store the current size as a comparison |
280 | // point for the next frame |
281 | m_lastSize = currentSize; |
282 | m_lastMultisample = m_multisample; |
283 | |
284 | // Rebuild FBO if size/multisampling has changed |
285 | const bool usesFBO = scene3DRenderer->m_compositingMode == Scene3DItem::FBO; |
286 | auto node = scene3DRenderer->m_node; |
287 | if (node == nullptr) { |
288 | node = new Scene3DSGNode(); |
289 | scene3DRenderer->m_node = node; |
290 | } |
291 | if (usesFBO) { |
292 | // Rebuild FBO and textures if never created or a resize has occurred |
293 | if ((m_multisampledFBO.isNull() || forceRecreate) && m_multisample) { |
294 | m_multisampledFBO.reset(other: createMultisampledFramebufferObject(size: m_lastSize)); |
295 | if (m_multisampledFBO->format().samples() == 0 || !QOpenGLFramebufferObject::hasOpenGLFramebufferBlit()) { |
296 | m_multisample = false; |
297 | m_multisampledFBO.reset(other: nullptr); |
298 | } |
299 | } |
300 | |
301 | const bool generateNewTexture = m_finalFBO.isNull() || forceRecreate; |
302 | if (generateNewTexture) { |
303 | m_finalFBO.reset(other: createFramebufferObject(size: m_lastSize)); |
304 | m_textureId = m_finalFBO->texture(); |
305 | m_texture.reset(other: QNativeInterface::QSGOpenGLTexture::fromNative(textureId: m_textureId, window, size: m_finalFBO->size(), options: QQuickWindow::TextureHasAlphaChannel)); |
306 | } |
307 | |
308 | // Set texture on node |
309 | if (!node->texture() || generateNewTexture) |
310 | node->setTexture(m_texture.data()); |
311 | } |
312 | |
313 | // Mark SGNodes as dirty so that QQuick will trigger some rendering |
314 | if (node) |
315 | node->markDirty(bits: QSGNode::DirtyMaterial); |
316 | } |
317 | |
318 | void Scene3DRenderer::GLRenderer::beforeRendering(Scene3DRenderer *scene3DRenderer) |
319 | { |
320 | Q_UNUSED(scene3DRenderer); |
321 | } |
322 | |
323 | void Scene3DRenderer::GLRenderer::beforeRenderPassRecording(Scene3DRenderer *scene3DRenderer) |
324 | { |
325 | // When this is fired, beforeRendering was fired and the RenderPass for QtQuick was |
326 | // started -> meaning the window was cleared but actual draw commands have yet to be |
327 | // recorded. |
328 | // When using the GL Renderer and FBO, that's the state we want to be in. |
329 | |
330 | QMutexLocker l(&scene3DRenderer->m_windowMutex); |
331 | // Lock to ensure the window doesn't change while we are rendering |
332 | if (!scene3DRenderer->m_window || !scene3DRenderer->m_shouldRender) |
333 | return; |
334 | // Reset flag for next frame |
335 | scene3DRenderer->m_shouldRender = false; |
336 | ContextSaver saver; |
337 | |
338 | // Create and bind FBO if using the FBO compositing mode |
339 | const bool usesFBO = scene3DRenderer->m_compositingMode == Scene3DItem::FBO; |
340 | if (usesFBO) { |
341 | // Bind FBO |
342 | if (m_multisample) //Only try to use MSAA when available |
343 | m_multisampledFBO->bind(); |
344 | else |
345 | m_finalFBO->bind(); |
346 | } |
347 | |
348 | // Render Qt3D Scene |
349 | // Qt3D takes care of resetting the GL state to default values |
350 | m_renderer->render(swapBuffers: usesFBO); |
351 | |
352 | // We may have called doneCurrent() so restore the context if the rendering surface was changed |
353 | // Note: keep in mind that the ContextSave also restores the surface when destroyed |
354 | if (saver.context()->surface() != saver.surface()) |
355 | saver.context()->makeCurrent(surface: saver.surface()); |
356 | |
357 | if (usesFBO) { |
358 | if (m_multisample) { |
359 | // Blit multisampled FBO with non multisampled FBO with texture attachment |
360 | const QRect dstRect(QPoint(0, 0), m_finalFBO->size()); |
361 | const QRect srcRect(QPoint(0, 0), m_multisampledFBO->size()); |
362 | QOpenGLFramebufferObject::blitFramebuffer(target: m_finalFBO.data(), targetRect: dstRect, |
363 | source: m_multisampledFBO.data(), sourceRect: srcRect, |
364 | GL_COLOR_BUFFER_BIT, |
365 | GL_NEAREST, |
366 | readColorAttachmentIndex: 0, drawColorAttachmentIndex: 0, |
367 | restorePolicy: QOpenGLFramebufferObject::DontRestoreFramebufferBinding); |
368 | } |
369 | |
370 | // Restore QtQuick FBO |
371 | QOpenGLFramebufferObject::bindDefault(); |
372 | |
373 | // Only show the node once Qt3D has rendered to it |
374 | // Avoids showing garbage on the first frame |
375 | if (scene3DRenderer->m_node) |
376 | scene3DRenderer->m_node->show(); |
377 | } |
378 | } |
379 | |
380 | void Scene3DRenderer::GLRenderer::shutdown(Scene3DRenderer *sceneRenderer) |
381 | { |
382 | // Shutdown the Renderer Aspect while the OpenGL context |
383 | // is still valid |
384 | if (sceneRenderer->m_renderAspect) |
385 | m_renderer->shutdown(); |
386 | |
387 | m_finalFBO.reset(); |
388 | m_multisampledFBO.reset(); |
389 | } |
390 | |
391 | void Scene3DRenderer::RHIRenderer::initialize(Scene3DRenderer *scene3DRenderer, |
392 | Qt3DRender::Render::AbstractRenderer *renderer) |
393 | { |
394 | QQuickWindow *window = scene3DRenderer->m_window; |
395 | Q_ASSERT(window); |
396 | Q_ASSERT(renderer->api() == API::RHI); |
397 | |
398 | // Retrieve RHI context |
399 | QSGRendererInterface *rif = window->rendererInterface(); |
400 | const bool isRhi = QSGRendererInterface::isApiRhiBased(api: rif->graphicsApi()); |
401 | Q_ASSERT(isRhi); |
402 | if (isRhi) { |
403 | m_rhi = static_cast<QRhi *>(rif->getResource(window, resource: QSGRendererInterface::RhiResource)); |
404 | if (!m_rhi) |
405 | qFatal(msg: "No QRhi from QQuickWindow, this cannot happen" ); |
406 | m_renderer = renderer; |
407 | m_renderer->setRenderDriver(Qt3DRender::Render::AbstractRenderer::Scene3D); |
408 | m_renderer->setRHIContext(m_rhi); |
409 | m_renderer->initialize(); |
410 | } |
411 | } |
412 | |
413 | void Scene3DRenderer::RHIRenderer::beforeSynchronize(Scene3DRenderer *scene3DRenderer) |
414 | { |
415 | // Check size / multisampling |
416 | QQuickWindow *window = scene3DRenderer->m_window; |
417 | |
418 | if (!window) |
419 | return; |
420 | |
421 | // Only render if we are sure aspectManager->processFrame was called prior |
422 | // We could otherwise enter a deadlock state |
423 | if (!scene3DRenderer->m_allowRendering.tryAcquire(n: std::max(a: scene3DRenderer->m_allowRendering.available(), b: 1))) |
424 | return; |
425 | |
426 | // In the case of OnDemand rendering, we still need to get to this |
427 | // point to ensure we have processed jobs for all aspects. |
428 | // We also still need to call render() to allow proceeding with the |
429 | // next frame. However it won't be performing any 3d rendering at all |
430 | // so we do it here and return early. This prevents a costly QtQuick |
431 | // SceneGraph update for nothing |
432 | if (scene3DRenderer->m_skipFrame) { |
433 | scene3DRenderer->m_skipFrame = false; |
434 | m_renderer->render(swapBuffers: false); |
435 | return; |
436 | } |
437 | |
438 | scene3DRenderer->m_shouldRender = true; |
439 | |
440 | const QSize boundingRectSize = scene3DRenderer->boundingSize(); |
441 | const QSize currentSize = boundingRectSize * window->effectiveDevicePixelRatio(); |
442 | const bool sizeHasChanged = currentSize != m_lastSize; |
443 | const bool multisampleHasChanged = scene3DRenderer->multisample() != m_lastMultisample; |
444 | // Store the current size and multisample as a comparison point for the next frame |
445 | m_lastMultisample = scene3DRenderer->multisample(); |
446 | m_lastSize = currentSize; |
447 | |
448 | const bool forceRecreate = sizeHasChanged || multisampleHasChanged; |
449 | const bool usesFBO = scene3DRenderer->m_compositingMode == Scene3DItem::FBO; |
450 | // Not sure how we could support underlay rendering properly given Qt3D RHI will render into its own |
451 | // RHI RenderPasses prior to QtQuick and beginning a new RenderPass clears the screen |
452 | Q_ASSERT(usesFBO); |
453 | const bool generateNewTexture = m_texture.isNull() || forceRecreate; |
454 | const int samples = m_lastMultisample ? 4 : 1; |
455 | |
456 | if (generateNewTexture) { |
457 | releaseRHIResources(); |
458 | |
459 | // Depth / Stencil |
460 | m_rhiDepthRenderBuffer = m_rhi->newRenderBuffer(type: QRhiRenderBuffer::DepthStencil, pixelSize: m_lastSize, sampleCount: samples); |
461 | m_rhiDepthRenderBuffer->create(); |
462 | |
463 | // Color Attachment or Color Resolutin texture (if using MSAA) |
464 | m_rhiTexture = m_rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: m_lastSize, sampleCount: 1, flags: QRhiTexture::RenderTarget|QRhiTexture::UsedAsTransferSource); |
465 | m_rhiTexture->create(); |
466 | |
467 | // Color Attachment description |
468 | QRhiTextureRenderTargetDescription renderTargetDesc; |
469 | renderTargetDesc.setDepthStencilBuffer(m_rhiDepthRenderBuffer); |
470 | if (samples > 1) { |
471 | m_rhiMSAARenderBuffer = m_rhi->newRenderBuffer(type: QRhiRenderBuffer::Color, pixelSize: m_lastSize, sampleCount: samples, flags: {}, backingFormatHint: m_rhiTexture->format()); |
472 | m_rhiMSAARenderBuffer->create(); |
473 | QRhiColorAttachment att; |
474 | att.setRenderBuffer(m_rhiMSAARenderBuffer); |
475 | att.setResolveTexture(m_rhiTexture); |
476 | renderTargetDesc.setColorAttachments({ att }); |
477 | } else { |
478 | renderTargetDesc.setColorAttachments({ m_rhiTexture }); |
479 | } |
480 | |
481 | m_rhiRenderTarget = m_rhi->newTextureRenderTarget(desc: renderTargetDesc); |
482 | m_rhiRenderTargetPassDescriptor = m_rhiRenderTarget->newCompatibleRenderPassDescriptor(); |
483 | m_rhiRenderTarget->setRenderPassDescriptor(m_rhiRenderTargetPassDescriptor); |
484 | m_rhiRenderTarget->create(); |
485 | |
486 | // Create QSGTexture from QRhiTexture |
487 | auto *windowPriv = QQuickWindowPrivate::get(c: window); |
488 | m_texture.reset(other: windowPriv->createTextureFromNativeTexture(nativeObjectHandle: m_rhiTexture->nativeTexture().object, |
489 | nativeLayoutOrState: 0, size: m_lastSize, options: QQuickWindow::TextureHasAlphaChannel)); |
490 | |
491 | // Set the Default RenderTarget to use on the RHI Renderer |
492 | // Note: this will release all pipelines using previousRenderTarget |
493 | m_renderer->setDefaultRHIRenderTarget(m_rhiRenderTarget); |
494 | } |
495 | |
496 | Scene3DSGNode *node = scene3DRenderer->m_node; |
497 | if (node == nullptr) { |
498 | node = new Scene3DSGNode(); |
499 | scene3DRenderer->m_node = node; |
500 | } |
501 | |
502 | // Set texture on node |
503 | if (!node->texture() || generateNewTexture) |
504 | node->setTexture(m_texture.data()); |
505 | |
506 | // Mark SGNodes as dirty so that QQuick will trigger some rendering |
507 | node->markDirty(bits: QSGNode::DirtyMaterial); |
508 | } |
509 | |
510 | void Scene3DRenderer::RHIRenderer::beforeRendering(Scene3DRenderer *scene3DRenderer) |
511 | { |
512 | // This is fired before QtQuick starts its RenderPass but after a RHI Command Buffer |
513 | // has been created and after the RHI Frame has begun |
514 | |
515 | // This means the swap chain has been set up and that we only need to record commands |
516 | |
517 | // On the Qt3D side this also means we shouldn't be calling m_rhi->beginFrame/endFrame |
518 | // and we should only be rendering using the provided swap chain |
519 | |
520 | // Therefore, if a QRenderSurfaceSelector is used in the FrameGraph to render to another surface, |
521 | // this will not be supported with Scene3D/RHI |
522 | |
523 | |
524 | QMutexLocker l(&scene3DRenderer->m_windowMutex); |
525 | // Lock to ensure the window doesn't change while we are rendering |
526 | if (!scene3DRenderer->m_window || !scene3DRenderer->m_shouldRender) |
527 | return; |
528 | // Reset flag for next frame |
529 | scene3DRenderer->m_shouldRender = false; |
530 | |
531 | // TO DO: We need to check what we do about 3DViews and Composition Mode as those are not |
532 | // directly applicable with RHI |
533 | |
534 | // Retrieve Command Buffer used by QtQuick |
535 | QRhiCommandBuffer *cb = nullptr; |
536 | QSGRendererInterface *rif = scene3DRenderer->m_window ->rendererInterface(); |
537 | QRhiSwapChain *swapchain = static_cast<QRhiSwapChain *>(rif->getResource(window: scene3DRenderer->m_window, resource: QSGRendererInterface::RhiSwapchainResource)); |
538 | if (swapchain) { |
539 | cb = swapchain->currentFrameCommandBuffer(); |
540 | } else { |
541 | cb = static_cast<QRhiCommandBuffer *>(rif->getResource(window: scene3DRenderer->m_window, resource: QSGRendererInterface::RhiRedirectCommandBuffer)); |
542 | } |
543 | |
544 | Q_ASSERT(cb); |
545 | // Tell Qt3D renderer to use provided command buffer |
546 | m_renderer->setRHICommandBuffer(cb); |
547 | m_renderer->render(swapBuffers: false); |
548 | |
549 | // Only show the node once Qt3D has rendered to it |
550 | // Avoids showing garbage on the first frame |
551 | if (scene3DRenderer->m_node) |
552 | scene3DRenderer->m_node->show(); |
553 | } |
554 | |
555 | void Scene3DRenderer::RHIRenderer::beforeRenderPassRecording(Scene3DRenderer *scene3DRenderer) |
556 | { |
557 | Q_UNUSED(scene3DRenderer); |
558 | } |
559 | |
560 | void Scene3DRenderer::RHIRenderer::shutdown(Scene3DRenderer *scene3DRenderer) |
561 | { |
562 | if (scene3DRenderer->m_renderAspect) |
563 | m_renderer->shutdown(); |
564 | |
565 | releaseRHIResources(); |
566 | } |
567 | |
568 | bool Scene3DRenderer::RHIRenderer::isYUp() const |
569 | { |
570 | return m_rhi->isYUpInFramebuffer(); |
571 | } |
572 | |
573 | void Scene3DRenderer::RHIRenderer::releaseRHIResources() |
574 | { |
575 | delete m_rhiRenderTarget; |
576 | m_rhiRenderTarget = nullptr; |
577 | |
578 | delete m_rhiTexture; |
579 | m_rhiTexture = nullptr; |
580 | |
581 | delete m_rhiDepthRenderBuffer; |
582 | m_rhiDepthRenderBuffer = nullptr; |
583 | |
584 | delete m_rhiMSAARenderBuffer; |
585 | m_rhiMSAARenderBuffer = nullptr; |
586 | |
587 | delete m_rhiColorRenderBuffer; |
588 | m_rhiColorRenderBuffer = nullptr; |
589 | |
590 | delete m_rhiRenderTargetPassDescriptor; |
591 | m_rhiRenderTargetPassDescriptor = nullptr; |
592 | } |
593 | |
594 | Scene3DRenderer::QuickRenderer::QuickRenderer() {} |
595 | Scene3DRenderer::QuickRenderer::~QuickRenderer() {} |
596 | |
597 | } // namespace Qt3DRender |
598 | |
599 | QT_END_NAMESPACE |
600 | |
601 | #include "moc_scene3drenderer_p.cpp" |
602 | |