1// Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB).
2// Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include "renderer_p.h"
6
7#include <Qt3DCore/qentity.h>
8#include <Qt3DCore/private/vector_helper_p.h>
9
10#include <Qt3DRender/qmaterial.h>
11#include <Qt3DRender/qmesh.h>
12#include <Qt3DRender/qrenderpass.h>
13#include <Qt3DRender/qshaderprogram.h>
14#include <Qt3DRender/qtechnique.h>
15#include <Qt3DRender/qrenderaspect.h>
16#include <Qt3DRender/qeffect.h>
17
18#include <Qt3DRender/private/qsceneimporter_p.h>
19#include <Qt3DRender/private/renderstates_p.h>
20#include <Qt3DRender/private/cameraselectornode_p.h>
21#include <Qt3DRender/private/framegraphvisitor_p.h>
22#include <Qt3DRender/private/cameralens_p.h>
23#include <Qt3DRender/private/entity_p.h>
24#include <Qt3DRender/private/renderlogging_p.h>
25#include <Qt3DRender/private/material_p.h>
26#include <Qt3DRender/private/renderpassfilternode_p.h>
27#include <Qt3DRender/private/shader_p.h>
28#include <Qt3DRender/private/buffer_p.h>
29#include <Qt3DRender/private/technique_p.h>
30#include <Qt3DRender/private/scenemanager_p.h>
31#include <Qt3DRender/private/techniquefilternode_p.h>
32#include <Qt3DRender/private/viewportnode_p.h>
33#include <Qt3DRender/private/vsyncframeadvanceservice_p.h>
34#include <Qt3DRender/private/managers_p.h>
35#include <Qt3DRender/private/buffermanager_p.h>
36#include <Qt3DRender/private/nodemanagers_p.h>
37#include <Qt3DRender/private/geometryrenderermanager_p.h>
38#include <Qt3DRender/private/techniquemanager_p.h>
39#include <Qt3DRender/private/platformsurfacefilter_p.h> // for SurfaceLocker
40#include <Qt3DRender/private/rendercapture_p.h>
41#include <Qt3DRender/private/updatelevelofdetailjob_p.h>
42#include <Qt3DRender/private/buffercapture_p.h>
43#include <Qt3DRender/private/offscreensurfacehelper_p.h>
44#include <Qt3DRender/private/subtreeenabler_p.h>
45#include <Qt3DRender/private/qshaderprogrambuilder_p.h>
46#include <Qt3DRender/private/qshaderprogram_p.h>
47#include <Qt3DRender/private/qrenderaspect_p.h>
48#ifndef Q_OS_INTEGRITY
49#include <imguirenderer_p.h>
50#endif
51
52#include <Qt3DRender/qcameralens.h>
53#include <Qt3DCore/private/qabstractaspectjobmanager_p.h>
54#include <Qt3DCore/private/qaspectmanager_p.h>
55#include <Qt3DCore/private/qsysteminformationservice_p.h>
56#include <Qt3DCore/private/qsysteminformationservice_p_p.h>
57#include <Qt3DRender/private/resourceaccessor_p.h>
58#include <Qt3DRender/private/renderlogging_p.h>
59#include <Qt3DRender/private/renderstateset_p.h>
60#include <Qt3DRender/private/setfence_p.h>
61#include <Qt3DRender/private/qsetfence_p.h>
62#include <Qt3DRender/private/waitfence_p.h>
63#include <Qt3DRender/private/renderqueue_p.h>
64
65#include <glbuffer_p.h>
66#include <graphicscontext_p.h>
67#include <rendercommand_p.h>
68#include <renderview_p.h>
69#include <gltexture_p.h>
70#include <openglvertexarrayobject_p.h>
71#include <renderviewbuilder_p.h>
72#include <glresourcemanagers_p.h>
73#include <commandexecuter_p.h>
74
75#include <QStack>
76#include <QOffscreenSurface>
77#include <QSurface>
78#include <QElapsedTimer>
79#include <QLibraryInfo>
80#include <QMutexLocker>
81#include <QPluginLoader>
82#include <QDir>
83#include <QUrl>
84#include <QOffscreenSurface>
85#include <QWindow>
86#include <QThread>
87
88#include <QtGui/private/qopenglcontext_p.h>
89#include "frameprofiler_p.h"
90
91QT_BEGIN_NAMESPACE
92
93namespace Qt3DRender {
94namespace Render {
95namespace OpenGL {
96
97using RendererCache = Render::RendererCache<RenderCommand>;
98
99namespace {
100
101class CachingLightGatherer : public LightGatherer {
102public:
103 CachingLightGatherer(RendererCache *cache)
104 : LightGatherer()
105 , m_cache(cache)
106 {
107 }
108
109 void run() override
110 {
111 LightGatherer::run();
112
113 m_cache->gatheredLights = std::move(lights());
114 std::sort(first: m_cache->gatheredLights.begin(), last: m_cache->gatheredLights.end(),
115 comp: [] (const LightSource &a, const LightSource &b) {
116 return a.entity < b.entity;
117 });
118
119 m_cache->environmentLight = environmentLight();
120 }
121
122private:
123 RendererCache *m_cache;
124};
125
126class CachingRenderableEntityFilter : public RenderableEntityFilter {
127public:
128 CachingRenderableEntityFilter(RendererCache *cache)
129 : RenderableEntityFilter()
130 , m_cache(cache)
131 {
132
133 }
134
135 void run() override
136 {
137 RenderableEntityFilter::run();
138
139 std::vector<Entity *> selectedEntities = std::move(filteredEntities());
140 std::sort(first: selectedEntities.begin(), last: selectedEntities.end());
141
142 m_cache->renderableEntities = std::move(selectedEntities);
143 }
144
145private:
146 RendererCache *m_cache;
147};
148
149class CachingComputableEntityFilter : public ComputableEntityFilter {
150public:
151 CachingComputableEntityFilter(RendererCache *cache)
152 : ComputableEntityFilter()
153 , m_cache(cache)
154 {
155
156 }
157
158 void run() override
159 {
160 ComputableEntityFilter::run();
161
162 std::vector<Entity *> selectedEntities = std::move(filteredEntities());
163 std::sort(first: selectedEntities.begin(), last: selectedEntities.end());
164
165 m_cache->computeEntities = std::move(selectedEntities);
166 }
167
168private:
169 RendererCache *m_cache;
170};
171
172} // anonymous
173
174/*!
175 \internal
176
177 Renderer shutdown procedure:
178
179 Since the renderer relies on the surface and OpenGLContext to perform its cleanup,
180 it is shutdown when the surface is set to nullptr
181
182 When the surface is set to nullptr this will request the RenderThread to terminate
183 and will prevent createRenderBinJobs from returning a set of jobs as there is nothing
184 more to be rendered.
185
186 In turn, this will call shutdown which will make the OpenGL context current one last time
187 to allow cleanups requiring a call to QOpenGLContext::currentContext to execute properly.
188 At the end of that function, the GraphicsContext is set to null.
189
190 At this point though, the QAspectThread is still running its event loop and will only stop
191 a short while after.
192 */
193
194Renderer::Renderer()
195 : m_services(nullptr)
196 , m_aspect(nullptr)
197 , m_nodesManager(nullptr)
198 , m_renderSceneRoot(nullptr)
199 , m_defaultRenderStateSet(nullptr)
200 , m_submissionContext(nullptr)
201 , m_vsyncFrameAdvanceService(new VSyncFrameAdvanceService(false))
202 , m_waitForInitializationToBeCompleted(0)
203 , m_hasBeenInitializedMutex()
204 , m_exposed(0)
205 , m_lastFrameCorrect(0)
206 , m_glContext(nullptr)
207 , m_shareContext(nullptr)
208 , m_time(0)
209 , m_settings(nullptr)
210 , m_updateShaderDataTransformJob(Render::UpdateShaderDataTransformJobPtr::create())
211 , m_cleanupJob(Render::FrameCleanupJobPtr::create())
212 , m_sendBufferCaptureJob(Render::SendBufferCaptureJobPtr::create())
213 , m_filterCompatibleTechniqueJob(FilterCompatibleTechniqueJobPtr::create())
214 , m_lightGathererJob(new CachingLightGatherer(&m_cache))
215 , m_renderableEntityFilterJob(new CachingRenderableEntityFilter(&m_cache))
216 , m_computableEntityFilterJob(new CachingComputableEntityFilter(&m_cache))
217 , m_bufferGathererJob(CreateSynchronizerJobPtr([this] { lookForDirtyBuffers(); }, JobTypes::DirtyBufferGathering, 0))
218 , m_vaoGathererJob(CreateSynchronizerJobPtr([this] { lookForAbandonedVaos(); }, JobTypes::DirtyVaoGathering, 0))
219 , m_textureGathererJob(CreateSynchronizerJobPtr([this] { lookForDirtyTextures(); }, JobTypes::DirtyTextureGathering, 0))
220 , m_introspectShaderJob(CreateSynchronizerPostFramePtr([this] { reloadDirtyShaders(); },
221 [this] (Qt3DCore::QAspectManager *m) { sendShaderChangesToFrontend(m); },
222 JobTypes::DirtyShaderGathering))
223 , m_ownedContext(false)
224 , m_offscreenHelper(nullptr)
225 , m_glResourceManagers(nullptr)
226 , m_commandExecuter(new Qt3DRender::Debug::CommandExecuter(this))
227 , m_shouldSwapBuffers(true)
228 , m_imGuiRenderer(nullptr)
229 , m_jobsInLastFrame(0)
230{
231 // Set renderer as running - it will wait in the context of the
232 // RenderThread for RenderViews to be submitted
233 m_running.fetchAndStoreOrdered(newValue: 1);
234
235 m_introspectShaderJob->addDependency(dependency: m_filterCompatibleTechniqueJob);
236
237 m_filterCompatibleTechniqueJob->setRenderer(this);
238
239 m_defaultRenderStateSet = new RenderStateSet;
240 m_defaultRenderStateSet->addState(state: StateVariant::createState<DepthTest>(GL_LESS));
241 m_defaultRenderStateSet->addState(state: StateVariant::createState<CullFace>(GL_BACK));
242 m_defaultRenderStateSet->addState(state: StateVariant::createState<ColorMask>(values: true, values: true, values: true, values: true));
243}
244
245Renderer::~Renderer()
246{
247 Q_ASSERT(m_running.fetchAndStoreOrdered(0) == 0);
248
249 delete m_defaultRenderStateSet;
250 delete m_glResourceManagers;
251
252 if (!m_ownedContext)
253 QObject::disconnect(m_contextConnection);
254
255#ifndef Q_OS_INTEGRITY
256 delete m_imGuiRenderer;
257#endif
258}
259
260void Renderer::dumpInfo() const
261{
262 qDebug() << Q_FUNC_INFO << "t =" << m_time;
263
264 const ShaderManager *shaderManager = m_nodesManager->shaderManager();
265 qDebug() << "=== Shader Manager ===";
266 qDebug() << *shaderManager;
267
268 const TextureManager *textureManager = m_nodesManager->textureManager();
269 qDebug() << "=== Texture Manager ===";
270 qDebug() << *textureManager;
271
272 const TextureImageManager *textureImageManager = m_nodesManager->textureImageManager();
273 qDebug() << "=== Texture Image Manager ===";
274 qDebug() << *textureImageManager;
275}
276
277void Renderer::setRenderDriver(AbstractRenderer::RenderDriver driver)
278{
279 m_driver = driver;
280}
281
282AbstractRenderer::RenderDriver Renderer::renderDriver() const
283{
284 return m_driver;
285}
286
287qint64 Renderer::time() const
288{
289 return m_time;
290}
291
292void Renderer::setTime(qint64 time)
293{
294 m_time = time;
295}
296
297void Renderer::setJobsInLastFrame(int jobsInLastFrame)
298{
299 m_jobsInLastFrame = jobsInLastFrame;
300}
301
302void Renderer::setAspect(QRenderAspect *aspect)
303{
304 m_aspect = aspect;
305 m_updateShaderDataTransformJob->addDependency(dependency: QRenderAspectPrivate::get(q: aspect)->m_worldTransformJob);
306}
307
308void Renderer::setNodeManagers(NodeManagers *managers)
309{
310 m_nodesManager = managers;
311 m_glResourceManagers = new GLResourceManagers();
312 m_scene2DResourceAccessor.reset(t: new ResourceAccessor(this, m_nodesManager));
313
314 m_updateShaderDataTransformJob->setManagers(m_nodesManager);
315 m_cleanupJob->setManagers(m_nodesManager);
316 m_filterCompatibleTechniqueJob->setManager(m_nodesManager->techniqueManager());
317 m_sendBufferCaptureJob->setManagers(m_nodesManager);
318 m_lightGathererJob->setManager(m_nodesManager->renderNodesManager());
319 m_renderableEntityFilterJob->setManager(m_nodesManager->renderNodesManager());
320 m_computableEntityFilterJob->setManager(m_nodesManager->renderNodesManager());
321}
322
323void Renderer::setServices(Qt3DCore::QServiceLocator *services)
324{
325 m_services = services;
326
327 m_nodesManager->sceneManager()->setDownloadService(m_services->downloadHelperService());
328}
329
330QRenderAspect *Renderer::aspect() const
331{
332 return m_aspect;
333}
334
335NodeManagers *Renderer::nodeManagers() const
336{
337 return m_nodesManager;
338}
339
340/*!
341 \internal
342
343 Return context which can be used to share resources safely
344 with qt3d main render context.
345*/
346QOpenGLContext *Renderer::shareContext() const
347{
348 QMutexLocker lock(&m_shareContextMutex);
349 return m_shareContext ? m_shareContext
350 : (m_submissionContext->openGLContext()
351 ? m_submissionContext->openGLContext()->shareContext()
352 : nullptr);
353}
354
355void Renderer::setOpenGLContext(QOpenGLContext *context)
356{
357 m_glContext = context;
358}
359
360void Renderer::setScreen(QScreen *scr)
361{
362 m_screen = scr;
363}
364
365QScreen *Renderer::screen() const
366{
367 return m_screen;
368}
369
370bool Renderer::accessOpenGLTexture(Qt3DCore::QNodeId nodeId,
371 QOpenGLTexture **texture,
372 QMutex **lock,
373 bool readonly)
374{
375 Texture *tex = m_nodesManager->textureManager()->lookupResource(id: nodeId);
376 if (!tex)
377 return false;
378
379 GLTexture *glTex = m_glResourceManagers->glTextureManager()->lookupResource(id: tex->peerId());
380 if (!glTex)
381 return false;
382
383 if (glTex->isDirty())
384 return false;
385
386 if (!readonly)
387 glTex->setExternalRenderingEnabled(true);
388
389 GLTexture::TextureUpdateInfo texInfo = glTex->createOrUpdateGLTexture();
390 *texture = texInfo.texture;
391
392 if (!readonly)
393 *lock = glTex->externalRenderingLock();
394
395 return true;
396}
397
398QSharedPointer<RenderBackendResourceAccessor> Renderer::resourceAccessor() const
399{
400 return m_scene2DResourceAccessor;
401}
402
403// Called in RenderThread context by the run method of RenderThread
404// RenderThread has locked the mutex already and unlocks it when this
405// method termintates
406void Renderer::initialize()
407{
408 QMutexLocker lock(&m_hasBeenInitializedMutex);
409 m_submissionContext.reset(other: new SubmissionContext);
410 m_submissionContext->setRenderer(this);
411
412 {
413 QMutexLocker lock(&m_shareContextMutex);
414 // If we are using our own context (not provided by QtQuick),
415 // we need to create it
416 if (!m_glContext) {
417 m_glContext = new QOpenGLContext;
418 if (m_screen)
419 m_glContext->setScreen(m_screen);
420 m_glContext->setShareContext(qt_gl_global_share_context());
421
422 // TO DO: Shouldn't we use the highest context available and trust
423 // QOpenGLContext to fall back on the best lowest supported ?
424 const QByteArray debugLoggingMode = qgetenv(varName: "QT3DRENDER_DEBUG_LOGGING");
425
426 if (!debugLoggingMode.isEmpty()) {
427 QSurfaceFormat sf = m_glContext->format();
428 sf.setOption(option: QSurfaceFormat::DebugContext);
429 m_glContext->setFormat(sf);
430 }
431
432 // Create OpenGL context
433
434 if (m_glContext->create())
435 qCDebug(Backend) << "OpenGL context created with actual format" << m_glContext->format();
436 else
437 qCWarning(Backend) << Q_FUNC_INFO << "OpenGL context creation failed";
438 m_ownedContext = true;
439
440 QObject::connect(sender: m_glContext, signal: &QOpenGLContext::aboutToBeDestroyed,
441 slot: [this] { m_frameProfiler.reset(); });
442 } else {
443 // Context is not owned by us, so we need to know if it gets destroyed
444 m_contextConnection = QObject::connect(sender: m_glContext, signal: &QOpenGLContext::aboutToBeDestroyed,
445 slot: [this] { releaseGraphicsResources(); });
446 }
447
448 qCDebug(Backend) << "Qt3D shared context:" << m_glContext->shareContext();
449 qCDebug(Backend) << "Qt global shared context:" << qt_gl_global_share_context();
450
451 if (!m_glContext->shareContext()) {
452 m_shareContext = new QOpenGLContext;
453 if (m_glContext->screen())
454 m_shareContext->setScreen(m_glContext->screen());
455 m_shareContext->setFormat(m_glContext->format());
456 m_shareContext->setShareContext(m_glContext);
457 m_shareContext->create();
458 }
459
460 // Note: we don't have a surface at this point
461 // The context will be made current later on (at render time)
462 m_submissionContext->setOpenGLContext(m_glContext);
463
464 // Store the format used by the context and queue up creating an
465 // offscreen surface in the main thread so that it is available
466 // for use when we want to shutdown the renderer. We need to create
467 // the offscreen surface on the main thread because on some platforms
468 // (MS Windows), an offscreen surface is just a hidden QWindow.
469 m_format = m_glContext->format();
470 QMetaObject::invokeMethod(obj: m_offscreenHelper, member: "createOffscreenSurface");
471 }
472
473 // Awake setScenegraphRoot in case it was waiting
474 m_waitForInitializationToBeCompleted.release(n: 1);
475 // Allow the aspect manager to proceed
476 m_vsyncFrameAdvanceService->proceedToNextFrame();
477
478 // Force initial refresh
479 markDirty(changes: AllDirty, node: nullptr);
480}
481
482/*!
483 * \internal
484 *
485 * Signals for the renderer to stop rendering. If a threaded renderer is in use,
486 * the render thread will call releaseGraphicsResources() just before the thread exits.
487 * If rendering synchronously, this function will call releaseGraphicsResources().
488 */
489void Renderer::shutdown()
490{
491 // Ensure we have waited to be fully initialized before trying to shut down
492 // (in case initialization is taking place at the same time)
493 QMutexLocker lock(&m_hasBeenInitializedMutex);
494
495 qCDebug(Backend) << Q_FUNC_INFO << "Requesting renderer shutdown";
496 const bool wasRunning = m_running.testAndSetRelaxed(expectedValue: 1, newValue: 0);
497
498 // We might have already been shutdown
499 if (!wasRunning)
500 return;
501
502 // We delete any renderqueue that we may not have had time to render
503 // before the surface was destroyed
504 QMutexLocker lockRenderQueue(m_renderQueue.mutex());
505 m_renderQueue.reset();
506 lockRenderQueue.unlock();
507
508 releaseGraphicsResources();
509
510 // Destroy internal managers
511 // This needs to be done before the nodeManager is destroy
512 // as the internal resources might somehow rely on nodeManager resources
513 delete m_glResourceManagers;
514 m_glResourceManagers = nullptr;
515}
516
517/*!
518 \internal
519
520 When using a threaded renderer this function is called in the context of the
521 RenderThread to do any shutdown and cleanup that needs to be performed in the
522 thread where the OpenGL context lives.
523
524 When using Scene3D or anything that provides a custom QOpenGLContext (not
525 owned by Qt3D) this function is called whenever the signal
526 QOpenGLContext::aboutToBeDestroyed is emitted. In that case this function
527 is called in the context of the emitter's thread.
528*/
529void Renderer::releaseGraphicsResources()
530{
531 // We may get called twice when running inside of a Scene3D. Once when Qt Quick
532 // wants to shutdown, and again when the render aspect gets unregistered. So
533 // check that we haven't already cleaned up before going any further.
534 if (!m_submissionContext)
535 return;
536
537 // Try to temporarily make the context current so we can free up any resources
538 QMutexLocker locker(&m_offscreenSurfaceMutex);
539 QOffscreenSurface *offscreenSurface = m_offscreenHelper->offscreenSurface();
540 if (!offscreenSurface) {
541 qWarning() << "Failed to make context current: OpenGL resources will not be destroyed";
542 // We still need to delete the submission context
543 m_submissionContext.reset(other: nullptr);
544 return;
545 }
546
547 QOpenGLContext *context = m_submissionContext->openGLContext();
548 Q_ASSERT(context);
549
550 if (context->thread() == QThread::currentThread()) {
551 QSurface *lastContextSurface = context->surface();
552
553 if (context->makeCurrent(surface: offscreenSurface)) {
554 // Clean up the graphics context and any resources
555 const std::vector<HGLTexture> &activeTexturesHandles = m_glResourceManagers->glTextureManager()->activeHandles();
556 for (const HGLTexture &textureHandle : activeTexturesHandles) {
557 GLTexture *tex = m_glResourceManagers->glTextureManager()->data(handle: textureHandle);
558 tex->destroy();
559 }
560
561 // Do the same thing with buffers
562 const std::vector<HGLBuffer> &activeBuffers = m_glResourceManagers->glBufferManager()->activeHandles();
563 for (const HGLBuffer &bufferHandle : activeBuffers) {
564 GLBuffer *buffer = m_glResourceManagers->glBufferManager()->data(handle: bufferHandle);
565 buffer->destroy(ctx: m_submissionContext.data());
566 }
567
568 // Do the same thing with shaders
569 const std::vector<GLShader *> shaders = m_glResourceManagers->glShaderManager()->takeActiveResources();
570 qDeleteAll(c: shaders);
571
572 // Do the same thing with VAOs
573 const std::vector<HVao> &activeVaos = m_glResourceManagers->vaoManager()->activeHandles();
574 for (const HVao &vaoHandle : activeVaos) {
575 OpenGLVertexArrayObject *vao = m_glResourceManagers->vaoManager()->data(handle: vaoHandle);
576 vao->destroy();
577 }
578
579 m_submissionContext->releaseRenderTargets();
580
581 m_frameProfiler.reset();
582 if (m_ownedContext) {
583 context->doneCurrent();
584 } else {
585 // Leave the context in the state we found it in by restoring
586 // its last used surface. This satisfies expectations when used
587 // with QQuickWidgets that surface on current context after
588 // QQuickRenderControl cleanup is the same as prior to the
589 // cleanup. Arguably this could also be checked for in
590 // QQuickWidgetPrivate::invalidateRenderControl.
591 context->makeCurrent(surface: lastContextSurface);
592 }
593 }
594 } else {
595 qWarning() << "Failed to make context current: OpenGL resources will not be destroyed";
596 }
597
598 if (m_ownedContext)
599 delete context;
600 if (m_shareContext)
601 delete m_shareContext;
602
603 m_submissionContext.reset(other: nullptr);
604 qCDebug(Backend) << Q_FUNC_INFO << "Renderer properly shutdown";
605}
606
607void Renderer::setSurfaceExposed(bool exposed)
608{
609 qCDebug(Backend) << "Window exposed: " << exposed;
610 m_exposed.fetchAndStoreOrdered(newValue: exposed);
611}
612
613Render::FrameGraphNode *Renderer::frameGraphRoot() const
614{
615 Q_ASSERT(m_settings);
616 if (m_nodesManager && m_nodesManager->frameGraphManager() && m_settings)
617 return m_nodesManager->frameGraphManager()->lookupNode(id: m_settings->activeFrameGraphID());
618 return nullptr;
619}
620
621// QAspectThread context
622// Order of execution :
623// 1) RenderThread is created -> release 1 of m_waitForInitializationToBeCompleted when started
624// 2) setSceneRoot waits to acquire initialization
625// 3) submitRenderView -> check for surface
626// -> make surface current + create proper glHelper if needed
627void Renderer::setSceneRoot(Entity *sgRoot)
628{
629 Q_ASSERT(sgRoot);
630
631 // If initialization hasn't been completed we must wait
632 m_waitForInitializationToBeCompleted.acquire();
633
634 m_renderSceneRoot = sgRoot;
635 if (!m_renderSceneRoot)
636 qCWarning(Backend) << "Failed to build render scene";
637 m_renderSceneRoot->dump();
638 qCDebug(Backend) << Q_FUNC_INFO << "DUMPING SCENE";
639
640 // Set the scene root on the jobs
641 m_cleanupJob->setRoot(m_renderSceneRoot);
642
643 // Set all flags to dirty
644 m_dirtyBits.marked |= AbstractRenderer::AllDirty;
645}
646
647void Renderer::setSettings(RenderSettings *settings)
648{
649 m_settings = settings;
650}
651
652RenderSettings *Renderer::settings() const
653{
654 return m_settings;
655}
656
657// Either called by render if Qt3D is in charge of rendering (in the mainthread)
658// or by QRenderAspectPrivate::render (for Scene3D, potentially from a RenderThread)
659// This will wait until renderQueue is ready or shutdown was requested
660void Renderer::render(bool swapBuffers)
661{
662 Renderer::ViewSubmissionResultData submissionData;
663 bool preprocessingComplete = false;
664 bool beganDrawing = false;
665
666 // Blocking until RenderQueue is full
667 const bool canSubmit = waitUntilReadyToSubmit();
668
669 // If it returns false -> we are shutting down
670 if (!canSubmit)
671 return;
672
673 m_shouldSwapBuffers = swapBuffers;
674 const std::vector<Render::OpenGL::RenderView *> &renderViews = m_renderQueue.nextFrameQueue();
675 const bool queueIsEmpty = m_renderQueue.targetRenderViewCount() == 0;
676
677 // RenderQueue is complete (but that means it may be of size 0)
678 if (!queueIsEmpty) {
679 Qt3DCore::QTaskLogger submissionStatsPart1(m_services->systemInformation(),
680 { JobTypes::FrameSubmissionPart1, 0 },
681 Qt3DCore::QTaskLogger::Submission);
682 Qt3DCore::QTaskLogger submissionStatsPart2(m_services->systemInformation(),
683 { JobTypes::FrameSubmissionPart2, 0 },
684 Qt3DCore::QTaskLogger::Submission);
685 { // Scoped to destroy surfaceLock
686 QSurface *surface = nullptr;
687 for (const RenderView *rv: renderViews) {
688 surface = rv->surface();
689 if (surface)
690 break;
691 }
692
693 SurfaceLocker surfaceLock(surface);
694 const bool surfaceIsValid = (surface && surfaceLock.isSurfaceValid());
695 if (surfaceIsValid) {
696 // Reset state for each draw if we don't have complete control of the context
697 if (!m_ownedContext)
698 m_submissionContext->setCurrentStateSet(nullptr);
699 beganDrawing = m_submissionContext->beginDrawing(surface);
700 if (beganDrawing) {
701 // 1) Execute commands for buffer uploads, texture updates, shader loading first
702 updateGLResources();
703 // 2) Update VAO and copy data into commands to allow concurrent submission
704 prepareCommandsSubmission(renderViews);
705 preprocessingComplete = true;
706
707 // Purge shader which aren't used any longer
708 static int callCount = 0;
709 ++callCount;
710 const int shaderPurgePeriod = 600;
711 if (callCount % shaderPurgePeriod == 0)
712 m_glResourceManagers->glShaderManager()->purge();
713 }
714 }
715 }
716
717 // Only try to submit the RenderViews if the preprocessing was successful
718 if (preprocessingComplete) {
719 submissionStatsPart1.end(t: submissionStatsPart2.restart());
720
721 // 3) Submit the render commands for frame n (making sure we never reference something that could be changing)
722 // Render using current device state and renderer configuration
723 submissionData = submitRenderViews(renderViews);
724
725 // Perform any required cleanup of the Graphics resources (Buffers deleted, Shader deleted...)
726 cleanGraphicsResources();
727 }
728
729 // Execute the pending shell commands
730 m_commandExecuter->performAsynchronousCommandExecution(views: renderViews);
731
732 if (preprocessingComplete && activeProfiler())
733 m_frameProfiler->writeResults();
734 }
735
736 // Perform the last swapBuffers calls
737 // Finish up with last surface used in the list of RenderViews
738 if (beganDrawing) {
739 SurfaceLocker surfaceLock(submissionData.surface);
740 // Finish up with last surface used in the list of RenderViews
741 const bool swapBuffers = submissionData.lastBoundFBOId == m_submissionContext->defaultFBO()
742 && surfaceLock.isSurfaceValid()
743 && m_shouldSwapBuffers;
744 m_submissionContext->endDrawing(swapBuffers);
745 }
746
747 // Reset RenderQueue and destroy the renderViews
748 m_renderQueue.reset();
749
750 // Allow next frame to be built once we are done doing all rendering
751 m_vsyncFrameAdvanceService->proceedToNextFrame();
752}
753
754// Called by RenderViewJobs
755// When the frameQueue is complete and we are using a renderThread
756// we allow the render thread to proceed
757void Renderer::enqueueRenderView(RenderView *renderView, int submitOrder)
758{
759 QMutexLocker locker(m_renderQueue.mutex()); // Prevent out of order execution
760 // We cannot use a lock free primitive here because:
761 // - QList is not thread safe
762 // - Even if the insert is made correctly, the isFrameComplete call
763 // could be invalid since depending on the order of execution
764 // the counter could be complete but the renderview not yet added to the
765 // buffer depending on whichever order the cpu decides to process this
766 const bool isQueueComplete = m_renderQueue.queueRenderView(renderView, submissionOrderIndex: submitOrder);
767 locker.unlock(); // We're done protecting the queue at this point
768 if (isQueueComplete) {
769 if (m_running.loadRelaxed())
770 Q_ASSERT(m_submitRenderViewsSemaphore.available() == 0);
771 m_submitRenderViewsSemaphore.release(n: 1);
772 }
773}
774
775Profiling::FrameProfiler *Renderer::activeProfiler() const
776{
777 if (m_services && m_services->systemInformation()->isGraphicsTraceEnabled()) {
778 if (m_frameProfiler.isNull())
779 m_frameProfiler.reset(other: new Profiling::FrameProfiler(m_services->systemInformation()));
780
781 return m_frameProfiler.data();
782 }
783
784 return nullptr;
785}
786
787bool Renderer::waitUntilReadyToSubmit()
788{
789 // Make sure that we've been told to render before rendering
790 // Prevent ouf of order execution
791 m_submitRenderViewsSemaphore.acquire(n: 1);
792
793 // Check if shutdown has been requested
794 if (m_running.loadRelaxed() == 0)
795 return false;
796
797 // The semaphore should only
798 // be released when the frame queue is complete and there's
799 // something to render
800 // The case of shutdown should have been handled just before
801 Q_ASSERT(m_renderQueue.isFrameQueueComplete());
802 return true;
803}
804
805// Main thread
806QVariant Renderer::executeCommand(const QStringList &args)
807{
808 return m_commandExecuter->executeCommand(args);
809}
810
811/*!
812 \internal
813 Called in the context of the aspect thread from QRenderAspect::onRegistered
814*/
815void Renderer::setOffscreenSurfaceHelper(OffscreenSurfaceHelper *helper)
816{
817 QMutexLocker locker(&m_offscreenSurfaceMutex);
818 m_offscreenHelper = helper;
819}
820
821QSurfaceFormat Renderer::format()
822{
823 return m_format;
824}
825
826// When this function is called, we must not be processing the commands for frame n+1
827void Renderer::prepareCommandsSubmission(const std::vector<RenderView *> &renderViews)
828{
829 OpenGLVertexArrayObject *vao = nullptr;
830 QHash<HVao, bool> updatedTable;
831
832 for (RenderView *rv: renderViews) {
833 rv->forEachCommand(func: [&] (RenderCommand &command) {
834 // Update/Create VAO
835 if (command.m_type == RenderCommand::Draw) {
836 Geometry *rGeometry = m_nodesManager->data<Geometry, GeometryManager>(handle: command.m_geometry);
837 GeometryRenderer *rGeometryRenderer = m_nodesManager->data<GeometryRenderer, GeometryRendererManager>(handle: command.m_geometryRenderer);
838 GLShader *shader = command.m_glShader;
839
840 // We should never have inserted a command for which these are null
841 // in the first place
842 Q_ASSERT(rGeometry && rGeometryRenderer && shader);
843
844 // The VAO should be created only once for a QGeometry and a ShaderProgram
845 // Manager should have a VAO Manager that are indexed by QMeshData and Shader
846 // RenderCommand should have a handle to the corresponding VAO for the Mesh and Shader
847 HVao vaoHandle;
848
849 // If shader was loaded this frame, skip creating VAO for the command
850 // as we have to wait for next frame to make sure command was build against valid shader
851 if (m_lastLoadedShaderIds.contains(t: command.m_shaderId)) {
852 command.m_isValid = false;
853 return;
854 }
855
856 // Create VAO or return already created instance associated with command shader/geometry
857 // (VAO is emulated if not supported)
858 createOrUpdateVAO(command: &command, previousVAOHandle: &vaoHandle, vao: &vao);
859 command.m_vao = vaoHandle;
860
861 // Avoids redoing the same thing for the same VAO
862 if (!updatedTable.contains(key: vaoHandle)) {
863 updatedTable.insert(key: vaoHandle, value: true);
864
865 // Do we have any attributes that are dirty ?
866 const bool requiresPartialVAOUpdate = requiresVAOAttributeUpdate(geometry: rGeometry, command: &command);
867
868 // If true, we need to reupload all attributes to set the VAO
869 // Otherwise only dirty attributes will be updates
870 const bool requiresFullVAOUpdate = (!vao->isSpecified()) || (rGeometry->isDirty() || rGeometryRenderer->isDirty());
871
872 // Append dirty Geometry to temporary vector
873 // so that its dirtiness can be unset later
874 if (rGeometry->isDirty())
875 m_dirtyGeometry.push_back(x: rGeometry);
876
877 if (!command.m_activeAttributes.empty() && (requiresFullVAOUpdate || requiresPartialVAOUpdate)) {
878 Profiling::GLTimeRecorder recorder(Profiling::VAOUpload, activeProfiler());
879 // Activate shader
880 m_submissionContext->activateShader(shader);
881 // Bind VAO
882 vao->bind();
883 // Update or set Attributes and Buffers for the given rGeometry and Command
884 // Note: this fills m_dirtyAttributes as well
885 if (updateVAOWithAttributes(geometry: rGeometry, command: &command, shader, forceUpdate: requiresFullVAOUpdate))
886 vao->setSpecified(true);
887 }
888 }
889
890 // Unset dirtiness on rGeometryRenderer only
891 // The rGeometry may be shared by several rGeometryRenderer
892 // so we cannot unset its dirtiness at this point
893 if (rGeometryRenderer->isDirty())
894 rGeometryRenderer->unsetDirty();
895 } else if (command.m_type == RenderCommand::Compute) {
896 GLShader *shader = command.m_glShader;
897 Q_ASSERT(shader);
898 }
899 });
900 }
901
902 // Make sure we leave nothing bound
903 if (vao)
904 vao->release();
905
906 // Unset dirtiness on Geometry and Attributes
907 // Note: we cannot do it in the loop above as we want to be sure that all
908 // the VAO which reference the geometry/attributes are properly updated
909 for (Attribute *attribute : m_dirtyAttributes)
910 attribute->unsetDirty();
911 m_dirtyAttributes.clear();
912
913 for (Geometry *geometry : m_dirtyGeometry)
914 geometry->unsetDirty();
915 m_dirtyGeometry.clear();
916}
917
918// Executed in a job
919void Renderer::lookForAbandonedVaos()
920{
921 const std::vector<HVao> &activeVaos = m_glResourceManagers->vaoManager()->activeHandles();
922 for (HVao handle : activeVaos) {
923 OpenGLVertexArrayObject *vao = m_glResourceManagers->vaoManager()->data(handle);
924
925 // Make sure to only mark VAOs for deletion that were already created
926 // (ignore those that might be currently under construction in the render thread)
927 if (vao && vao->isAbandoned(geomMgr: m_nodesManager->geometryManager(), shaderMgr: m_glResourceManagers->glShaderManager())) {
928 m_abandonedVaosMutex.lock();
929 m_abandonedVaos.push_back(x: handle);
930 m_abandonedVaosMutex.unlock();
931 }
932 }
933}
934
935// Executed in a job
936void Renderer::lookForDirtyBuffers()
937{
938 const std::vector<HBuffer> &activeBufferHandles = m_nodesManager->bufferManager()->activeHandles();
939 for (const HBuffer &handle: activeBufferHandles) {
940 Buffer *buffer = m_nodesManager->bufferManager()->data(handle);
941 if (buffer->isDirty())
942 m_dirtyBuffers.push_back(x: handle);
943 }
944}
945
946// Called in prepareSubmission
947void Renderer::lookForDownloadableBuffers()
948{
949 m_downloadableBuffers.clear();
950 const std::vector<HBuffer> &activeBufferHandles = m_nodesManager->bufferManager()->activeHandles();
951 for (const HBuffer &handle : activeBufferHandles) {
952 Buffer *buffer = m_nodesManager->bufferManager()->data(handle);
953 if (buffer->access() & Qt3DCore::QBuffer::Read)
954 m_downloadableBuffers.push_back(x: buffer->peerId());
955 }
956}
957
958// Executed in a job
959void Renderer::lookForDirtyTextures()
960{
961 // To avoid having Texture or TextureImage maintain relationships between
962 // one another, we instead perform a lookup here to check if a texture
963 // image has been updated to then notify textures referencing the image
964 // that they need to be updated
965 TextureImageManager *imageManager = m_nodesManager->textureImageManager();
966 const std::vector<HTextureImage> &activeTextureImageHandles = imageManager->activeHandles();
967 Qt3DCore::QNodeIdVector dirtyImageIds;
968 for (const HTextureImage &handle: activeTextureImageHandles) {
969 TextureImage *image = imageManager->data(handle);
970 if (image->isDirty()) {
971 dirtyImageIds.push_back(t: image->peerId());
972 image->unsetDirty();
973 }
974 }
975
976 TextureManager *textureManager = m_nodesManager->textureManager();
977 const std::vector<HTexture> &activeTextureHandles = textureManager->activeHandles();
978 for (const HTexture &handle: activeTextureHandles) {
979 Texture *texture = textureManager->data(handle);
980 const Qt3DCore::QNodeIdVector imageIds = texture->textureImageIds();
981
982 // Does the texture reference any of the dirty texture images?
983 for (const Qt3DCore::QNodeId &imageId : imageIds) {
984 if (dirtyImageIds.contains(t: imageId)) {
985 texture->addDirtyFlag(flags: Texture::DirtyImageGenerators);
986 break;
987 }
988 }
989
990 // Dirty meaning that something has changed on the texture
991 // either properties, parameters, shared texture id, generator or a texture image
992 if (texture->dirtyFlags() != Texture::NotDirty)
993 m_dirtyTextures.push_back(x: handle);
994 // Note: texture dirty flags are reset when actually updating the
995 // textures in updateGLResources() as resetting flags here would make
996 // us lose information about what was dirty exactly.
997 }
998}
999
1000// Executed in a job
1001void Renderer::reloadDirtyShaders()
1002{
1003 Q_ASSERT(isRunning());
1004 const std::vector<HTechnique> &activeTechniques = m_nodesManager->techniqueManager()->activeHandles();
1005 const std::vector<HShaderBuilder> &activeBuilders = m_nodesManager->shaderBuilderManager()->activeHandles();
1006 for (const HTechnique &techniqueHandle : activeTechniques) {
1007 Technique *technique = m_nodesManager->techniqueManager()->data(handle: techniqueHandle);
1008 // If api of the renderer matches the one from the technique
1009 if (technique->isCompatibleWithRenderer()) {
1010 const auto passIds = technique->renderPasses();
1011 for (const Qt3DCore::QNodeId &passId : passIds) {
1012 RenderPass *renderPass = m_nodesManager->renderPassManager()->lookupResource(id: passId);
1013 HShader shaderHandle = m_nodesManager->shaderManager()->lookupHandle(id: renderPass->shaderProgram());
1014 Shader *shader = m_nodesManager->shaderManager()->data(handle: shaderHandle);
1015
1016 // Shader could be null if the pass doesn't reference one yet
1017 if (!shader)
1018 continue;
1019
1020 ShaderBuilder *shaderBuilder = nullptr;
1021 for (const HShaderBuilder &builderHandle : activeBuilders) {
1022 ShaderBuilder *builder = m_nodesManager->shaderBuilderManager()->data(handle: builderHandle);
1023 if (builder->shaderProgramId() == shader->peerId()) {
1024 shaderBuilder = builder;
1025 break;
1026 }
1027 }
1028
1029 if (shaderBuilder) {
1030 shaderBuilder->setGraphicsApi(*technique->graphicsApiFilter());
1031
1032 for (int i = 0; i <= QShaderProgram::Compute; i++) {
1033 const auto shaderType = static_cast<QShaderProgram::ShaderType>(i);
1034 if (!shaderBuilder->shaderGraph(type: shaderType).isValid())
1035 continue;
1036
1037 if (shaderBuilder->isShaderCodeDirty(type: shaderType)) {
1038 shaderBuilder->generateCode(type: shaderType);
1039 Qt3DCore::moveAtEnd(destination&: m_shaderBuilderUpdates, source: shaderBuilder->takePendingUpdates());
1040 }
1041
1042 const auto code = shaderBuilder->shaderCode(type: shaderType);
1043 shader->setShaderCode(type: shaderType, code);
1044 }
1045 }
1046
1047 if (shader->isDirty()) {
1048 if (!Qt3DCore::contains(destination: m_dirtyShaders, element: shaderHandle))
1049 m_dirtyShaders.push_back(x: shaderHandle);
1050 }
1051 }
1052 }
1053 }
1054}
1055
1056// Executed in job (in main thread when jobs are done)
1057void Renderer::sendShaderChangesToFrontend(Qt3DCore::QAspectManager *manager)
1058{
1059 Q_ASSERT(isRunning());
1060
1061 // Sync Shader
1062 const std::vector<HShader> &activeShaders = m_nodesManager->shaderManager()->activeHandles();
1063 for (const HShader &handle :activeShaders) {
1064 Shader *s = m_nodesManager->shaderManager()->data(handle);
1065 if (!s)
1066 continue;
1067
1068 if (s->requiresFrontendSync()) {
1069 QShaderProgram *frontend = static_cast<decltype(frontend)>(manager->lookupNode(id: s->peerId()));
1070 // Could happen as a backend shader might live beyong the frontend
1071 // the time needed to destroy the GLShader assoicated with it.
1072 if (!frontend)
1073 continue;
1074 QShaderProgramPrivate *dFrontend = static_cast<decltype(dFrontend)>(Qt3DCore::QNodePrivate::get(q: frontend));
1075 s->unsetRequiresFrontendSync();
1076 dFrontend->setStatus(s->status());
1077 dFrontend->setLog(s->log());
1078 }
1079 }
1080
1081 // Sync ShaderBuilder
1082 const std::vector<ShaderBuilderUpdate> shaderBuilderUpdates = Qt3DCore::moveAndClear(data&: m_shaderBuilderUpdates);
1083 for (const ShaderBuilderUpdate &update : shaderBuilderUpdates) {
1084 QShaderProgramBuilder *builder = static_cast<decltype(builder)>(manager->lookupNode(id: update.builderId));
1085 QShaderProgramBuilderPrivate *dBuilder = static_cast<decltype(dBuilder)>(Qt3DCore::QNodePrivate::get(q: builder));
1086 dBuilder->setShaderCode(code: update.shaderCode, type: update.shaderType);
1087 }
1088}
1089
1090// Executed in a job (in main thread when jobs are done)
1091void Renderer::sendTextureChangesToFrontend(Qt3DCore::QAspectManager *manager)
1092{
1093 const std::vector<QPair<Texture::TextureUpdateInfo, Qt3DCore::QNodeIdVector>> updateTextureProperties = Qt3DCore::moveAndClear(data&: m_updatedTextureProperties);
1094 for (const auto &pair : updateTextureProperties) {
1095 const Qt3DCore::QNodeIdVector targetIds = pair.second;
1096 for (const Qt3DCore::QNodeId &targetId: targetIds) {
1097 // Lookup texture
1098 Texture *t = m_nodesManager->textureManager()->lookupResource(id: targetId);
1099 // If backend texture is Dirty, some property has changed and the properties we are
1100 // about to send are already outdate
1101 if (t == nullptr || t->dirtyFlags() != Texture::NotDirty)
1102 continue;
1103
1104 QAbstractTexture *texture = static_cast<QAbstractTexture *>(manager->lookupNode(id: targetId));
1105 if (!texture)
1106 continue;
1107 const TextureProperties &properties = pair.first.properties;
1108
1109 const bool blocked = texture->blockNotifications(block: true);
1110 texture->setWidth(properties.width);
1111 texture->setHeight(properties.height);
1112 texture->setDepth(properties.depth);
1113 texture->setLayers(properties.layers);
1114 texture->setFormat(properties.format);
1115 texture->blockNotifications(block: blocked);
1116
1117 QAbstractTexturePrivate *dTexture = static_cast<QAbstractTexturePrivate *>(Qt3DCore::QNodePrivate::get(q: texture));
1118
1119 dTexture->setStatus(properties.status);
1120 dTexture->setHandleType(pair.first.handleType);
1121 dTexture->setHandle(pair.first.handle);
1122 }
1123 }
1124}
1125
1126// Executed in main thread when jobs done
1127void Renderer::sendSetFenceHandlesToFrontend(Qt3DCore::QAspectManager *manager)
1128{
1129 const std::vector<QPair<Qt3DCore::QNodeId, GLFence>> updatedSetFence = Qt3DCore::moveAndClear(data&: m_updatedSetFences);
1130 FrameGraphManager *fgManager = m_nodesManager->frameGraphManager();
1131 for (const auto &pair : updatedSetFence) {
1132 FrameGraphNode *fgNode = fgManager->lookupNode(id: pair.first);
1133 if (fgNode != nullptr) { // Node could have been deleted before we got a chance to notify it
1134 Q_ASSERT(fgNode->nodeType() == FrameGraphNode::SetFence);
1135 QSetFence *frontend = static_cast<decltype(frontend)>(manager->lookupNode(id: fgNode->peerId()));
1136 QSetFencePrivate *dFrontend = static_cast<decltype(dFrontend)>(Qt3DCore::QNodePrivate::get(q: frontend));
1137 dFrontend->setHandleType(QSetFence::OpenGLFenceId);
1138 dFrontend->setHandle(QVariant::fromValue(value: pair.second));
1139 }
1140 }
1141}
1142
1143// Executed in main thread when jobs done
1144void Renderer::sendDisablesToFrontend(Qt3DCore::QAspectManager *manager)
1145{
1146 // SubtreeEnabled
1147 const auto updatedDisables = Qt3DCore::moveAndClear(data&: m_updatedDisableSubtreeEnablers);
1148 for (const auto &nodeId : updatedDisables) {
1149 QSubtreeEnabler *frontend = static_cast<decltype(frontend)>(manager->lookupNode(id: nodeId));
1150 frontend->setEnabled(false);
1151 }
1152
1153 // Compute Commands
1154 const std::vector<HComputeCommand> &activeCommands = m_nodesManager->computeJobManager()->activeHandles();
1155 for (const HComputeCommand &handle :activeCommands) {
1156 ComputeCommand *c = m_nodesManager->computeJobManager()->data(handle);
1157 if (c->hasReachedFrameCount()) {
1158 QComputeCommand *frontend = static_cast<decltype(frontend)>(manager->lookupNode(id: c->peerId()));
1159 frontend->setEnabled(false);
1160 c->resetHasReachedFrameCount();
1161 }
1162 }
1163}
1164
1165// Render Thread (or QtQuick RenderThread when using Scene3D)
1166// Scene3D: When using Scene3D rendering, we can't assume that when
1167// updateGLResources is called, the resource handles points to still existing
1168// objects. This is because Scene3D calls doRender independently of whether all
1169// jobs have completed or not which in turn calls proceedToNextFrame under some
1170// conditions. Such conditions are usually met on startup to avoid deadlocks.
1171// proceedToNextFrame triggers the syncChanges calls for the next frame, which
1172// may contain destruction changes targeting resources. When the above
1173// happens, this can result in the dirtyResource vectors containing handles of
1174// objects that may already have been destroyed
1175void Renderer::updateGLResources()
1176{
1177 {
1178 // Update active fence objects:
1179 // - Destroy fences that have reached their signaled state
1180 GLFenceManager *fenceManager = m_glResourceManagers->glFenceManager();
1181 const auto end = fenceManager->end();
1182 auto it = fenceManager->begin();
1183 while (it != end) {
1184 const GLFence fence = it.value();
1185 if (m_submissionContext->wasSyncSignaled(sync: fence)) {
1186 // Fence was signaled, we delete it
1187 // before removing the entry from the manager
1188 m_submissionContext->deleteSync(sync: fence);
1189 it = fenceManager->erase(it);
1190 } else {
1191 ++it;
1192 }
1193 }
1194 }
1195
1196 {
1197 Profiling::GLTimeRecorder recorder(Profiling::BufferUpload, activeProfiler());
1198 const std::vector<HBuffer> dirtyBufferHandles = Qt3DCore::moveAndClear(data&: m_dirtyBuffers);
1199 for (const HBuffer &handle: dirtyBufferHandles) {
1200 Buffer *buffer = m_nodesManager->bufferManager()->data(handle);
1201
1202 // Can be null when using Scene3D rendering
1203 if (buffer == nullptr)
1204 continue;
1205
1206 // Forces creation if it doesn't exit
1207 // Also note the binding point doesn't really matter here, we just upload data
1208 if (!m_submissionContext->hasGLBufferForBuffer(buffer))
1209 m_submissionContext->glBufferForRenderBuffer(buf: buffer);
1210 // Update the glBuffer data
1211 m_submissionContext->updateBuffer(buffer);
1212 buffer->unsetDirty();
1213 }
1214 }
1215
1216 {
1217 Profiling::GLTimeRecorder recorder(Profiling::ShaderUpload, activeProfiler());
1218 const std::vector<HShader> dirtyShaderHandles = Qt3DCore::moveAndClear(data&: m_dirtyShaders);
1219 ShaderManager *shaderManager = m_nodesManager->shaderManager();
1220 for (const HShader &handle: dirtyShaderHandles) {
1221 Shader *shader = shaderManager->data(handle);
1222
1223 // Can be null when using Scene3D rendering
1224 if (shader == nullptr)
1225 continue;
1226
1227 // Compile shader
1228 m_submissionContext->loadShader(shader, shaderManager, glShaderManager: m_glResourceManagers->glShaderManager());
1229
1230 // Release any VAO referencing this shader. When we build VAO, we
1231 // rely on the shader introspection to know the active uniforms In
1232 // case the shader is reloaded, we might end up having more/less
1233 // active uniforms than prior therefore we need to ensure VAO is
1234 // rebuilt.
1235 VAOManager *vaoManager = m_glResourceManagers->vaoManager();
1236 const std::vector<HVao> activeVaos = vaoManager->activeHandles(); // copy
1237 for (const HVao &vao : activeVaos) {
1238 if (vao.data() && vao->key().second == shader->peerId())
1239 vaoManager->releaseResource(id: vao->key());
1240 }
1241
1242 // Record shader id in vector of vectors loaded this frame
1243 // Given commands need to be built against loaded shader (at next frame)
1244 // we can make use of this vector to skip operations that target this
1245 // shader for this frame
1246 m_lastLoadedShaderIds.push_back(t: shader->peerId());
1247 }
1248 }
1249
1250 {
1251 Profiling::GLTimeRecorder recorder(Profiling::TextureUpload, activeProfiler());
1252 const std::vector<HTexture> activeTextureHandles = Qt3DCore::moveAndClear(data&: m_dirtyTextures);
1253 for (const HTexture &handle: activeTextureHandles) {
1254 Texture *texture = m_nodesManager->textureManager()->data(handle);
1255
1256 // Can be null when using Scene3D rendering
1257 if (texture == nullptr)
1258 continue;
1259
1260 // Create or Update GLTexture (the GLTexture instance is created
1261 // (not the underlying GL instance) if required and all things that
1262 // can take place without a GL context are done here)
1263 updateTexture(texture);
1264 }
1265 // We want to upload textures data at this point as the SubmissionThread and
1266 // AspectThread are locked ensuring no races between Texture/TextureImage and
1267 // GLTexture
1268 Qt3DCore::QNodeIdVector updatedTexturesForFrame;
1269 if (m_submissionContext != nullptr) {
1270 GLTextureManager *glTextureManager = m_glResourceManagers->glTextureManager();
1271 const std::vector<HGLTexture> &glTextureHandles = glTextureManager->activeHandles();
1272 // Upload texture data
1273 for (const HGLTexture &glTextureHandle : glTextureHandles) {
1274 GLTexture *glTexture = glTextureManager->data(handle: glTextureHandle);
1275
1276 // We create/update the actual GL texture using the GL context at this point
1277 const GLTexture::TextureUpdateInfo info = glTexture->createOrUpdateGLTexture();
1278
1279 // GLTexture creation provides us width/height/format ... information
1280 // for textures which had not initially specified these information (TargetAutomatic...)
1281 // Gather these information and store them to be distributed by a change next frame
1282 const Qt3DCore::QNodeIdVector referenceTextureIds = { glTextureManager->texNodeIdForGLTexture.value(key: glTexture) };
1283 // Store properties and referenceTextureIds
1284 if (info.wasUpdated) {
1285 Texture::TextureUpdateInfo updateInfo;
1286 updateInfo.properties = info.properties;
1287 updateInfo.handleType = QAbstractTexture::OpenGLTextureId;
1288 updateInfo.handle = info.texture ? QVariant(info.texture->textureId()) : QVariant();
1289 m_updatedTextureProperties.push_back(x: {updateInfo, referenceTextureIds});
1290 updatedTexturesForFrame += referenceTextureIds;
1291 }
1292 }
1293 }
1294
1295 // If the underlying GL Texture was for whatever reason recreated, we need to make sure
1296 // that if it is used as a color attachment, we rebuild the FBO next time it is used
1297 m_submissionContext->setUpdatedTexture(std::move(updatedTexturesForFrame));
1298
1299 // Record ids of texture to cleanup while we are still blocking the aspect thread
1300 m_textureIdsToCleanup += m_nodesManager->textureManager()->takeTexturesIdsToCleanup();
1301 }
1302
1303 // Record list of buffer that might need uploading
1304 lookForDownloadableBuffers();
1305
1306 // Remove destroyed FBOs
1307 {
1308 const Qt3DCore::QNodeIdVector destroyedRenderTargetIds = m_nodesManager->renderTargetManager()->takeRenderTargetIdsToCleanup();
1309 for (const Qt3DCore::QNodeId &renderTargetId : destroyedRenderTargetIds)
1310 m_submissionContext->releaseRenderTarget(id: renderTargetId);
1311 }
1312}
1313
1314// Render Thread
1315void Renderer::updateTexture(Texture *texture)
1316{
1317 // Check that the current texture images are still in place, if not, do not update
1318 const bool isValid = texture->isValid(manager: m_nodesManager->textureImageManager());
1319 if (!isValid) {
1320 qWarning() << Q_FUNC_INFO << "QTexture referencing invalid QTextureImages";
1321 return;
1322 }
1323
1324 // All textures are unique, if you instanciate twice the exact same texture
1325 // this will create 2 identical GLTextures, no sharing will take place
1326
1327 // Try to find the associated GLTexture for the backend Texture
1328 GLTextureManager *glTextureManager = m_glResourceManagers->glTextureManager();
1329 GLTexture *glTexture = glTextureManager->lookupResource(id: texture->peerId());
1330
1331 // No GLTexture associated yet -> create it
1332 if (glTexture == nullptr) {
1333 glTexture = glTextureManager->getOrCreateResource(id: texture->peerId());
1334 glTextureManager->texNodeIdForGLTexture.insert(key: glTexture, value: texture->peerId());
1335 }
1336
1337 // Update GLTexture to match Texture instance
1338 const Texture::DirtyFlags dirtyFlags = texture->dirtyFlags();
1339 if (dirtyFlags.testFlag(flag: Texture::DirtySharedTextureId))
1340 glTexture->setSharedTextureId(texture->sharedTextureId());
1341
1342 if (dirtyFlags.testFlag(flag: Texture::DirtyProperties))
1343 glTexture->setProperties(texture->properties());
1344
1345 if (dirtyFlags.testFlag(flag: Texture::DirtyParameters))
1346 glTexture->setParameters(texture->parameters());
1347
1348 // Will make the texture requestUpload
1349 if (dirtyFlags.testFlag(flag: Texture::DirtyImageGenerators)) {
1350 const Qt3DCore::QNodeIdVector textureImageIds = texture->textureImageIds();
1351 std::vector<GLTexture::Image> images;
1352 images.reserve(n: textureImageIds.size());
1353 // TODO: Move this into GLTexture directly
1354 for (const Qt3DCore::QNodeId &textureImageId : textureImageIds) {
1355 const TextureImage *img = m_nodesManager->textureImageManager()->lookupResource(id: textureImageId);
1356 if (img == nullptr) {
1357 qWarning() << Q_FUNC_INFO << "invalid TextureImage handle";
1358 } else {
1359 GLTexture::Image glImg {.generator: img->dataGenerator(), .layer: img->layer(), .mipLevel: img->mipLevel(), .face: img->face()};
1360 images.push_back(x: glImg);
1361 }
1362 }
1363 glTexture->setImages(std::move(images));
1364 }
1365
1366 // Will make the texture requestUpload
1367 if (dirtyFlags.testFlag(flag: Texture::DirtyDataGenerator))
1368 glTexture->setGenerator(texture->dataGenerator());
1369
1370 // Will make the texture requestUpload
1371 if (dirtyFlags.testFlag(flag: Texture::DirtyPendingDataUpdates))
1372 glTexture->addTextureDataUpdates(updates: texture->takePendingTextureDataUpdates());
1373
1374 // Unset the dirty flag on the texture
1375 texture->unsetDirty();
1376}
1377
1378// Render Thread
1379void Renderer::cleanupTexture(Qt3DCore::QNodeId cleanedUpTextureId)
1380{
1381 GLTextureManager *glTextureManager = m_glResourceManagers->glTextureManager();
1382 GLTexture *glTexture = glTextureManager->lookupResource(id: cleanedUpTextureId);
1383
1384 // Destroying the GLTexture implicitely also destroy the GL resources
1385 if (glTexture != nullptr) {
1386 glTextureManager->releaseResource(id: cleanedUpTextureId);
1387 glTextureManager->texNodeIdForGLTexture.remove(key: glTexture);
1388 }
1389}
1390
1391// Render Thread
1392void Renderer::cleanupShader(const Shader *shader)
1393{
1394 if (!shader)
1395 return;
1396
1397 GLShaderManager *glShaderManager = m_glResourceManagers->glShaderManager();
1398 GLShader *glShader = glShaderManager->lookupResource(shaderId: shader->peerId());
1399
1400 if (glShader != nullptr)
1401 glShaderManager->abandon(apiShader: glShader, shader);
1402}
1403
1404// Called by SubmitRenderView
1405void Renderer::downloadGLBuffers()
1406{
1407 const std::vector<Qt3DCore::QNodeId> downloadableHandles = Qt3DCore::moveAndClear(data&: m_downloadableBuffers);
1408 for (const Qt3DCore::QNodeId &bufferId : downloadableHandles) {
1409 BufferManager *bufferManager = m_nodesManager->bufferManager();
1410 BufferManager::ReadLocker locker(const_cast<const BufferManager *>(bufferManager));
1411 Buffer *buffer = bufferManager->lookupResource(id: bufferId);
1412 // Buffer could have been destroyed at this point
1413 if (!buffer)
1414 continue;
1415 // locker is protecting us from the buffer being destroy while we're looking
1416 // up its content
1417 const QByteArray content = m_submissionContext->downloadBufferContent(buffer);
1418 m_sendBufferCaptureJob->addRequest(request: QPair<Qt3DCore::QNodeId, QByteArray>(bufferId, content));
1419 }
1420}
1421
1422// Happens in RenderThread context when all RenderViewJobs are done
1423// Returns the id of the last bound FBO
1424Renderer::ViewSubmissionResultData Renderer::submitRenderViews(const std::vector<RenderView *> &renderViews)
1425{
1426 QElapsedTimer timer;
1427 quint64 queueElapsed = 0;
1428 timer.start();
1429
1430 const size_t renderViewsCount = renderViews.size();
1431 quint64 frameElapsed = queueElapsed;
1432 m_lastFrameCorrect.storeRelaxed(newValue: 1); // everything fine until now.....
1433
1434 qCDebug(Memory) << Q_FUNC_INFO << "rendering frame ";
1435
1436 // We might not want to render on the default FBO
1437 uint lastBoundFBOId = m_submissionContext->boundFrameBufferObject();
1438 QSurface *surface = nullptr;
1439 QSurface *previousSurface = nullptr;
1440 for (const RenderView *rv: renderViews) {
1441 previousSurface = rv->surface();
1442 if (previousSurface)
1443 break;
1444 }
1445 QSurface *lastUsedSurface = nullptr;
1446
1447 bool imGuiOverlayShown = false;
1448 for (size_t i = 0; i < renderViewsCount; ++i) {
1449 // Initialize GraphicsContext for drawing
1450 // If the RenderView has a RenderStateSet defined
1451 RenderView *renderView = renderViews.at(n: i);
1452
1453 if (renderView->shouldSkipSubmission())
1454 continue;
1455
1456 // Check if using the same surface as the previous RenderView.
1457 // If not, we have to free up the context from the previous surface
1458 // and make the context current on the new surface
1459 surface = renderView->surface();
1460 SurfaceLocker surfaceLock(surface);
1461
1462 // TO DO: Make sure that the surface we are rendering too has not been unset
1463
1464 // For now, if we do not have a surface, skip this renderview
1465 // TODO: Investigate if it's worth providing a fallback offscreen surface
1466 // to use when surface is null. Or if we should instead expose an
1467 // offscreensurface to Qt3D.
1468 if (!surface || !surfaceLock.isSurfaceValid()) {
1469 m_lastFrameCorrect.storeRelaxed(newValue: 0);
1470 continue;
1471 }
1472
1473 lastUsedSurface = surface;
1474 const bool surfaceHasChanged = surface != previousSurface;
1475
1476 if (surfaceHasChanged && previousSurface) {
1477 const bool swapBuffers = lastBoundFBOId == m_submissionContext->defaultFBO()
1478 && surfaceLock.isSurfaceValid()
1479 && m_shouldSwapBuffers;
1480 // We only call swap buffer if we are sure the previous surface is still valid
1481 m_submissionContext->endDrawing(swapBuffers);
1482 }
1483
1484 if (surfaceHasChanged) {
1485 // If we can't make the context current on the surface, skip to the
1486 // next RenderView. We won't get the full frame but we may get something
1487 if (!m_submissionContext->beginDrawing(surface)) {
1488 qWarning() << "Failed to make OpenGL context current on surface";
1489 m_lastFrameCorrect.storeRelaxed(newValue: 0);
1490 continue;
1491 }
1492
1493 previousSurface = surface;
1494 lastBoundFBOId = m_submissionContext->boundFrameBufferObject();
1495 }
1496
1497 // Apply Memory Barrier if needed
1498 if (renderView->memoryBarrier() != QMemoryBarrier::None)
1499 m_submissionContext->memoryBarrier(barriers: renderView->memoryBarrier());
1500
1501
1502 // Insert Fence into command stream if needed
1503 const Qt3DCore::QNodeIdVector insertFenceIds = renderView->insertFenceIds();
1504 GLFenceManager *fenceManager = m_glResourceManagers->glFenceManager();
1505 for (const Qt3DCore::QNodeId &insertFenceId : insertFenceIds) {
1506 // If the fence is not in the manager, then it hasn't been inserted
1507 // into the command stream yet.
1508 if (fenceManager->find(key: insertFenceId) == fenceManager->end()) {
1509 // Insert fence into command stream
1510 GLFence glFence = m_submissionContext->fenceSync();
1511 // Record glFence
1512 fenceManager->insert(key: insertFenceId, value: glFence);
1513 // Add entry for notification changes to be sent
1514 m_updatedSetFences.push_back(x: {insertFenceId, glFence});
1515 }
1516 // If it is in the manager, then it hasn't been signaled yet,
1517 // nothing we can do but try at the next frame
1518 }
1519
1520 // Wait for fences if needed
1521 const QList<WaitFence::Data> waitFences = renderView->waitFences();
1522 for (const auto &waitFence : waitFences) {
1523 // TO DO
1524 if (waitFence.handleType != QWaitFence::OpenGLFenceId) {
1525 qWarning() << "WaitFence handleType should be OpenGLFenceId when using the Qt 3D OpenGL renderer";
1526 continue;
1527 }
1528 GLFence fence = reinterpret_cast<GLFence>(waitFence.handle.value<qintptr>());
1529 if (fence == nullptr)
1530 continue;
1531
1532 if (waitFence.waitOnCPU)
1533 m_submissionContext->clientWaitSync(sync: fence, nanoSecTimeout: waitFence.timeout);
1534 else
1535 m_submissionContext->waitSync(sync: fence);
1536 }
1537
1538 // Note: the RenderStateSet is allocated once per RV if needed
1539 // and it contains a list of StateVariant value types
1540 RenderStateSet *renderViewStateSet = renderView->stateSet();
1541
1542 {
1543 Profiling::GLTimeRecorder recorder(Profiling::StateUpdate, activeProfiler());
1544 // Set the RV state if not null,
1545 if (renderViewStateSet != nullptr)
1546 m_submissionContext->setCurrentStateSet(renderViewStateSet);
1547 else
1548 m_submissionContext->setCurrentStateSet(m_defaultRenderStateSet);
1549 }
1550
1551 // Set RenderTarget ...
1552 // Activate RenderTarget
1553 {
1554 Profiling::GLTimeRecorder recorder(Profiling::RenderTargetUpdate, activeProfiler());
1555 m_submissionContext->activateRenderTarget(id: renderView->renderTargetId(),
1556 attachments: renderView->attachmentPack(),
1557 defaultFboId: lastBoundFBOId);
1558 }
1559
1560 {
1561 Profiling::GLTimeRecorder recorder(Profiling::ClearBuffer, activeProfiler());
1562 // set color, depth, stencil clear values (only if needed)
1563 auto clearBufferTypes = renderView->clearTypes();
1564 if (clearBufferTypes & QClearBuffers::ColorBuffer) {
1565 const QVector4D vCol = renderView->globalClearColorBufferInfo().clearColor;
1566 m_submissionContext->clearColor(color: QColor::fromRgbF(r: vCol.x(), g: vCol.y(), b: vCol.z(), a: vCol.w()));
1567 }
1568 if (clearBufferTypes & QClearBuffers::DepthBuffer)
1569 m_submissionContext->clearDepthValue(depth: renderView->clearDepthValue());
1570 if (clearBufferTypes & QClearBuffers::StencilBuffer)
1571 m_submissionContext->clearStencilValue(stencil: renderView->clearStencilValue());
1572
1573 // Clear BackBuffer
1574 m_submissionContext->clearBackBuffer(buffers: clearBufferTypes);
1575
1576 // if there are ClearColors set for different draw buffers,
1577 // clear each of these draw buffers individually now
1578 const std::vector<ClearBufferInfo> &clearDrawBuffers = renderView->specificClearColorBufferInfo();
1579 for (const ClearBufferInfo &clearBuffer : clearDrawBuffers)
1580 m_submissionContext->clearBufferf(drawbuffer: clearBuffer.drawBufferIndex, values: clearBuffer.clearColor);
1581 }
1582
1583 // Set the Viewport
1584 m_submissionContext->setViewport(viewport: renderView->viewport(), surfaceSize: renderView->surfaceSize());
1585
1586 // Execute the render commands
1587 if (!executeCommandsSubmission(rv: renderView))
1588 m_lastFrameCorrect.storeRelaxed(newValue: 0); // something went wrong; make sure to render the next frame!
1589
1590 // executeCommandsSubmission takes care of restoring the stateset to the value
1591 // of gc->currentContext() at the moment it was called (either
1592 // renderViewStateSet or m_defaultRenderStateSet)
1593 if (!renderView->renderCaptureNodeId().isNull()) {
1594 const QRenderCaptureRequest request = renderView->renderCaptureRequest();
1595 const QSize size = m_submissionContext->renderTargetSize(surfaceSize: renderView->surfaceSize());
1596 QRect rect(QPoint(0, 0), size);
1597 if (!request.rect.isEmpty())
1598 rect = rect.intersected(other: request.rect);
1599 QImage image;
1600 if (!rect.isEmpty()) {
1601 // Bind fbo as read framebuffer
1602 m_submissionContext->bindFramebuffer(fbo: m_submissionContext->activeFBO(), mode: GraphicsHelperInterface::FBORead);
1603 image = m_submissionContext->readFramebuffer(rect);
1604 } else {
1605 qWarning() << "Requested capture rectangle is outside framebuffer";
1606 }
1607 Render::RenderCapture *renderCapture =
1608 static_cast<Render::RenderCapture*>(m_nodesManager->frameGraphManager()->lookupNode(id: renderView->renderCaptureNodeId()));
1609 renderCapture->addRenderCapture(captureId: request.captureId, image);
1610 const Qt3DCore::QNodeId renderCaptureId = renderView->renderCaptureNodeId();
1611 QMutexLocker lock(&m_pendingRenderCaptureSendRequestsMutex);
1612 if (!Qt3DCore::contains(destination: m_pendingRenderCaptureSendRequests, element: renderCaptureId))
1613 m_pendingRenderCaptureSendRequests.push_back(x: renderView->renderCaptureNodeId());
1614 }
1615
1616 if (renderView->isDownloadBuffersEnable())
1617 downloadGLBuffers();
1618
1619 // Perform BlitFramebuffer operations
1620 if (renderView->hasBlitFramebufferInfo()) {
1621 const auto &blitFramebufferInfo = renderView->blitFrameBufferInfo();
1622 const Qt3DCore::QNodeId inputTargetId = blitFramebufferInfo.sourceRenderTargetId;
1623 const Qt3DCore::QNodeId outputTargetId = blitFramebufferInfo.destinationRenderTargetId;
1624 const QRect inputRect = blitFramebufferInfo.sourceRect;
1625 const QRect outputRect = blitFramebufferInfo.destinationRect;
1626 const QRenderTargetOutput::AttachmentPoint inputAttachmentPoint = blitFramebufferInfo.sourceAttachmentPoint;
1627 const QRenderTargetOutput::AttachmentPoint outputAttachmentPoint = blitFramebufferInfo.destinationAttachmentPoint;
1628 const QBlitFramebuffer::InterpolationMethod interpolationMethod = blitFramebufferInfo.interpolationMethod;
1629 m_submissionContext->blitFramebuffer(outputRenderTargetId: inputTargetId, inputRenderTargetId: outputTargetId, inputRect, outputRect, defaultFboId: lastBoundFBOId,
1630 inputAttachmentPoint, outputAttachmentPoint,
1631 interpolationMethod);
1632 }
1633
1634#ifndef Q_OS_INTEGRITY
1635 if (!imGuiOverlayShown && renderView->showDebugOverlay()) {
1636 imGuiOverlayShown = true;
1637 if (!m_imGuiRenderer) {
1638 m_imGuiRenderer = new Debug::ImGuiRenderer(this);
1639 if (m_settings)
1640 m_imGuiRenderer->setCapabilities(m_settings->capabilities());
1641 }
1642
1643 m_imGuiRenderer->renderDebugOverlay(renderViews, renderView, jobsInLastFrame: m_jobsInLastFrame);
1644 }
1645#endif
1646
1647 frameElapsed = timer.elapsed() - frameElapsed;
1648 qCDebug(Rendering) << Q_FUNC_INFO << "Submitted Renderview " << i + 1 << "/" << renderViewsCount << "in " << frameElapsed << "ms";
1649 frameElapsed = timer.elapsed();
1650 }
1651
1652 // Bind lastBoundFBOId back. Needed also in threaded mode.
1653 // lastBoundFBOId != m_graphicsContext->activeFBO() when the last FrameGraph leaf node/renderView
1654 // contains RenderTargetSelector/RenderTarget
1655 if (lastBoundFBOId != m_submissionContext->activeFBO())
1656 m_submissionContext->bindFramebuffer(fbo: lastBoundFBOId, mode: GraphicsHelperInterface::FBOReadAndDraw);
1657
1658 // Reset state and call doneCurrent if the surface
1659 // is valid and was actually activated
1660 if (lastUsedSurface && m_submissionContext->hasValidGLHelper()) {
1661 // Reset state to the default state if the last stateset is not the
1662 // defaultRenderStateSet
1663 if (m_submissionContext->currentStateSet() != m_defaultRenderStateSet)
1664 m_submissionContext->setCurrentStateSet(m_defaultRenderStateSet);
1665 }
1666
1667 queueElapsed = timer.elapsed() - queueElapsed;
1668 qCDebug(Rendering) << Q_FUNC_INFO << "Submission of Queue in " << queueElapsed << "ms <=> " << queueElapsed / renderViewsCount << "ms per RenderView <=> Avg " << 1000.0f / (queueElapsed * 1.0f/ renderViewsCount * 1.0f) << " RenderView/s";
1669 qCDebug(Rendering) << Q_FUNC_INFO << "Submission Completed in " << timer.elapsed() << "ms";
1670
1671 // Stores the necessary information to safely perform
1672 // the last swap buffer call
1673 ViewSubmissionResultData resultData;
1674 resultData.lastBoundFBOId = lastBoundFBOId;
1675 resultData.surface = lastUsedSurface;
1676 return resultData;
1677}
1678
1679void Renderer::markDirty(BackendNodeDirtySet changes, BackendNode *node)
1680{
1681 Q_UNUSED(node);
1682 m_dirtyBits.marked |= changes;
1683}
1684
1685Renderer::BackendNodeDirtySet Renderer::dirtyBits()
1686{
1687 return m_dirtyBits.marked;
1688}
1689
1690#if defined(QT_BUILD_INTERNAL)
1691void Renderer::clearDirtyBits(BackendNodeDirtySet changes)
1692{
1693 m_dirtyBits.remaining &= ~changes;
1694 m_dirtyBits.marked &= ~changes;
1695}
1696#endif
1697
1698bool Renderer::shouldRender() const
1699{
1700 // Only render if something changed during the last frame, or the last frame
1701 // was not rendered successfully (or render-on-demand is disabled)
1702 return ((m_settings && m_settings->renderPolicy() == QRenderSettings::Always)
1703 || m_dirtyBits.marked != 0
1704 || m_dirtyBits.remaining != 0
1705 || !m_lastFrameCorrect.loadRelaxed());
1706}
1707
1708void Renderer::skipNextFrame()
1709{
1710 Q_ASSERT(m_settings->renderPolicy() != QRenderSettings::Always);
1711
1712 // make submitRenderViews() actually run
1713 m_renderQueue.setNoRender();
1714 m_submitRenderViewsSemaphore.release(n: 1);
1715}
1716
1717void Renderer::jobsDone(Qt3DCore::QAspectManager *manager)
1718{
1719 // called in main thread once all jobs are done running
1720
1721 // sync captured renders to frontend
1722 QMutexLocker lock(&m_pendingRenderCaptureSendRequestsMutex);
1723 const std::vector<Qt3DCore::QNodeId> pendingCaptureIds = Qt3DCore::moveAndClear(data&: m_pendingRenderCaptureSendRequests);
1724 lock.unlock();
1725 for (const Qt3DCore::QNodeId &id : pendingCaptureIds) {
1726 auto *backend = static_cast<Qt3DRender::Render::RenderCapture *>
1727 (m_nodesManager->frameGraphManager()->lookupNode(id));
1728 backend->syncRenderCapturesToFrontend(manager);
1729 }
1730
1731 // Do we need to notify any texture about property changes?
1732 if (m_updatedTextureProperties.size() > 0)
1733 sendTextureChangesToFrontend(manager);
1734
1735 sendDisablesToFrontend(manager);
1736 sendSetFenceHandlesToFrontend(manager);
1737}
1738
1739bool Renderer::processMouseEvent(QObject *object, QMouseEvent *event)
1740{
1741 Q_UNUSED(object);
1742
1743#ifndef Q_OS_INTEGRITY
1744 if (m_imGuiRenderer)
1745 m_imGuiRenderer->processEvent(event);
1746#endif
1747 return false;
1748}
1749
1750bool Renderer::processKeyEvent(QObject *object, QKeyEvent *event)
1751{
1752 Q_UNUSED(object);
1753
1754#ifndef Q_OS_INTEGRITY
1755 if (m_imGuiRenderer)
1756 m_imGuiRenderer->processEvent(event);
1757#endif
1758 return false;
1759}
1760
1761// Jobs we may have to run even if no rendering will happen
1762std::vector<Qt3DCore::QAspectJobPtr> Renderer::preRenderingJobs()
1763{
1764 if (m_sendBufferCaptureJob->hasRequests())
1765 return {m_sendBufferCaptureJob};
1766 return {};
1767}
1768
1769// Waits to be told to create jobs for the next frame
1770// Called by QRenderAspect jobsToExecute context of QAspectThread
1771// Returns all the jobs (and with proper dependency chain) required
1772// for the rendering of the scene
1773std::vector<Qt3DCore::QAspectJobPtr> Renderer::renderBinJobs()
1774{
1775 std::vector<Qt3DCore::QAspectJobPtr> renderBinJobs;
1776
1777 // Remove previous dependencies
1778 m_cleanupJob->removeDependency(dependency: QWeakPointer<Qt3DCore::QAspectJob>());
1779
1780 const bool dirtyParametersForCurrentFrame = m_dirtyBits.marked & AbstractRenderer::ParameterDirty;
1781 const BackendNodeDirtySet dirtyBitsForFrame = m_dirtyBits.marked | m_dirtyBits.remaining;
1782 m_dirtyBits.marked = {};
1783 m_dirtyBits.remaining = {};
1784 BackendNodeDirtySet notCleared = {};
1785
1786 // Add jobs
1787 if (dirtyBitsForFrame & AbstractRenderer::TransformDirty)
1788 renderBinJobs.push_back(x: m_updateShaderDataTransformJob);
1789
1790 // TO DO: Conditionally add if skeletons dirty
1791 renderBinJobs.push_back(x: m_cleanupJob);
1792
1793 // Jobs to prepare GL Resource upload
1794 renderBinJobs.push_back(x: m_vaoGathererJob);
1795
1796 if (dirtyBitsForFrame & AbstractRenderer::BuffersDirty)
1797 renderBinJobs.push_back(x: m_bufferGathererJob);
1798
1799 if (dirtyBitsForFrame & AbstractRenderer::TexturesDirty)
1800 renderBinJobs.push_back(x: m_textureGathererJob);
1801
1802 // Layer cache is dependent on layers, layer filters (hence FG structure
1803 // changes) and the enabled flag on entities
1804 const bool entitiesEnabledDirty = dirtyBitsForFrame & AbstractRenderer::EntityEnabledDirty;
1805 const bool frameGraphDirty = dirtyBitsForFrame & AbstractRenderer::FrameGraphDirty;
1806 const bool layersDirty = dirtyBitsForFrame & AbstractRenderer::LayersDirty;
1807 const bool layersCacheNeedsToBeRebuilt = layersDirty || entitiesEnabledDirty || frameGraphDirty;
1808 const bool shadersDirty = dirtyBitsForFrame & AbstractRenderer::ShadersDirty;
1809 const bool materialDirty = dirtyBitsForFrame & AbstractRenderer::MaterialDirty;
1810 const bool lightsDirty = dirtyBitsForFrame & AbstractRenderer::LightsDirty;
1811 const bool computeableDirty = dirtyBitsForFrame & AbstractRenderer::ComputeDirty;
1812 const bool renderableDirty = dirtyBitsForFrame & AbstractRenderer::GeometryDirty;
1813 const bool materialCacheNeedsToBeRebuilt = shadersDirty || materialDirty || frameGraphDirty;
1814 const bool renderCommandsDirty = materialCacheNeedsToBeRebuilt || renderableDirty || computeableDirty;
1815
1816 if (renderableDirty)
1817 renderBinJobs.push_back(x: m_renderableEntityFilterJob);
1818
1819 if (computeableDirty)
1820 renderBinJobs.push_back(x: m_computableEntityFilterJob);
1821
1822 if (lightsDirty)
1823 renderBinJobs.push_back(x: m_lightGathererJob);
1824
1825 // Sync rendering is synchronous, queue should always be reset
1826 // when this is called
1827 Q_ASSERT(m_renderQueue.wasReset());
1828 // Traverse the current framegraph. For each leaf node create a
1829 // RenderView and set its configuration then create a job to
1830 // populate the RenderView with a set of RenderCommands that get
1831 // their details from the RenderNodes that are visible to the
1832 // Camera selected by the framegraph configuration
1833 if (frameGraphDirty) {
1834 FrameGraphVisitor visitor(m_nodesManager->frameGraphManager());
1835 m_frameGraphLeaves = visitor.traverse(root: frameGraphRoot());
1836 // Remove leaf nodes that no longer exist from cache
1837 const QList<FrameGraphNode *> keys = m_cache.leafNodeCache.keys();
1838 for (FrameGraphNode *leafNode : keys) {
1839 if (!Qt3DCore::contains(destination: m_frameGraphLeaves, element: leafNode))
1840 m_cache.leafNodeCache.remove(key: leafNode);
1841 }
1842
1843 // Handle single shot subtree enablers
1844 const auto subtreeEnablers = visitor.takeEnablersToDisable();
1845 for (auto *node : subtreeEnablers)
1846 m_updatedDisableSubtreeEnablers.push_back(x: node->peerId());
1847 }
1848
1849 int idealThreadCount = Qt3DCore::QAspectJobManager::idealThreadCount();
1850
1851 const size_t fgBranchCount = m_frameGraphLeaves.size();
1852 if (fgBranchCount > 1) {
1853 int workBranches = int(fgBranchCount);
1854 for (auto leaf: m_frameGraphLeaves)
1855 if (leaf->nodeType() == FrameGraphNode::NoDraw)
1856 --workBranches;
1857
1858 if (idealThreadCount > 4 && workBranches)
1859 idealThreadCount = qMax(a: 4, b: idealThreadCount / workBranches);
1860 }
1861
1862 for (size_t i = 0; i < fgBranchCount; ++i) {
1863 FrameGraphNode *leaf = m_frameGraphLeaves[i];
1864 RenderViewBuilder builder(leaf, int(i), this);
1865 builder.setOptimalJobCount(leaf->nodeType() == FrameGraphNode::NoDraw ? 1 : idealThreadCount);
1866
1867 // If we have a new RV (wasn't in the cache before, then it contains no cached data)
1868 const bool isNewRV = !m_cache.leafNodeCache.contains(key: leaf);
1869 builder.setLayerCacheNeedsToBeRebuilt(layersCacheNeedsToBeRebuilt || isNewRV);
1870 builder.setMaterialGathererCacheNeedsToBeRebuilt(materialCacheNeedsToBeRebuilt || isNewRV);
1871 builder.setRenderCommandCacheNeedsToBeRebuilt(renderCommandsDirty || isNewRV);
1872 builder.setLightCacheNeedsToBeRebuilt(lightsDirty);
1873
1874 // Insert leaf into cache
1875 if (isNewRV) {
1876 m_cache.leafNodeCache[leaf] = {};
1877 }
1878 builder.prepareJobs();
1879 Qt3DCore::moveAtEnd(destination&: renderBinJobs, source: builder.buildJobHierachy());
1880 }
1881
1882 // Set target number of RenderViews
1883 m_renderQueue.setTargetRenderViewCount(int(fgBranchCount));
1884
1885 if (isRunning() && m_submissionContext->isInitialized()) {
1886 if (dirtyBitsForFrame & AbstractRenderer::TechniquesDirty )
1887 renderBinJobs.push_back(x: m_filterCompatibleTechniqueJob);
1888 if (dirtyBitsForFrame & AbstractRenderer::ShadersDirty)
1889 renderBinJobs.push_back(x: m_introspectShaderJob);
1890 } else {
1891 notCleared |= AbstractRenderer::TechniquesDirty;
1892 notCleared |= AbstractRenderer::ShadersDirty;
1893 }
1894
1895 m_dirtyBits.remaining = dirtyBitsForFrame & notCleared;
1896
1897 // Dirty Parameters might need 2 frames to react if the parameter references a texture
1898 if (dirtyParametersForCurrentFrame)
1899 m_dirtyBits.remaining |= AbstractRenderer::ParameterDirty;
1900
1901 return renderBinJobs;
1902}
1903
1904Qt3DCore::QAbstractFrameAdvanceService *Renderer::frameAdvanceService() const
1905{
1906 return static_cast<Qt3DCore::QAbstractFrameAdvanceService *>(m_vsyncFrameAdvanceService.data());
1907}
1908
1909// Called by executeCommands
1910void Renderer::performDraw(const RenderCommand *command)
1911{
1912 // Indirect Draw Calls
1913 if (command->m_drawIndirect) {
1914
1915 // Bind the indirect draw buffer
1916 Buffer *indirectDrawBuffer = m_nodesManager->bufferManager()->data(handle: command->m_indirectDrawBuffer);
1917 if (Q_UNLIKELY(indirectDrawBuffer == nullptr)) {
1918 qWarning() << "Invalid Indirect Draw Buffer - failed to retrieve Buffer";
1919 return;
1920 }
1921
1922 // Get GLBuffer from Buffer;
1923 GLBuffer *indirectDrawGLBuffer = m_submissionContext->glBufferForRenderBuffer(buf: indirectDrawBuffer);
1924 if (Q_UNLIKELY(indirectDrawGLBuffer == nullptr)) {
1925 qWarning() << "Invalid Indirect Draw Buffer - failed to retrieve GLBuffer";
1926 return;
1927 }
1928
1929 // Bind GLBuffer
1930 const bool successfullyBound = indirectDrawGLBuffer->bind(ctx: m_submissionContext.data(), t: GLBuffer::DrawIndirectBuffer);
1931
1932 if (Q_LIKELY(successfullyBound)) {
1933 // TO DO: Handle multi draw variants if attribute count > 1
1934 if (command->m_drawIndexed) {
1935 m_submissionContext->drawElementsIndirect(mode: command->m_primitiveType,
1936 type: command->m_indexAttributeDataType,
1937 indirect: reinterpret_cast<void*>(quintptr(command->m_indirectAttributeByteOffset)));
1938 } else {
1939 m_submissionContext->drawArraysIndirect(mode: command->m_primitiveType,
1940 indirect: reinterpret_cast<void*>(quintptr(command->m_indirectAttributeByteOffset)));
1941 }
1942 } else {
1943 qWarning() << "Failed to bind IndirectDrawBuffer";
1944 }
1945
1946 } else { // Direct Draw Calls
1947
1948 // TO DO: Add glMulti Draw variants
1949 if (command->m_primitiveType == QGeometryRenderer::Patches)
1950 m_submissionContext->setVerticesPerPatch(command->m_verticesPerPatch);
1951
1952 if (command->m_primitiveRestartEnabled)
1953 m_submissionContext->enablePrimitiveRestart(restartIndex: command->m_restartIndexValue);
1954
1955 // TO DO: Add glMulti Draw variants
1956 if (command->m_drawIndexed) {
1957 Profiling::GLTimeRecorder recorder(Profiling::DrawElement, activeProfiler());
1958 m_submissionContext->drawElementsInstancedBaseVertexBaseInstance(primitiveType: command->m_primitiveType,
1959 primitiveCount: command->m_primitiveCount,
1960 indexType: command->m_indexAttributeDataType,
1961 indices: reinterpret_cast<void*>(quintptr(command->m_indexAttributeByteOffset)),
1962 instances: command->m_instanceCount,
1963 baseVertex: command->m_indexOffset,
1964 baseInstance: command->m_firstInstance);
1965 } else {
1966 Profiling::GLTimeRecorder recorder(Profiling::DrawArray, activeProfiler());
1967 m_submissionContext->drawArraysInstancedBaseInstance(primitiveType: command->m_primitiveType,
1968 first: command->m_firstVertex,
1969 count: command->m_primitiveCount,
1970 instances: command->m_instanceCount,
1971 baseinstance: command->m_firstInstance);
1972 }
1973 }
1974
1975#if defined(QT3D_RENDER_ASPECT_OPENGL_DEBUG)
1976 int err = m_submissionContext->openGLContext()->functions()->glGetError();
1977 if (err)
1978 qCWarning(Rendering) << "GL error after drawing mesh:" << QString::number(err, 16);
1979#endif
1980
1981 if (command->m_primitiveRestartEnabled)
1982 m_submissionContext->disablePrimitiveRestart();
1983}
1984
1985void Renderer::performCompute(const RenderView *, RenderCommand *command)
1986{
1987 {
1988 Profiling::GLTimeRecorder recorder(Profiling::ShaderUpdate, activeProfiler());
1989 GLShader *shader = m_glResourceManagers->glShaderManager()->lookupResource(shaderId: command->m_shaderId);
1990 m_submissionContext->activateShader(shader);
1991 }
1992 {
1993 Profiling::GLTimeRecorder recorder(Profiling::UniformUpdate, activeProfiler());
1994 m_submissionContext->setParameters(parameterPack&: command->m_parameterPack, shader: command->m_glShader);
1995 }
1996 {
1997 Profiling::GLTimeRecorder recorder(Profiling::DispatchCompute, activeProfiler());
1998 m_submissionContext->dispatchCompute(x: command->m_workGroups[0],
1999 y: command->m_workGroups[1],
2000 z: command->m_workGroups[2]);
2001 }
2002 // HACK: Reset the compute flag to dirty
2003 m_dirtyBits.marked |= AbstractRenderer::ComputeDirty;
2004
2005#if defined(QT3D_RENDER_ASPECT_OPENGL_DEBUG)
2006 int err = m_submissionContext->openGLContext()->functions()->glGetError();
2007 if (err)
2008 qCWarning(Rendering) << "GL error after drawing mesh:" << QString::number(err, 16);
2009#endif
2010}
2011
2012void Renderer::createOrUpdateVAO(RenderCommand *command,
2013 HVao *previousVaoHandle,
2014 OpenGLVertexArrayObject **vao)
2015{
2016 const VAOIdentifier vaoKey(command->m_geometry, command->m_shaderId);
2017
2018 VAOManager *vaoManager = m_glResourceManagers->vaoManager();
2019 command->m_vao = vaoManager->lookupHandle(id: vaoKey);
2020
2021 if (command->m_vao.isNull()) {
2022 qCDebug(Rendering) << Q_FUNC_INFO << "Allocating new VAO";
2023 command->m_vao = vaoManager->getOrAcquireHandle(id: vaoKey);
2024 vaoManager->data(handle: command->m_vao)->create(ctx: m_submissionContext.data(), key: vaoKey);
2025 }
2026
2027 if (*previousVaoHandle != command->m_vao) {
2028 *previousVaoHandle = command->m_vao;
2029 *vao = vaoManager->data(handle: command->m_vao);
2030 }
2031 Q_ASSERT(*vao);
2032}
2033
2034// Called by RenderView->submit() in RenderThread context
2035// Returns true, if all RenderCommands were sent to the GPU
2036bool Renderer::executeCommandsSubmission(RenderView *rv)
2037{
2038 bool allCommandsIssued = true;
2039
2040 // Render drawing commands
2041
2042 // Use the graphicscontext to submit the commands to the underlying
2043 // graphics API (OpenGL)
2044
2045 // Save the RenderView base stateset
2046 RenderStateSet *globalState = m_submissionContext->currentStateSet();
2047 OpenGLVertexArrayObject *vao = nullptr;
2048
2049 rv->forEachCommand(func: [&] (RenderCommand &command) {
2050
2051 if (command.m_type == RenderCommand::Compute) { // Compute Call
2052 performCompute(rv, command: &command);
2053 } else { // Draw Command
2054 // Check if we have a valid command that can be drawn
2055 if (!command.m_isValid) {
2056 allCommandsIssued = false;
2057 return;
2058 }
2059
2060 vao = m_glResourceManagers->vaoManager()->data(handle: command.m_vao);
2061
2062 // something may have went wrong when initializing the VAO
2063 if (!vao->isSpecified()) {
2064 allCommandsIssued = false;
2065 return;
2066 }
2067
2068 {
2069 Profiling::GLTimeRecorder recorder(Profiling::ShaderUpdate, activeProfiler());
2070 //// We activate the shader here
2071 GLShader *shader = command.m_glShader;
2072 if (!m_submissionContext->activateShader(shader)) {
2073 allCommandsIssued = false;
2074 return;
2075 }
2076 }
2077
2078 {
2079 Profiling::GLTimeRecorder recorder(Profiling::VAOUpdate, activeProfiler());
2080 // Bind VAO
2081 vao->bind();
2082 }
2083
2084 {
2085 Profiling::GLTimeRecorder recorder(Profiling::UniformUpdate, activeProfiler());
2086 //// Update program uniforms
2087 if (!m_submissionContext->setParameters(parameterPack&: command.m_parameterPack, shader: command.m_glShader)) {
2088 allCommandsIssued = false;
2089 // If we have failed to set uniform (e.g unable to bind a texture)
2090 // we won't perform the draw call which could show invalid content
2091 return;
2092 }
2093 }
2094
2095 //// OpenGL State
2096 // TO DO: Make states not dependendent on their backend node for this step
2097 // Set state
2098 RenderStateSet *localState = command.m_stateSet.data();
2099
2100
2101 {
2102 Profiling::GLTimeRecorder recorder(Profiling::StateUpdate, activeProfiler());
2103 // Merge the RenderCommand state with the globalState of the RenderView
2104 // Or restore the globalState if no stateSet for the RenderCommand
2105 if (localState != nullptr) {
2106 command.m_stateSet->merge(other: globalState);
2107 m_submissionContext->setCurrentStateSet(localState);
2108 } else {
2109 m_submissionContext->setCurrentStateSet(globalState);
2110 }
2111 }
2112 // All Uniforms for a pass are stored in the QUniformPack of the command
2113 // Uniforms for Effect, Material and Technique should already have been correctly resolved
2114 // at that point
2115
2116 //// Draw Calls
2117 performDraw(command: &command);
2118 }
2119 }); // end of RenderCommands loop
2120
2121 // We cache the VAO and release it only at the end of the exectute frame
2122 // We try to minimize VAO binding between RenderCommands
2123 if (vao)
2124 vao->release();
2125
2126 // Reset to the state we were in before executing the render commands
2127 m_submissionContext->setCurrentStateSet(globalState);
2128
2129 return allCommandsIssued;
2130}
2131
2132bool Renderer::updateVAOWithAttributes(Geometry *geometry,
2133 const RenderCommand *command,
2134 GLShader *shader,
2135 bool forceUpdate)
2136{
2137 m_dirtyAttributes.reserve(n: m_dirtyAttributes.size() + geometry->attributes().size());
2138 const auto attributeIds = geometry->attributes();
2139
2140 for (Qt3DCore::QNodeId attributeId : attributeIds) {
2141 // TO DO: Improvement we could store handles and use the non locking policy on the attributeManager
2142 Attribute *attribute = m_nodesManager->attributeManager()->lookupResource(id: attributeId);
2143
2144 if (attribute == nullptr)
2145 return false;
2146
2147 Buffer *buffer = m_nodesManager->bufferManager()->lookupResource(id: attribute->bufferId());
2148
2149 // Buffer update was already performed at this point
2150 // Just make sure the attribute reference a valid buffer
2151 if (buffer == nullptr)
2152 return false;
2153
2154 // Index Attribute
2155 bool attributeWasDirty = false;
2156 if (attribute->attributeType() == Qt3DCore::QAttribute::IndexAttribute) {
2157 if ((attributeWasDirty = attribute->isDirty()) == true || forceUpdate)
2158 m_submissionContext->specifyIndices(buffer);
2159 // Vertex Attribute
2160 } else if (Qt3DCore::contains(destination: command->m_activeAttributes, element: attribute->nameId())) {
2161 if ((attributeWasDirty = attribute->isDirty()) == true || forceUpdate) {
2162 // Find the location for the attribute
2163 const std::vector<ShaderAttribute> &shaderAttributes = shader->attributes();
2164 const ShaderAttribute *attributeDescription = nullptr;
2165 for (const ShaderAttribute &shaderAttribute : shaderAttributes) {
2166 if (shaderAttribute.m_nameId == attribute->nameId()) {
2167 attributeDescription = &shaderAttribute;
2168 break;
2169 }
2170 }
2171 if (!attributeDescription || attributeDescription->m_location < 0)
2172 return false;
2173 m_submissionContext->specifyAttribute(attribute, buffer, attributeDescription);
2174 }
2175 }
2176
2177 // Append attribute to temporary vector so that its dirtiness
2178 // can be cleared at the end of the frame
2179 if (attributeWasDirty)
2180 m_dirtyAttributes.push_back(x: attribute);
2181
2182 // Note: We cannot call unsertDirty on the Attribute at this
2183 // point as we don't know if the attributes are being shared
2184 // with other geometry / geometryRenderer in which case they still
2185 // should remain dirty so that VAO for these commands are properly
2186 // updated
2187 }
2188
2189 return true;
2190}
2191
2192bool Renderer::requiresVAOAttributeUpdate(Geometry *geometry,
2193 const RenderCommand *command) const
2194{
2195 const auto attributeIds = geometry->attributes();
2196
2197 for (Qt3DCore::QNodeId attributeId : attributeIds) {
2198 // TO DO: Improvement we could store handles and use the non locking policy on the attributeManager
2199 Attribute *attribute = m_nodesManager->attributeManager()->lookupResource(id: attributeId);
2200
2201 if (attribute == nullptr)
2202 continue;
2203
2204 if ((attribute->attributeType() == Qt3DCore::QAttribute::IndexAttribute && attribute->isDirty()) || (Qt3DCore::contains(destination: command->m_activeAttributes, element: attribute->nameId()) && attribute->isDirty()))
2205 return true;
2206 }
2207 return false;
2208}
2209
2210// Erase graphics related resources that may become unused after a frame
2211void Renderer::cleanGraphicsResources()
2212{
2213 // Clean buffers
2214 const QList<Qt3DCore::QNodeId> buffersToRelease = m_nodesManager->bufferManager()->takeBuffersToRelease();
2215 for (Qt3DCore::QNodeId bufferId : buffersToRelease)
2216 m_submissionContext->releaseBuffer(bufferId);
2217
2218 // When Textures are cleaned up, their id is saved so that they can be
2219 // cleaned up in the render thread
2220 const QList<Qt3DCore::QNodeId> cleanedUpTextureIds = Qt3DCore::moveAndClear(data&: m_textureIdsToCleanup);
2221 for (const Qt3DCore::QNodeId &textureCleanedUpId: cleanedUpTextureIds)
2222 cleanupTexture(cleanedUpTextureId: textureCleanedUpId);
2223
2224 // Delete abandoned VAOs
2225 m_abandonedVaosMutex.lock();
2226 const std::vector<HVao> abandonedVaos = Qt3DCore::moveAndClear(data&: m_abandonedVaos);
2227 m_abandonedVaosMutex.unlock();
2228 for (const HVao &vaoHandle : abandonedVaos) {
2229 // might have already been destroyed last frame, but added by the cleanup job before, so
2230 // check if the VAO is really still existent
2231 OpenGLVertexArrayObject *vao = m_glResourceManagers->vaoManager()->data(handle: vaoHandle);
2232 if (vao) {
2233 vao->destroy();
2234 // We remove VAO from manager using its VAOIdentifier
2235 m_glResourceManagers->vaoManager()->release(handle: vaoHandle);
2236 }
2237 }
2238
2239 // Abandon GL shaders when a Shader node is destroyed Note: We are sure
2240 // that when this gets executed, all scene changes have been received and
2241 // shader nodes updated
2242 const QList<Qt3DCore::QNodeId> cleanedUpShaderIds = m_nodesManager->shaderManager()->takeShaderIdsToCleanup();
2243 for (const Qt3DCore::QNodeId &shaderCleanedUpId: cleanedUpShaderIds) {
2244 cleanupShader(shader: m_nodesManager->shaderManager()->lookupResource(id: shaderCleanedUpId));
2245 // We can really release the texture at this point
2246 m_nodesManager->shaderManager()->releaseResource(id: shaderCleanedUpId);
2247 }
2248 m_lastLoadedShaderIds.clear();
2249}
2250
2251const GraphicsApiFilterData *Renderer::contextInfo() const
2252{
2253 return m_submissionContext->contextInfo();
2254}
2255
2256SubmissionContext *Renderer::submissionContext() const
2257{
2258 return m_submissionContext.data();
2259}
2260
2261} // namespace OpenGL
2262} // namespace Render
2263} // namespace Qt3DRender
2264
2265QT_END_NAMESPACE
2266

source code of qt3d/src/plugins/renderers/opengl/renderer/renderer.cpp