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

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