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