1 /****************************************************************************
2 **
3 ** Copyright (C) 2020 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
85 #include <Qt3DRender/qcameralens.h>
86 #include <Qt3DCore/private/qabstractaspectjobmanager_p.h>
87 #include <Qt3DCore/private/qaspectmanager_p.h>
88 #include <Qt3DCore/private/qsysteminformationservice_p.h>
89 #include <Qt3DCore/private/qsysteminformationservice_p_p.h>
90 #include <Qt3DRender/private/resourceaccessor_p.h>
91 #include <Qt3DRender/private/renderlogging_p.h>
92 #include <Qt3DRender/private/renderstateset_p.h>
93 #include <Qt3DRender/private/setfence_p.h>
94 #include <Qt3DRender/private/stringtoint_p.h>
95 #include <Qt3DRender/private/qrenderaspect_p.h>
96
97 #include <rhibuffer_p.h>
98 #include <rhigraphicspipeline_p.h>
99
100 #include <rendercommand_p.h>
101 #include <renderqueue_p.h>
102 #include <renderview_p.h>
103 #include <texture_p.h>
104 #include <renderviewbuilder_p.h>
105 #include <rhiresourcemanagers_p.h>
106 #include <commandexecuter_p.h>
107 #include <submissioncontext_p.h>
108
109 #include <QStack>
110 #include <QOffscreenSurface>
111 #include <QSurface>
112 #include <QElapsedTimer>
113 #include <QLibraryInfo>
114 #include <QMutexLocker>
115 #include <QPluginLoader>
116 #include <QDir>
117 #include <QUrl>
118 #include <QOffscreenSurface>
119 #include <QWindow>
120 #include <QThread>
121 #include <QKeyEvent>
122 #include <QMouseEvent>
123
124 #include <QtGui/private/qopenglcontext_p.h>
125 #include <QGuiApplication>
126
127 QT_BEGIN_NAMESPACE
128
129 using namespace Qt3DCore;
130
131 namespace Qt3DRender {
132 namespace Render {
133 namespace Rhi {
134
135 namespace {
136
137 class CachingLightGatherer : public LightGatherer
138 {
139 public:
CachingLightGatherer(RendererCache * cache)140 CachingLightGatherer(RendererCache *cache) : LightGatherer(), m_cache(cache) { }
141
run()142 void run() override
143 {
144 LightGatherer::run();
145
146 QMutexLocker lock(m_cache->mutex());
147 m_cache->gatheredLights = lights();
148 m_cache->environmentLight = environmentLight();
149 }
150
151 private:
152 RendererCache *m_cache;
153 };
154
155 class CachingRenderableEntityFilter : public RenderableEntityFilter
156 {
157 public:
CachingRenderableEntityFilter(RendererCache * cache)158 CachingRenderableEntityFilter(RendererCache *cache) : RenderableEntityFilter(), m_cache(cache)
159 {
160 }
161
run()162 void run() override
163 {
164 RenderableEntityFilter::run();
165
166 QVector<Entity *> selectedEntities = filteredEntities();
167 std::sort(selectedEntities.begin(), selectedEntities.end());
168
169 QMutexLocker lock(m_cache->mutex());
170 m_cache->renderableEntities = selectedEntities;
171 }
172
173 private:
174 RendererCache *m_cache;
175 };
176
177 class CachingComputableEntityFilter : public ComputableEntityFilter
178 {
179 public:
CachingComputableEntityFilter(RendererCache * cache)180 CachingComputableEntityFilter(RendererCache *cache) : ComputableEntityFilter(), m_cache(cache)
181 {
182 }
183
run()184 void run() override
185 {
186 ComputableEntityFilter::run();
187
188 QVector<Entity *> selectedEntities = filteredEntities();
189 std::sort(selectedEntities.begin(), selectedEntities.end());
190
191 QMutexLocker lock(m_cache->mutex());
192 m_cache->computeEntities = selectedEntities;
193 }
194
195 private:
196 RendererCache *m_cache;
197 };
198
locationForAttribute(Attribute * attr,RHIShader * shader)199 int locationForAttribute(Attribute *attr, RHIShader *shader) noexcept
200 {
201 const QVector<ShaderAttribute> attribInfo = shader->attributes();
202 const auto it = std::find_if(
203 attribInfo.begin(), attribInfo.end(),
204 [attr](const ShaderAttribute &sAttr) { return attr->nameId() == sAttr.m_nameId; });
205 if (it != attribInfo.end())
206 return it->m_location;
207 return -1;
208 }
209
210 } // anonymous
211
212 /*!
213 \internal
214
215 Renderer shutdown procedure:
216
217 Since the renderer relies on the surface and OpenGLContext to perform its cleanup,
218 it is shutdown when the surface is set to nullptr
219
220 When the surface is set to nullptr this will request the RenderThread to terminate
221 and will prevent createRenderBinJobs from returning a set of jobs as there is nothing
222 more to be rendered.
223
224 In turn, this will call shutdown which will make the OpenGL context current one last time
225 to allow cleanups requiring a call to QOpenGLContext::currentContext to execute properly.
226 At the end of that function, the GraphicsContext is set to null.
227
228 At this point though, the QAspectThread is still running its event loop and will only stop
229 a short while after.
230 */
231
Renderer(QRenderAspect::RenderType type)232 Renderer::Renderer(QRenderAspect::RenderType type)
233 : m_services(nullptr),
234 m_aspect(nullptr),
235 m_nodesManager(nullptr),
236 m_renderSceneRoot(nullptr),
237 m_defaultRenderStateSet(nullptr),
238 m_submissionContext(nullptr),
239 m_renderQueue(new RenderQueue()),
240 m_renderThread(type == QRenderAspect::Threaded ? new RenderThread(this) : nullptr),
241 m_vsyncFrameAdvanceService(new VSyncFrameAdvanceService(m_renderThread != nullptr)),
242 m_waitForInitializationToBeCompleted(0),
243 m_hasBeenInitializedMutex(),
244 m_exposed(0),
245 m_lastFrameCorrect(0),
246 m_glContext(nullptr),
247 m_time(0),
248 m_settings(nullptr),
249 m_updateShaderDataTransformJob(Render::UpdateShaderDataTransformJobPtr::create()),
250 m_cleanupJob(Render::FrameCleanupJobPtr::create()),
251 m_sendBufferCaptureJob(Render::SendBufferCaptureJobPtr::create()),
252 m_filterCompatibleTechniqueJob(FilterCompatibleTechniqueJobPtr::create()),
253 m_lightGathererJob(new CachingLightGatherer(&m_cache)),
254 m_renderableEntityFilterJob(new CachingRenderableEntityFilter(&m_cache)),
255 m_computableEntityFilterJob(new CachingComputableEntityFilter(&m_cache)),
256 m_bufferGathererJob(SynchronizerJobPtr::create([this] { lookForDirtyBuffers(); },
257 JobTypes::DirtyBufferGathering)),
__anon62ce21360402null258 m_textureGathererJob(SynchronizerJobPtr::create([this] { lookForDirtyTextures(); },
259 JobTypes::DirtyTextureGathering)),
260 m_introspectShaderJob(SynchronizerPostFramePtr::create(
__anon62ce21360502null261 [this] { reloadDirtyShaders(); },
__anon62ce21360602(Qt3DCore::QAspectManager *m) 262 [this](Qt3DCore::QAspectManager *m) { sendShaderChangesToFrontend(m); },
263 JobTypes::DirtyShaderGathering)),
264 m_ownedContext(false),
265 m_offscreenHelper(nullptr),
266 m_RHIResourceManagers(nullptr),
267 m_commandExecuter(new Qt3DRender::Debug::CommandExecuter(this)),
268 m_shouldSwapBuffers(true)
269 {
270 std::fill_n(m_textureTransform, 4, 0.f);
271
272 // Set renderer as running - it will wait in the context of the
273 // RenderThread for RenderViews to be submitted
274 m_running.fetchAndStoreOrdered(1);
275 if (m_renderThread)
276 m_renderThread->waitForStart();
277
278 m_introspectShaderJob->addDependency(m_filterCompatibleTechniqueJob);
279
280 m_filterCompatibleTechniqueJob->setRenderer(this);
281
282 m_defaultRenderStateSet = new RenderStateSet;
283 m_defaultRenderStateSet->addState(StateVariant::createState<DepthTest>(GL_LESS));
284 m_defaultRenderStateSet->addState(StateVariant::createState<CullFace>(GL_BACK));
285 m_defaultRenderStateSet->addState(StateVariant::createState<ColorMask>(true, true, true, true));
286 }
287
~Renderer()288 Renderer::~Renderer()
289 {
290 Q_ASSERT(m_running.fetchAndStoreOrdered(0) == 0);
291 if (m_renderThread)
292 Q_ASSERT(m_renderThread->isFinished());
293
294 delete m_renderQueue;
295 delete m_defaultRenderStateSet;
296 delete m_RHIResourceManagers;
297
298 if (!m_ownedContext)
299 QObject::disconnect(m_contextConnection);
300 }
301
dumpInfo() const302 void Renderer::dumpInfo() const
303 {
304 qDebug() << Q_FUNC_INFO << "t =" << m_time;
305
306 const ShaderManager *shaderManager = m_nodesManager->shaderManager();
307 qDebug() << "=== Shader Manager ===";
308 qDebug() << *shaderManager;
309
310 const TextureManager *textureManager = m_nodesManager->textureManager();
311 qDebug() << "=== Texture Manager ===";
312 qDebug() << *textureManager;
313
314 const TextureImageManager *textureImageManager = m_nodesManager->textureImageManager();
315 qDebug() << "=== Texture Image Manager ===";
316 qDebug() << *textureImageManager;
317 }
318
api() const319 API Renderer::api() const
320 {
321 return API::OpenGL;
322 }
323
time() const324 qint64 Renderer::time() const
325 {
326 return m_time;
327 }
328
setTime(qint64 time)329 void Renderer::setTime(qint64 time)
330 {
331 m_time = time;
332 }
333
setJobsInLastFrame(int jobsInLastFrame)334 void Renderer::setJobsInLastFrame(int jobsInLastFrame)
335 {
336 m_jobsInLastFrame = jobsInLastFrame;
337 }
338
setAspect(QRenderAspect * aspect)339 void Renderer::setAspect(QRenderAspect *aspect)
340 {
341 m_aspect = aspect;
342 m_updateShaderDataTransformJob->addDependency(
343 QRenderAspectPrivate::get(aspect)->m_worldTransformJob);
344 }
345
setNodeManagers(NodeManagers * managers)346 void Renderer::setNodeManagers(NodeManagers *managers)
347 {
348 m_nodesManager = managers;
349 m_RHIResourceManagers = new RHIResourceManagers();
350 m_scene2DResourceAccessor.reset(new ResourceAccessor(this, m_nodesManager));
351
352 m_updateShaderDataTransformJob->setManagers(m_nodesManager);
353 m_cleanupJob->setManagers(m_nodesManager);
354 m_filterCompatibleTechniqueJob->setManager(m_nodesManager->techniqueManager());
355 m_sendBufferCaptureJob->setManagers(m_nodesManager);
356 m_lightGathererJob->setManager(m_nodesManager->renderNodesManager());
357 m_renderableEntityFilterJob->setManager(m_nodesManager->renderNodesManager());
358 m_computableEntityFilterJob->setManager(m_nodesManager->renderNodesManager());
359 }
360
setServices(QServiceLocator * services)361 void Renderer::setServices(QServiceLocator *services)
362 {
363 m_services = services;
364
365 m_nodesManager->sceneManager()->setDownloadService(m_services->downloadHelperService());
366 }
367
aspect() const368 QRenderAspect *Renderer::aspect() const
369 {
370 return m_aspect;
371 }
372
nodeManagers() const373 NodeManagers *Renderer::nodeManagers() const
374 {
375 return m_nodesManager;
376 }
377
378 /*!
379 \internal
380
381 Return context which can be used to share resources safely
382 with qt3d main render context.
383 */
shareContext() const384 QOpenGLContext *Renderer::shareContext() const
385 {
386 return nullptr;
387 }
388
389 // Executed in the reloadDirtyShader job
loadShader(Shader * shader,HShader shaderHandle)390 void Renderer::loadShader(Shader *shader, HShader shaderHandle)
391 {
392 Q_UNUSED(shader);
393 if (!m_dirtyShaders.contains(shaderHandle))
394 m_dirtyShaders.push_back(shaderHandle);
395 }
396
setOpenGLContext(QOpenGLContext * context)397 void Renderer::setOpenGLContext(QOpenGLContext *context)
398 {
399 m_glContext = context;
400 }
401
setScreen(QScreen * scr)402 void Renderer::setScreen(QScreen *scr)
403 {
404 m_screen = scr;
405 }
406
screen() const407 QScreen *Renderer::screen() const
408 {
409 return m_screen;
410 }
411
accessOpenGLTexture(Qt3DCore::QNodeId nodeId,QOpenGLTexture ** texture,QMutex ** lock,bool readonly)412 bool Renderer::accessOpenGLTexture(Qt3DCore::QNodeId nodeId, QOpenGLTexture **texture,
413 QMutex **lock, bool readonly)
414 {
415 RHI_UNIMPLEMENTED;
416
417 Texture *tex = m_nodesManager->textureManager()->lookupResource(nodeId);
418 if (!tex)
419 return false;
420
421 RHITexture *glTex = m_RHIResourceManagers->rhiTextureManager()->lookupResource(tex->peerId());
422 if (!glTex)
423 return false;
424
425 if (glTex->isDirty())
426 return false;
427
428 if (!readonly)
429 glTex->setExternalRenderingEnabled(true);
430
431 // RHITexture::TextureUpdateInfo texInfo =
432 // glTex->createOrUpdateRhiTexture(m_submissionContext.data()); *texture = texInfo.texture;
433
434 if (!readonly)
435 *lock = glTex->externalRenderingLock();
436
437 return true;
438 }
439
resourceAccessor() const440 QSharedPointer<RenderBackendResourceAccessor> Renderer::resourceAccessor() const
441 {
442 return m_scene2DResourceAccessor;
443 }
444
445 // Called in RenderThread context by the run method of RenderThread
446 // RenderThread has locked the mutex already and unlocks it when this
447 // method termintates
initialize()448 void Renderer::initialize()
449 {
450 QMutexLocker lock(&m_hasBeenInitializedMutex);
451 m_submissionContext.reset(new SubmissionContext);
452 m_submissionContext->setRenderer(this);
453
454 // RHI initialization
455 {
456 qCDebug(Backend) << Q_FUNC_INFO << "Requesting renderer initialize";
457 m_submissionContext->initialize();
458
459 // We need to adapt texture coordinates
460 // m_textureTransform is (a;b) in texCoord = a * texCoord + b
461 if (m_submissionContext->rhi()->isYUpInFramebuffer()) {
462 // OpenGL case - that is what we assume to be the default so we do not change
463 // anything
464 m_textureTransform[0] = 1.f;
465 m_textureTransform[1] = 1.f;
466 m_textureTransform[2] = 0.f;
467 m_textureTransform[3] = 0.f;
468 } else {
469 // Other cases : y = 1 - y
470 m_textureTransform[0] = 1.f;
471 m_textureTransform[1] = -1.f;
472 m_textureTransform[2] = 0.f;
473 m_textureTransform[3] = 1.f;
474 }
475
476 // Awake setScenegraphRoot in case it was waiting
477 m_waitForInitializationToBeCompleted.release(1);
478
479 // Allow the aspect manager to proceed
480 m_vsyncFrameAdvanceService->proceedToNextFrame();
481
482 // Force initial refresh
483 markDirty(AllDirty, nullptr);
484 return;
485 }
486 }
487
488 /*!
489 * \internal
490 *
491 * Signals for the renderer to stop rendering. If a threaded renderer is in use,
492 * the render thread will call releaseGraphicsResources() just before the thread exits.
493 * If rendering synchronously, this function will call releaseGraphicsResources().
494 */
shutdown()495 void Renderer::shutdown()
496 {
497 // Ensure we have waited to be fully initialized before trying to shut down
498 // (in case initialization is taking place at the same time)
499 QMutexLocker lock(&m_hasBeenInitializedMutex);
500
501 qCDebug(Backend) << Q_FUNC_INFO << "Requesting renderer shutdown";
502 m_running.storeRelaxed(0);
503
504 // We delete any renderqueue that we may not have had time to render
505 // before the surface was destroyed
506 QMutexLocker lockRenderQueue(m_renderQueue->mutex());
507 qDeleteAll(m_renderQueue->nextFrameQueue());
508 m_renderQueue->reset();
509 lockRenderQueue.unlock();
510
511 if (!m_renderThread) {
512 releaseGraphicsResources();
513 } else {
514 // Wake up the render thread in case it is waiting for some renderviews
515 // to be ready. The isReadyToSubmit() function checks for a shutdown
516 // having been requested.
517 m_submitRenderViewsSemaphore.release(1);
518 m_renderThread->wait();
519 }
520
521 // Destroy internal managers
522 // This needs to be done before the nodeManager is destroy
523 // as the internal resources might somehow rely on nodeManager resources
524 delete m_RHIResourceManagers;
525 m_RHIResourceManagers = nullptr;
526 }
527
528 /*!
529 \internal
530
531 When using a threaded renderer this function is called in the context of the
532 RenderThread to do any shutdown and cleanup that needs to be performed in the
533 thread where the OpenGL context lives.
534
535 When using Scene3D or anything that provides a custom QOpenGLContext (not
536 owned by Qt3D) this function is called whenever the signal
537 QOpenGLContext::aboutToBeDestroyed is emitted. In that case this function
538 is called in the context of the emitter's thread.
539 */
releaseGraphicsResources()540 void Renderer::releaseGraphicsResources()
541 {
542 // We may get called twice when running inside of a Scene3D. Once when Qt Quick
543 // wants to shutdown, and again when the render aspect gets unregistered. So
544 // check that we haven't already cleaned up before going any further.
545 if (!m_submissionContext)
546 return;
547
548 // Try to temporarily make the context current so we can free up any resources
549 QMutexLocker locker(&m_offscreenSurfaceMutex);
550 QOffscreenSurface *offscreenSurface = m_offscreenHelper->offscreenSurface();
551 if (!offscreenSurface) {
552 qWarning() << "Failed to make context current: OpenGL resources will not be destroyed";
553 // We still need to delete the submission context
554 m_submissionContext.reset(nullptr);
555 return;
556 }
557
558 //* QOpenGLContext *context = m_submissionContext->openGLContext();
559 //* Q_ASSERT(context);
560 //*
561 //* if (context->thread() == QThread::currentThread() && context->makeCurrent(offscreenSurface))
562 //{
563 //*
564 //* // Clean up the graphics context and any resources
565 //* const QVector<HRHITexture> activeTexturesHandles =
566 //m_RHIResourceManagers->rhiTextureManager()->activeHandles();
567 //* for (const HRHITexture &textureHandle : activeTexturesHandles) {
568 //* RHITexture *tex = m_RHIResourceManagers->rhiTextureManager()->data(textureHandle);
569 //* tex->destroy();
570 //* }
571 //*
572 //* // Do the same thing with buffers
573 //* const QVector<HRHIBuffer> activeBuffers =
574 //m_RHIResourceManagers->rhiBufferManager()->activeHandles();
575 //* for (const HRHIBuffer &bufferHandle : activeBuffers) {
576 //* RHIBuffer *buffer = m_RHIResourceManagers->rhiBufferManager()->data(bufferHandle);
577 //* buffer->destroy(m_submissionContext.data());
578 //* }
579 //*
580 //* // Do the same thing with shaders
581 //* const QVector<RHIShader *> shaders =
582 //m_RHIResourceManagers->rhiShaderManager()->takeActiveResources();
583 //* qDeleteAll(shaders);
584 //*
585 //*
586 //* context->doneCurrent();
587 //* } else {
588 //* qWarning() << "Failed to make context current: OpenGL resources will not be destroyed";
589 //* }
590 //*
591 //* if (m_ownedContext)
592 //* delete context;
593
594 m_submissionContext.reset(nullptr);
595
596 qCDebug(Backend) << Q_FUNC_INFO << "Renderer properly shutdown";
597 }
598
setSurfaceExposed(bool exposed)599 void Renderer::setSurfaceExposed(bool exposed)
600 {
601 qCDebug(Backend) << "Window exposed: " << exposed;
602 m_exposed.fetchAndStoreOrdered(exposed);
603 }
604
frameGraphRoot() const605 Render::FrameGraphNode *Renderer::frameGraphRoot() const
606 {
607 Q_ASSERT(m_settings);
608 if (m_nodesManager && m_nodesManager->frameGraphManager() && m_settings)
609 return m_nodesManager->frameGraphManager()->lookupNode(m_settings->activeFrameGraphID());
610 return nullptr;
611 }
612
613 // QAspectThread context
614 // Order of execution :
615 // 1) RenderThread is created -> release 1 of m_waitForInitializationToBeCompleted when started
616 // 2) setSceneRoot waits to acquire initialization
617 // 3) submitRenderView -> check for surface
618 // -> make surface current + create proper glHelper if needed
setSceneRoot(Entity * sgRoot)619 void Renderer::setSceneRoot(Entity *sgRoot)
620 {
621 Q_ASSERT(sgRoot);
622
623 // If initialization hasn't been completed we must wait
624 m_waitForInitializationToBeCompleted.acquire();
625
626 m_renderSceneRoot = sgRoot;
627 if (!m_renderSceneRoot)
628 qCWarning(Backend) << "Failed to build render scene";
629 m_renderSceneRoot->dump();
630 qCDebug(Backend) << Q_FUNC_INFO << "DUMPING SCENE";
631
632 // Set the scene root on the jobs
633 m_cleanupJob->setRoot(m_renderSceneRoot);
634
635 // Set all flags to dirty
636 m_dirtyBits.marked |= AbstractRenderer::AllDirty;
637 }
638
setSettings(RenderSettings * settings)639 void Renderer::setSettings(RenderSettings *settings)
640 {
641 m_settings = settings;
642 }
643
settings() const644 RenderSettings *Renderer::settings() const
645 {
646 return m_settings;
647 }
648
render()649 void Renderer::render()
650 {
651 // Traversing the framegraph tree from root to lead node
652 // Allows us to define the rendering set up
653 // Camera, RenderTarget ...
654
655 // Utimately the renderer should be a framework
656 // For the processing of the list of renderviews
657
658 // Matrice update, bounding volumes computation ...
659 // Should be jobs
660
661 // namespace Qt3DCore has 2 distincts node trees
662 // One scene description
663 // One framegraph description
664
665 while (m_running.loadRelaxed() > 0) {
666 doRender();
667 // TO DO: Restore windows exposed detection
668 // Probably needs to happens some place else though
669 }
670 }
671
672 // Either called by render if Qt3D is in charge of the RenderThread
673 // or by QRenderAspectPrivate::renderSynchronous (for Scene3D)
doRender(bool swapBuffers)674 void Renderer::doRender(bool swapBuffers)
675 {
676 Renderer::ViewSubmissionResultData submissionData;
677 bool hasCleanedQueueAndProceeded = false;
678 bool preprocessingComplete = false;
679 bool beganDrawing = false;
680
681 // Blocking until RenderQueue is full
682 const bool canSubmit = isReadyToSubmit();
683 m_shouldSwapBuffers = swapBuffers;
684
685 // Lock the mutex to protect access to the renderQueue while we look for its state
686 QMutexLocker locker(m_renderQueue->mutex());
687 const bool queueIsComplete = m_renderQueue->isFrameQueueComplete();
688 const bool queueIsEmpty = m_renderQueue->targetRenderViewCount() == 0;
689
690 bool mustCleanResources = false;
691
692 // When using synchronous rendering (QtQuick)
693 // We are not sure that the frame queue is actually complete
694 // Since a call to render may not be synched with the completions
695 // of the RenderViewJobs
696 // In such a case we return early, waiting for a next call with
697 // the frame queue complete at this point
698
699 // RenderQueue is complete (but that means it may be of size 0)
700 if (canSubmit && (queueIsComplete && !queueIsEmpty)) {
701 const QVector<Render::Rhi::RenderView *> renderViews = m_renderQueue->nextFrameQueue();
702 QTaskLogger submissionStatsPart1(m_services->systemInformation(),
703 { JobTypes::FrameSubmissionPart1, 0 },
704 QTaskLogger::Submission);
705 QTaskLogger submissionStatsPart2(m_services->systemInformation(),
706 { JobTypes::FrameSubmissionPart2, 0 },
707 QTaskLogger::Submission);
708
709 QVector<RHIPassInfo> rhiPassesInfo;
710
711 if (canRender()) {
712 QSurface *surface = nullptr;
713 for (const RenderView *rv : renderViews) {
714 surface = rv->surface();
715 if (surface)
716 break;
717 }
718
719 // In case we did not draw because e.g. there wase no swapchain,
720 // we keep the resource updates from the previous frame.
721 if (!m_submissionContext->m_currentUpdates) {
722 m_submissionContext->m_currentUpdates =
723 m_submissionContext->rhi()->nextResourceUpdateBatch();
724 }
725
726 // 1) Execute commands for buffer uploads, texture updates, shader loading first
727 updateResources();
728
729 rhiPassesInfo = prepareCommandsSubmission(renderViews);
730 // 2) Update Pipelines and copy data into commands to allow concurrent submission
731 preprocessingComplete = true;
732
733 bool hasCommands = false;
734 for (const RenderView *rv : renderViews) {
735 const auto &commands = rv->commands();
736 hasCommands = std::any_of(commands.begin(), commands.end(),
737 [](const RenderCommand &cmd) { return cmd.isValid(); });
738 if (hasCommands)
739 break;
740 }
741
742 if (hasCommands) {
743 // Scoped to destroy surfaceLock
744 SurfaceLocker surfaceLock(surface);
745 const bool surfaceIsValid = (surface && surfaceLock.isSurfaceValid());
746 if (surfaceIsValid) {
747 beganDrawing = m_submissionContext->beginDrawing(surface);
748 if (beganDrawing) {
749 // Purge shader which aren't used any longer
750 static int callCount = 0;
751 ++callCount;
752 const int shaderPurgePeriod = 600;
753 if (callCount % shaderPurgePeriod == 0)
754 m_RHIResourceManagers->rhiShaderManager()->purge();
755 }
756 }
757 }
758 // 2) Proceed to next frame and start preparing frame n + 1
759 m_renderQueue->reset();
760 locker.unlock(); // Done protecting RenderQueue
761 m_vsyncFrameAdvanceService->proceedToNextFrame();
762 hasCleanedQueueAndProceeded = true;
763
764 // Only try to submit the RenderViews if the preprocessing was successful
765 // This part of the submission is happening in parallel to the RV building for the next
766 // frame
767 if (beganDrawing) {
768 submissionStatsPart1.end(submissionStatsPart2.restart());
769
770 // 3) Submit the render commands for frame n (making sure we never reference
771 // something that could be changing) Render using current device state and renderer
772 // configuration
773 submissionData = submitRenderViews(rhiPassesInfo);
774
775 // Perform any required cleanup of the Graphics resources (Buffers deleted, Shader
776 // deleted...)
777 mustCleanResources = true;
778 }
779 }
780
781 // Execute the pending shell commands
782 m_commandExecuter->performAsynchronousCommandExecution(renderViews);
783
784 // Delete all the RenderViews which will clear the allocators
785 // that were used for their allocation
786 qDeleteAll(renderViews);
787 }
788
789 // If hasCleanedQueueAndProceeded isn't true this implies that something went wrong
790 // with the rendering and/or the renderqueue is incomplete from some reason
791 // or alternatively it could be complete but empty (RenderQueue of size 0)
792
793 if (!hasCleanedQueueAndProceeded) {
794 // RenderQueue was full but something bad happened when
795 // trying to render it and therefore proceedToNextFrame was not called
796 // Note: in this case the renderQueue mutex is still locked
797
798 // Reset the m_renderQueue so that we won't try to render
799 // with a queue used by a previous frame with corrupted content
800 // if the current queue was correctly submitted
801 m_renderQueue->reset();
802
803 // We allow the RenderTickClock service to proceed to the next frame
804 // In turn this will allow the aspect manager to request a new set of jobs
805 // to be performed for each aspect
806 m_vsyncFrameAdvanceService->proceedToNextFrame();
807 }
808
809 // Perform the last swapBuffers calls after the proceedToNextFrame
810 // as this allows us to gain a bit of time for the preparation of the
811 // next frame
812 // Finish up with last surface used in the list of RenderViews
813 if (beganDrawing) {
814 SurfaceLocker surfaceLock(submissionData.surface);
815 // Finish up with last surface used in the list of RenderViews
816 const bool swapBuffers = submissionData.lastBoundFBOId == m_submissionContext->defaultFBO()
817 && surfaceLock.isSurfaceValid() && m_shouldSwapBuffers;
818 m_submissionContext->endDrawing(swapBuffers);
819
820 if (mustCleanResources)
821 cleanGraphicsResources();
822 }
823 }
824
825 // Called by RenderViewJobs
826 // When the frameQueue is complete and we are using a renderThread
827 // we allow the render thread to proceed
enqueueRenderView(RenderView * renderView,int submitOrder)828 void Renderer::enqueueRenderView(RenderView *renderView, int submitOrder)
829 {
830 QMutexLocker locker(m_renderQueue->mutex()); // Prevent out of order execution
831 // We cannot use a lock free primitive here because:
832 // - QVector is not thread safe
833 // - Even if the insert is made correctly, the isFrameComplete call
834 // could be invalid since depending on the order of execution
835 // the counter could be complete but the renderview not yet added to the
836 // buffer depending on whichever order the cpu decides to process this
837 const bool isQueueComplete = m_renderQueue->queueRenderView(renderView, submitOrder);
838 locker.unlock(); // We're done protecting the queue at this point
839 if (isQueueComplete) {
840 if (m_renderThread && m_running.loadRelaxed())
841 Q_ASSERT(m_submitRenderViewsSemaphore.available() == 0);
842 m_submitRenderViewsSemaphore.release(1);
843 }
844 }
845
canRender() const846 bool Renderer::canRender() const
847
848 {
849 // Make sure that we've not been told to terminate
850 if (m_renderThread && !m_running.loadRelaxed()) {
851 qCDebug(Rendering) << "RenderThread termination requested whilst waiting";
852 return false;
853 }
854
855 // TO DO: Check if all surfaces have been destroyed...
856 // It may be better if the last window to be closed trigger a call to shutdown
857 // Rather than having checks for the surface everywhere
858
859 return true;
860 }
861
isReadyToSubmit()862 bool Renderer::isReadyToSubmit()
863 {
864 // Make sure that we've been told to render before rendering
865 // Prevent ouf of order execution
866 m_submitRenderViewsSemaphore.acquire(1);
867
868 // Check if shutdown has been requested
869 if (m_running.loadRelaxed() == 0)
870 return false;
871
872 // The semaphore should only
873 // be released when the frame queue is complete and there's
874 // something to render
875 // The case of shutdown should have been handled just before
876 Q_ASSERT(m_renderQueue->isFrameQueueComplete());
877 return true;
878 }
879
880 // Main thread
executeCommand(const QStringList & args)881 QVariant Renderer::executeCommand(const QStringList &args)
882 {
883 return m_commandExecuter->executeCommand(args);
884 }
885
886 /*!
887 \internal
888 Called in the context of the aspect thread from QRenderAspect::onRegistered
889 */
setOffscreenSurfaceHelper(OffscreenSurfaceHelper * helper)890 void Renderer::setOffscreenSurfaceHelper(OffscreenSurfaceHelper *helper)
891 {
892 QMutexLocker locker(&m_offscreenSurfaceMutex);
893 m_offscreenHelper = helper;
894 }
895
format()896 QSurfaceFormat Renderer::format()
897 {
898 return m_submissionContext->format();
899 }
900
updateGraphicsPipeline(RenderCommand & cmd,RenderView * rv,int renderViewIndex)901 void Renderer::updateGraphicsPipeline(RenderCommand &cmd, RenderView *rv, int renderViewIndex)
902 {
903 if (!cmd.m_rhiShader) {
904 qDebug() << "Warning: command has no shader";
905 return;
906 }
907
908 // The Graphics Pipeline defines
909 // - Render State (Depth, Culling, Stencil, Blending)
910 // - Shader Resources Binding
911 // - Shader Vertex Attribute Layout
912
913 // This means we need to have one GraphicsPipeline per
914 // - geometry
915 // - material
916 // - state (RV + RC)
917
918 RenderStateSet *renderState = nullptr;
919 {
920 RenderStateSet *globalState =
921 (rv->stateSet() != nullptr) ? rv->stateSet() : m_defaultRenderStateSet;
922
923 // Merge global state into local state
924 RenderStateSet *localState = cmd.m_stateSet.data();
925 if (localState != nullptr) {
926 localState->merge(globalState);
927 renderState = localState;
928 } else {
929 renderState = globalState;
930 }
931 }
932
933 // Try to retrieve existing pipeline
934 auto &pipelineManager = *m_RHIResourceManagers->rhiGraphicsPipelineManager();
935 const GraphicsPipelineIdentifier pipelineKey { cmd.m_geometry, cmd.m_shaderId,
936 renderViewIndex };
937 RHIGraphicsPipeline *graphicsPipeline = pipelineManager.getOrCreateResource(pipelineKey);
938 // TO DO: Ensure we find a way to know when the state is dirty to trigger a rebuild
939
940 if (!graphicsPipeline) {
941 qDebug() << "Warning : could not create a graphics pipeline";
942 return;
943 }
944
945 // Increase score so that we know the pipeline was used for this frame and shouldn't be
946 // destroyed
947 graphicsPipeline->increaseScore();
948
949 // TO DO: Set to true if geometry, shader or render state dirty
950 bool requiredRebuild = false;
951
952 // Note: we can rebuild add/remove things from the QRhiShaderResourceBindings after having
953 // created the pipeline and rebuild it. Changes should be picked up automatically
954
955 // Create pipeline if it doesn't exist or needs to be updated
956 if (graphicsPipeline->pipeline() == nullptr || requiredRebuild) {
957 bool ok = true;
958
959 const SubmissionContext::SwapChainInfo *swapchain =
960 m_submissionContext->swapChainForSurface(rv->surface());
961 if (!swapchain || !swapchain->swapChain || !swapchain->renderPassDescriptor)
962 return;
963
964 // TO DO: Find a way to recycle those
965 // Create Per Command UBO
966 QRhiBuffer *commandUBO = m_submissionContext->rhi()->newBuffer(
967 QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, sizeof(CommandUBO));
968 QRhiBuffer *rvUBO = m_submissionContext->rhi()->newBuffer(
969 QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, sizeof(RenderViewUBO));
970 commandUBO->build();
971 rvUBO->build();
972 graphicsPipeline->setCommandUBO(commandUBO);
973 graphicsPipeline->setRenderViewUBO(rvUBO);
974
975 QVector<QRhiShaderResourceBinding> uboBindings;
976 uboBindings << QRhiShaderResourceBinding::uniformBuffer(
977 0,
978 QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage,
979 rvUBO)
980 << QRhiShaderResourceBinding::uniformBuffer(
981 1,
982 QRhiShaderResourceBinding::VertexStage
983 | QRhiShaderResourceBinding::FragmentStage,
984 commandUBO);
985
986 // Create additional empty UBO Buffer for UBO with binding point > 1 (since we assume 0 and
987 // 1 and for Qt3D standard values)
988 const QVector<ShaderUniformBlock> uniformBlocks = cmd.m_rhiShader->uniformBlocks();
989 QHash<int, RHIGraphicsPipeline::UBOBuffer> uboBuffers;
990 for (const ShaderUniformBlock &block : uniformBlocks) {
991 if (block.m_binding > 1) {
992 auto handle = m_RHIResourceManagers->rhiBufferManager()->allocateResource();
993 RHIBuffer *ubo = m_RHIResourceManagers->rhiBufferManager()->data(handle);
994 Q_ASSERT(ubo);
995 const QByteArray rawData(block.m_size, '\0');
996 ubo->allocate(m_submissionContext.data(), rawData, true);
997 ok = ubo->bind(m_submissionContext.data(), RHIBuffer::UniformBuffer);
998 uboBuffers[block.m_binding] = { handle, ubo };
999 uboBindings << QRhiShaderResourceBinding::uniformBuffer(
1000 block.m_binding,
1001 QRhiShaderResourceBinding::VertexStage
1002 | QRhiShaderResourceBinding::FragmentStage,
1003 ubo->rhiBuffer());
1004 }
1005 }
1006 graphicsPipeline->setUBOs(uboBuffers);
1007
1008 // Samplers
1009 for (const auto &textureParameter : cmd.m_parameterPack.textures()) {
1010 const auto handle = m_RHIResourceManagers->rhiTextureManager()->getOrAcquireHandle(
1011 textureParameter.nodeId);
1012 const auto textureData = m_RHIResourceManagers->rhiTextureManager()->data(handle);
1013
1014 for (const ShaderAttribute &samplerAttribute : cmd.m_rhiShader->samplers()) {
1015 if (samplerAttribute.m_nameId == textureParameter.glslNameId) {
1016 const auto rhiTexture = textureData->getRhiTexture();
1017 const auto rhiSampler = textureData->getRhiSampler();
1018 if (rhiTexture && rhiSampler) {
1019 uboBindings.push_back(QRhiShaderResourceBinding::sampledTexture(
1020 samplerAttribute.m_location,
1021 QRhiShaderResourceBinding::FragmentStage, rhiTexture, rhiSampler));
1022 }
1023 }
1024 }
1025 }
1026
1027 QRhiShaderResourceBindings *shaderResourceBindings =
1028 m_submissionContext->rhi()->newShaderResourceBindings();
1029 assert(shaderResourceBindings);
1030
1031 shaderResourceBindings->setBindings(uboBindings.cbegin(), uboBindings.cend());
1032 ok = shaderResourceBindings->build();
1033 assert(ok);
1034
1035 // Create pipeline
1036 QRhiGraphicsPipeline *pipeline = m_submissionContext->rhi()->newGraphicsPipeline();
1037 graphicsPipeline->setShaderResourceBindings(shaderResourceBindings);
1038 graphicsPipeline->setPipeline(pipeline);
1039 assert(pipeline);
1040
1041 const QShader vertexShader = cmd.m_rhiShader->shaderStage(QShader::VertexStage);
1042 const QShader fragmentShader = cmd.m_rhiShader->shaderStage(QShader::FragmentStage);
1043
1044 assert(vertexShader.isValid());
1045 assert(fragmentShader.isValid());
1046
1047 pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vertexShader },
1048 { QRhiShaderStage::Fragment, fragmentShader } });
1049
1050 QVarLengthArray<QRhiVertexInputBinding, 8> inputBindings;
1051 QVarLengthArray<QRhiVertexInputAttribute, 8> rhiAttributes;
1052
1053 const auto geom = cmd.m_geometry;
1054 const auto &attributes = geom->attributes();
1055
1056 struct BufferBinding
1057 {
1058 Qt3DCore::QNodeId bufferId;
1059 uint stride;
1060 QRhiVertexInputBinding::Classification classification;
1061 uint attributeDivisor;
1062 };
1063 QVector<BufferBinding> uniqueBindings;
1064
1065 auto rhiAttributeType = [](Attribute *attr) {
1066 switch (attr->vertexBaseType()) {
1067 case QAttribute::Byte:
1068 case QAttribute::UnsignedByte: {
1069 if (attr->vertexSize() == 1)
1070 return QRhiVertexInputAttribute::UNormByte;
1071 if (attr->vertexSize() == 2)
1072 return QRhiVertexInputAttribute::UNormByte2;
1073 if (attr->vertexSize() == 4)
1074 return QRhiVertexInputAttribute::UNormByte4;
1075 Q_FALLTHROUGH();
1076 }
1077 case QAttribute::Float: {
1078 if (attr->vertexSize() == 1)
1079 return QRhiVertexInputAttribute::Float;
1080 if (attr->vertexSize() == 2)
1081 return QRhiVertexInputAttribute::Float2;
1082 if (attr->vertexSize() == 3)
1083 return QRhiVertexInputAttribute::Float3;
1084 if (attr->vertexSize() == 4)
1085 return QRhiVertexInputAttribute::Float4;
1086 Q_FALLTHROUGH();
1087 }
1088 default:
1089 qWarning() << "Attribute type not handles by RHI";
1090 Q_UNREACHABLE();
1091 }
1092 };
1093
1094 // QRhiVertexInputBinding -> specifies the stride of an attribute, whether it's per vertex
1095 // or per instance and the instance divisor QRhiVertexInputAttribute -> specifies the format
1096 // of the attribute (offset, type), the shader location and the index of the binding
1097 // QRhiCommandBuffer::VertexInput -> binds a buffer to a binding
1098
1099 QHash<int, int> attributeNameToBinding;
1100
1101 for (Qt3DCore::QNodeId attribute_id : attributes) {
1102 Attribute *attrib = m_nodesManager->attributeManager()->lookupResource(attribute_id);
1103 if (attrib->attributeType() == QAttribute::VertexAttribute) {
1104 const bool isPerInstanceAttr = attrib->divisor() != 0;
1105 const QRhiVertexInputBinding::Classification classification = isPerInstanceAttr
1106 ? QRhiVertexInputBinding::PerInstance
1107 : QRhiVertexInputBinding::PerVertex;
1108 const BufferBinding binding { attrib->bufferId(), attrib->byteStride(),
1109 classification,
1110 isPerInstanceAttr ? attrib->divisor() : 1U };
1111
1112 const auto it =
1113 std::find_if(uniqueBindings.begin(), uniqueBindings.end(),
1114 [binding](const BufferBinding &a) {
1115 return binding.bufferId == a.bufferId
1116 && binding.stride == a.stride
1117 && binding.classification == a.classification
1118 && binding.attributeDivisor == a.attributeDivisor;
1119 });
1120
1121 int bindingIndex = uniqueBindings.size();
1122 if (it == uniqueBindings.end())
1123 uniqueBindings.push_back(binding);
1124 else
1125 bindingIndex = std::distance(uniqueBindings.begin(), it);
1126
1127 rhiAttributes.push_back({ bindingIndex,
1128 locationForAttribute(attrib, cmd.m_rhiShader),
1129 rhiAttributeType(attrib), attrib->byteOffset() });
1130
1131 attributeNameToBinding.insert(attrib->nameId(), bindingIndex);
1132 }
1133 }
1134
1135 inputBindings.resize(uniqueBindings.size());
1136 for (int i = 0, m = uniqueBindings.size(); i < m; ++i) {
1137 const BufferBinding binding = uniqueBindings.at(i);
1138 /*
1139 qDebug() << binding.bufferId
1140 << binding.stride
1141 << binding.classification
1142 << binding.attributeDivisor;
1143 */
1144 inputBindings[i] = QRhiVertexInputBinding { binding.stride, binding.classification,
1145 int(binding.attributeDivisor) };
1146 }
1147
1148 QRhiVertexInputLayout inputLayout;
1149 inputLayout.setBindings(inputBindings.begin(), inputBindings.end());
1150 inputLayout.setAttributes(rhiAttributes.begin(), rhiAttributes.end());
1151
1152 pipeline->setVertexInputLayout(inputLayout);
1153 pipeline->setShaderResourceBindings(shaderResourceBindings);
1154
1155 pipeline->setRenderPassDescriptor(swapchain->renderPassDescriptor);
1156
1157 graphicsPipeline->setAttributesToBindingHash(attributeNameToBinding);
1158
1159 // Render States
1160 m_submissionContext->applyStateSet(renderState, pipeline);
1161
1162 ok = pipeline->build();
1163 assert(ok);
1164 }
1165
1166 // Record RHIGraphicsPipeline into command for later use
1167 if (graphicsPipeline && graphicsPipeline->pipeline())
1168 cmd.pipeline = graphicsPipeline;
1169 }
1170
1171 // When this function is called, we must not be processing the commands for frame n+1
1172 QVector<Renderer::RHIPassInfo>
prepareCommandsSubmission(const QVector<RenderView * > & renderViews)1173 Renderer::prepareCommandsSubmission(const QVector<RenderView *> &renderViews)
1174 {
1175 // TO DO: Find a central place to initialize RHI resources
1176 const int renderViewCount = renderViews.size();
1177
1178 // We need to have a single RHI RenderPass per RenderTarget
1179 // as creating the pass clears the buffers
1180 // 1) We need to find all adjacents RenderViews that have the same renderTarget
1181 // and submit all of these as part of the same RHI pass
1182 QVector<RHIPassInfo> rhiPassesInfo;
1183
1184 for (int i = 0; i < renderViewCount;) {
1185 QVector<RenderView *> sameRenderTargetRVs;
1186 QVector<QRhiBuffer *> rvUbos;
1187 RenderView *refRV = renderViews.at(i);
1188 sameRenderTargetRVs.push_back(refRV);
1189
1190 for (i = i + 1; i < renderViewCount; ++i) {
1191 RenderView *curRV = renderViews.at(i);
1192 if (refRV->renderTargetId() == curRV->renderTargetId()) {
1193 sameRenderTargetRVs.push_back(curRV);
1194 } else
1195 break;
1196 }
1197
1198 RHIPassInfo bucket;
1199 bucket.rvs = std::move(sameRenderTargetRVs);
1200 bucket.surface = refRV->surface();
1201 bucket.renderTargetId = refRV->renderTargetId();
1202 bucket.attachmentPack = refRV->attachmentPack();
1203 rhiPassesInfo.push_back(bucket);
1204 }
1205
1206 for (int i = 0; i < renderViewCount; ++i) {
1207 RenderView *rv = renderViews.at(i);
1208 QVector<RenderCommand> &commands = rv->commands();
1209 for (RenderCommand &command : commands) {
1210 // Update/Create GraphicsPipelines
1211 if (command.m_type == RenderCommand::Draw) {
1212 Geometry *rGeometry =
1213 m_nodesManager->data<Geometry, GeometryManager>(command.m_geometry);
1214 GeometryRenderer *rGeometryRenderer =
1215 m_nodesManager->data<GeometryRenderer, GeometryRendererManager>(
1216 command.m_geometryRenderer);
1217 // By this time shaders should have been loaded
1218 RHIShader *shader = m_RHIResourceManagers->rhiShaderManager()->lookupResource(
1219 command.m_shaderId);
1220 if (!shader) {
1221 qDebug() << "Warning: could not find shader";
1222 continue;
1223 }
1224
1225 // We should never have inserted a command for which these are null
1226 // in the first place
1227 Q_ASSERT(rGeometry && rGeometryRenderer && shader);
1228
1229 // Unset dirtiness on rGeometryRenderer only
1230 // The rGeometry may be shared by several rGeometryRenderer
1231 // so we cannot unset its dirtiness at this point
1232 if (rGeometryRenderer->isDirty())
1233 rGeometryRenderer->unsetDirty();
1234
1235 // Prepare the ShaderParameterPack based on the active uniforms of the shader
1236 // shader->prepareUniforms(command.m_parameterPack);
1237
1238 updateGraphicsPipeline(command, rv, i);
1239
1240 } else if (command.m_type == RenderCommand::Compute) {
1241 RHI_UNIMPLEMENTED;
1242 // By this time shaders have been loaded
1243 RHIShader *shader = m_RHIResourceManagers->rhiShaderManager()->lookupResource(
1244 command.m_shaderId);
1245 command.m_rhiShader = shader;
1246 Q_ASSERT(shader);
1247
1248 // Prepare the ShaderParameterPack based on the active uniforms of the shader
1249 // shader->prepareUniforms(command.m_parameterPack);
1250 }
1251 }
1252 }
1253
1254 // Unset dirtiness on Geometry and Attributes
1255 // Note: we cannot do it in the loop above as we want to be sure that all
1256 // the VAO which reference the geometry/attributes are properly updated
1257 RHI_UNIMPLEMENTED;
1258 for (Attribute *attribute : qAsConst(m_dirtyAttributes))
1259 attribute->unsetDirty();
1260 m_dirtyAttributes.clear();
1261
1262 for (Geometry *geometry : qAsConst(m_dirtyGeometry))
1263 geometry->unsetDirty();
1264 m_dirtyGeometry.clear();
1265
1266 return rhiPassesInfo;
1267 }
1268
1269 // Executed in a job
lookForDirtyBuffers()1270 void Renderer::lookForDirtyBuffers()
1271 {
1272 const std::vector<HBuffer> &activeBufferHandles = m_nodesManager->bufferManager()->activeHandles();
1273 for (const HBuffer &handle : activeBufferHandles) {
1274 Buffer *buffer = m_nodesManager->bufferManager()->data(handle);
1275 if (buffer->isDirty())
1276 m_dirtyBuffers.push_back(handle);
1277 }
1278 }
1279
1280 // Called in prepareSubmission
lookForDownloadableBuffers()1281 void Renderer::lookForDownloadableBuffers()
1282 {
1283 m_downloadableBuffers.clear();
1284 const std::vector<HBuffer> &activeBufferHandles = m_nodesManager->bufferManager()->activeHandles();
1285 for (const HBuffer &handle : activeBufferHandles) {
1286 Buffer *buffer = m_nodesManager->bufferManager()->data(handle);
1287 if (buffer->access() & QBuffer::Read)
1288 m_downloadableBuffers.push_back(buffer->peerId());
1289 }
1290 }
1291
1292 // Executed in a job
lookForDirtyTextures()1293 void Renderer::lookForDirtyTextures()
1294 {
1295 // To avoid having Texture or TextureImage maintain relationships between
1296 // one another, we instead perform a lookup here to check if a texture
1297 // image has been updated to then notify textures referencing the image
1298 // that they need to be updated
1299 TextureImageManager *imageManager = m_nodesManager->textureImageManager();
1300 const std::vector<HTextureImage> &activeTextureImageHandles = imageManager->activeHandles();
1301 Qt3DCore::QNodeIdVector dirtyImageIds;
1302 for (const HTextureImage &handle : activeTextureImageHandles) {
1303 TextureImage *image = imageManager->data(handle);
1304 if (image->isDirty()) {
1305 dirtyImageIds.push_back(image->peerId());
1306 image->unsetDirty();
1307 }
1308 }
1309
1310 TextureManager *textureManager = m_nodesManager->textureManager();
1311 const std::vector<HTexture> &activeTextureHandles = textureManager->activeHandles();
1312 for (const HTexture &handle : activeTextureHandles) {
1313 Texture *texture = textureManager->data(handle);
1314 const QNodeIdVector imageIds = texture->textureImageIds();
1315
1316 // Does the texture reference any of the dirty texture images?
1317 for (const QNodeId imageId : imageIds) {
1318 if (dirtyImageIds.contains(imageId)) {
1319 texture->addDirtyFlag(Texture::DirtyImageGenerators);
1320 break;
1321 }
1322 }
1323
1324 // Dirty meaning that something has changed on the texture
1325 // either properties, parameters, shared texture id, generator or a texture image
1326 if (texture->dirtyFlags() != Texture::NotDirty)
1327 m_dirtyTextures.push_back(handle);
1328 // Note: texture dirty flags are reset when actually updating the
1329 // textures in updateGLResources() as resetting flags here would make
1330 // us lose information about what was dirty exactly.
1331 }
1332 }
1333
1334 // Executed in a job
reloadDirtyShaders()1335 void Renderer::reloadDirtyShaders()
1336 {
1337 Q_ASSERT(isRunning());
1338 const std::vector<HTechnique> &activeTechniques =
1339 m_nodesManager->techniqueManager()->activeHandles();
1340 const std::vector<HShaderBuilder> &activeBuilders =
1341 m_nodesManager->shaderBuilderManager()->activeHandles();
1342 for (const HTechnique &techniqueHandle : activeTechniques) {
1343 Technique *technique = m_nodesManager->techniqueManager()->data(techniqueHandle);
1344 // If api of the renderer matches the one from the technique
1345 if (technique->isCompatibleWithRenderer()) {
1346 const auto passIds = technique->renderPasses();
1347 for (const QNodeId &passId : passIds) {
1348 RenderPass *renderPass =
1349 m_nodesManager->renderPassManager()->lookupResource(passId);
1350 HShader shaderHandle =
1351 m_nodesManager->shaderManager()->lookupHandle(renderPass->shaderProgram());
1352 Shader *shader = m_nodesManager->shaderManager()->data(shaderHandle);
1353
1354 ShaderBuilder *shaderBuilder = nullptr;
1355 for (const HShaderBuilder &builderHandle : activeBuilders) {
1356 ShaderBuilder *builder =
1357 m_nodesManager->shaderBuilderManager()->data(builderHandle);
1358 if (builder->shaderProgramId() == shader->peerId()) {
1359 shaderBuilder = builder;
1360 break;
1361 }
1362 }
1363
1364 if (shaderBuilder) {
1365 shaderBuilder->setGraphicsApi(*technique->graphicsApiFilter());
1366
1367 for (int i = 0; i <= QShaderProgram::Compute; i++) {
1368 const auto shaderType = static_cast<QShaderProgram::ShaderType>(i);
1369 if (!shaderBuilder->shaderGraph(shaderType).isValid())
1370 continue;
1371
1372 if (shaderBuilder->isShaderCodeDirty(shaderType)) {
1373 shaderBuilder->generateCode(shaderType);
1374 m_shaderBuilderUpdates.append(shaderBuilder->takePendingUpdates());
1375 }
1376
1377 const auto code = shaderBuilder->shaderCode(shaderType);
1378 shader->setShaderCode(shaderType, code);
1379 }
1380 }
1381
1382 if (shader != nullptr && shader->isDirty())
1383 loadShader(shader, shaderHandle);
1384 }
1385 }
1386 }
1387 }
1388
1389 // Executed in job (in main thread when jobs are done)
sendShaderChangesToFrontend(Qt3DCore::QAspectManager * manager)1390 void Renderer::sendShaderChangesToFrontend(Qt3DCore::QAspectManager *manager)
1391 {
1392 Q_ASSERT(isRunning());
1393
1394 // Sync Shader
1395 const std::vector<HShader> &activeShaders = m_nodesManager->shaderManager()->activeHandles();
1396 for (const HShader &handle : activeShaders) {
1397 Shader *s = m_nodesManager->shaderManager()->data(handle);
1398 if (s->requiresFrontendSync()) {
1399 QShaderProgram *frontend =
1400 static_cast<decltype(frontend)>(manager->lookupNode(s->peerId()));
1401 QShaderProgramPrivate *dFrontend =
1402 static_cast<decltype(dFrontend)>(QNodePrivate::get(frontend));
1403 s->unsetRequiresFrontendSync();
1404 dFrontend->setStatus(s->status());
1405 dFrontend->setLog(s->log());
1406 }
1407 }
1408
1409 // Sync ShaderBuilder
1410 const QVector<ShaderBuilderUpdate> shaderBuilderUpdates = std::move(m_shaderBuilderUpdates);
1411 for (const ShaderBuilderUpdate &update : shaderBuilderUpdates) {
1412 QShaderProgramBuilder *builder =
1413 static_cast<decltype(builder)>(manager->lookupNode(update.builderId));
1414 QShaderProgramBuilderPrivate *dBuilder =
1415 static_cast<decltype(dBuilder)>(QNodePrivate::get(builder));
1416 dBuilder->setShaderCode(update.shaderCode, update.shaderType);
1417 }
1418 }
1419
1420 // Executed in a job (in main thread when jobs are done)
sendTextureChangesToFrontend(Qt3DCore::QAspectManager * manager)1421 void Renderer::sendTextureChangesToFrontend(Qt3DCore::QAspectManager *manager)
1422 {
1423 const QVector<QPair<Texture::TextureUpdateInfo, Qt3DCore::QNodeIdVector>>
1424 updateTextureProperties = std::move(m_updatedTextureProperties);
1425 for (const auto &pair : updateTextureProperties) {
1426 const Qt3DCore::QNodeIdVector targetIds = pair.second;
1427 for (const Qt3DCore::QNodeId &targetId : targetIds) {
1428 // Lookup texture
1429 Texture *t = m_nodesManager->textureManager()->lookupResource(targetId);
1430 // If backend texture is Dirty, some property has changed and the properties we are
1431 // about to send are already outdate
1432 if (t == nullptr || t->dirtyFlags() != Texture::NotDirty)
1433 continue;
1434
1435 QAbstractTexture *texture =
1436 static_cast<QAbstractTexture *>(manager->lookupNode(targetId));
1437 if (!texture)
1438 continue;
1439 const TextureProperties &properties = pair.first.properties;
1440
1441 const bool blocked = texture->blockNotifications(true);
1442 texture->setWidth(properties.width);
1443 texture->setHeight(properties.height);
1444 texture->setDepth(properties.depth);
1445 texture->setLayers(properties.layers);
1446 texture->setFormat(properties.format);
1447 texture->blockNotifications(blocked);
1448
1449 QAbstractTexturePrivate *dTexture =
1450 static_cast<QAbstractTexturePrivate *>(QNodePrivate::get(texture));
1451
1452 dTexture->setStatus(properties.status);
1453 dTexture->setHandleType(pair.first.handleType);
1454 dTexture->setHandle(pair.first.handle);
1455 }
1456 }
1457 }
1458
1459 // Executed in a job (in main thread when jobs done)
sendDisablesToFrontend(Qt3DCore::QAspectManager * manager)1460 void Renderer::sendDisablesToFrontend(Qt3DCore::QAspectManager *manager)
1461 {
1462 // SubtreeEnabled
1463 const auto updatedDisables = std::move(m_updatedDisableSubtreeEnablers);
1464 for (const auto &nodeId : updatedDisables) {
1465 QSubtreeEnabler *frontend = static_cast<decltype(frontend)>(manager->lookupNode(nodeId));
1466 frontend->setEnabled(false);
1467 }
1468
1469 // Compute Commands
1470 const std::vector<HComputeCommand> &activeCommands =
1471 m_nodesManager->computeJobManager()->activeHandles();
1472 for (const HComputeCommand &handle : activeCommands) {
1473 ComputeCommand *c = m_nodesManager->computeJobManager()->data(handle);
1474 if (c->hasReachedFrameCount()) {
1475 QComputeCommand *frontend =
1476 static_cast<decltype(frontend)>(manager->lookupNode(c->peerId()));
1477 frontend->setEnabled(false);
1478 c->resetHasReachedFrameCount();
1479 }
1480 }
1481 }
1482
1483 // Render Thread (or QtQuick RenderThread when using Scene3D)
1484 // Scene3D: When using Scene3D rendering, we can't assume that when
1485 // updateGLResources is called, the resource handles points to still existing
1486 // objects. This is because Scene3D calls doRender independently of whether all
1487 // jobs have completed or not which in turn calls proceedToNextFrame under some
1488 // conditions. Such conditions are usually met on startup to avoid deadlocks.
1489 // proceedToNextFrame triggers the syncChanges calls for the next frame, which
1490 // may contain destruction changes targeting resources. When the above
1491 // happens, this can result in the dirtyResource vectors containing handles of
1492 // objects that may already have been destroyed
updateResources()1493 void Renderer::updateResources()
1494 {
1495 {
1496 const QVector<HBuffer> dirtyBufferHandles = std::move(m_dirtyBuffers);
1497 for (const HBuffer &handle : dirtyBufferHandles) {
1498 Buffer *buffer = m_nodesManager->bufferManager()->data(handle);
1499
1500 // Can be null when using Scene3D rendering
1501 if (buffer == nullptr)
1502 continue;
1503
1504 // Forces creation if it doesn't exit
1505 // Also note the binding point doesn't really matter here, we just upload data
1506 if (!m_submissionContext->hasRHIBufferForBuffer(buffer))
1507 m_submissionContext->rhiBufferForRenderBuffer(buffer);
1508 // Update the RHIBuffer data
1509 m_submissionContext->updateBuffer(buffer);
1510 buffer->unsetDirty();
1511 }
1512 }
1513
1514 #ifndef SHADER_LOADING_IN_COMMAND_THREAD
1515 {
1516 const QVector<HShader> dirtyShaderHandles = std::move(m_dirtyShaders);
1517 ShaderManager *shaderManager = m_nodesManager->shaderManager();
1518 for (const HShader &handle : dirtyShaderHandles) {
1519 Shader *shader = shaderManager->data(handle);
1520
1521 // Can be null when using Scene3D rendering
1522 if (shader == nullptr)
1523 continue;
1524
1525 // Compile shader
1526 m_submissionContext->loadShader(shader, shaderManager,
1527 m_RHIResourceManagers->rhiShaderManager());
1528 }
1529 }
1530 #endif
1531
1532 {
1533 const QVector<HTexture> activeTextureHandles = std::move(m_dirtyTextures);
1534 for (const HTexture &handle : activeTextureHandles) {
1535 Texture *texture = m_nodesManager->textureManager()->data(handle);
1536
1537 // Can be null when using Scene3D rendering
1538 if (texture == nullptr)
1539 continue;
1540
1541 // Create or Update RHITexture (the RHITexture instance is created if required
1542 // and all things that can take place without a GL context are done here)
1543 updateTexture(texture);
1544 }
1545 // We want to upload textures data at this point as the SubmissionThread and
1546 // AspectThread are locked ensuring no races between Texture/TextureImage and
1547 // RHITexture
1548 if (m_submissionContext != nullptr) {
1549 RHITextureManager *rhiTextureManager = m_RHIResourceManagers->rhiTextureManager();
1550 const std::vector<HRHITexture> &glTextureHandles = rhiTextureManager->activeHandles();
1551 // Upload texture data
1552 for (const HRHITexture &glTextureHandle : glTextureHandles) {
1553 RHI_UNIMPLEMENTED;
1554 RHITexture *glTexture = rhiTextureManager->data(glTextureHandle);
1555
1556 // We create/update the actual GL texture using the GL context at this point
1557 const RHITexture::TextureUpdateInfo info =
1558 glTexture->createOrUpdateRhiTexture(m_submissionContext.data());
1559
1560 // RHITexture creation provides us width/height/format ... information
1561 // for textures which had not initially specified these information
1562 // (TargetAutomatic...) Gather these information and store them to be distributed by
1563 // a change next frame
1564 const QNodeIdVector referenceTextureIds = {
1565 rhiTextureManager->texNodeIdForRHITexture.value(glTexture)
1566 };
1567 // Store properties and referenceTextureIds
1568 if (info.wasUpdated) {
1569 Texture::TextureUpdateInfo updateInfo;
1570 updateInfo.properties = info.properties;
1571 updateInfo.handleType = QAbstractTexture::OpenGLTextureId;
1572 // updateInfo.handle = info.texture ?
1573 // QVariant(info.texture->textureId()) : QVariant();
1574 m_updatedTextureProperties.push_back({ updateInfo, referenceTextureIds });
1575 }
1576 }
1577 }
1578
1579 // Record ids of texture to cleanup while we are still blocking the aspect thread
1580 m_textureIdsToCleanup += m_nodesManager->textureManager()->takeTexturesIdsToCleanup();
1581 }
1582
1583 // Record list of buffer that might need uploading
1584 lookForDownloadableBuffers();
1585 }
1586
1587 // Render Thread
updateTexture(Texture * texture)1588 void Renderer::updateTexture(Texture *texture)
1589 {
1590 RHI_UNIMPLEMENTED;
1591 // Check that the current texture images are still in place, if not, do not update
1592 const bool isValid = texture->isValid(m_nodesManager->textureImageManager());
1593 if (!isValid) {
1594 qWarning() << Q_FUNC_INFO << "QTexture referencing invalid QTextureImages";
1595 return;
1596 }
1597
1598 // All textures are unique, if you instanciate twice the exact same texture
1599 // this will create 2 identical GLTextures, no sharing will take place
1600
1601 // Try to find the associated RHITexture for the backend Texture
1602 RHITextureManager *rhiTextureManager = m_RHIResourceManagers->rhiTextureManager();
1603 RHITexture *rhiTexture = rhiTextureManager->lookupResource(texture->peerId());
1604
1605 // No RHITexture associated yet -> create it
1606 if (rhiTexture == nullptr) {
1607 rhiTexture = rhiTextureManager->getOrCreateResource(texture->peerId());
1608 rhiTextureManager->texNodeIdForRHITexture.insert(rhiTexture, texture->peerId());
1609 }
1610
1611 // Update RHITexture to match Texture instance
1612 const Texture::DirtyFlags dirtyFlags = texture->dirtyFlags();
1613 if (dirtyFlags.testFlag(Texture::DirtySharedTextureId))
1614 rhiTexture->setSharedTextureId(texture->sharedTextureId());
1615
1616 if (dirtyFlags.testFlag(Texture::DirtyProperties))
1617 rhiTexture->setProperties(texture->properties());
1618
1619 if (dirtyFlags.testFlag(Texture::DirtyParameters))
1620 rhiTexture->setParameters(texture->parameters());
1621
1622 // Will make the texture requestUpload
1623 if (dirtyFlags.testFlag(Texture::DirtyImageGenerators)) {
1624 const QNodeIdVector textureImageIds = texture->textureImageIds();
1625 QVector<RHITexture::Image> images;
1626 images.reserve(textureImageIds.size());
1627 // TODO: Move this into RHITexture directly
1628 for (const QNodeId textureImageId : textureImageIds) {
1629 const TextureImage *img =
1630 m_nodesManager->textureImageManager()->lookupResource(textureImageId);
1631 if (img == nullptr) {
1632 qWarning() << Q_FUNC_INFO << "invalid TextureImage handle";
1633 } else {
1634 RHITexture::Image glImg { img->dataGenerator(), img->layer(), img->mipLevel(),
1635 img->face() };
1636 images.push_back(glImg);
1637 }
1638 }
1639 rhiTexture->setImages(images);
1640 }
1641
1642 // Will make the texture requestUpload
1643 if (dirtyFlags.testFlag(Texture::DirtyDataGenerator))
1644 rhiTexture->setGenerator(texture->dataGenerator());
1645
1646 // Will make the texture requestUpload
1647 if (dirtyFlags.testFlag(Texture::DirtyPendingDataUpdates))
1648 rhiTexture->addTextureDataUpdates(texture->takePendingTextureDataUpdates());
1649
1650 // Unset the dirty flag on the texture
1651 texture->unsetDirty();
1652 }
1653
1654 // Render Thread
cleanupTexture(Qt3DCore::QNodeId cleanedUpTextureId)1655 void Renderer::cleanupTexture(Qt3DCore::QNodeId cleanedUpTextureId)
1656 {
1657 RHITextureManager *rhiTextureManager = m_RHIResourceManagers->rhiTextureManager();
1658 RHITexture *glTexture = rhiTextureManager->lookupResource(cleanedUpTextureId);
1659
1660 // Destroying the RHITexture implicitely also destroy the GL resources
1661 if (glTexture != nullptr) {
1662 rhiTextureManager->releaseResource(cleanedUpTextureId);
1663 rhiTextureManager->texNodeIdForRHITexture.remove(glTexture);
1664 }
1665 }
1666
1667 // Render Thread
cleanupShader(const Shader * shader)1668 void Renderer::cleanupShader(const Shader *shader)
1669 {
1670 RHIShaderManager *rhiShaderManager = m_RHIResourceManagers->rhiShaderManager();
1671 RHIShader *glShader = rhiShaderManager->lookupResource(shader->peerId());
1672
1673 if (glShader != nullptr)
1674 rhiShaderManager->abandon(glShader, shader);
1675 }
1676
1677 // Called by SubmitRenderView
downloadGLBuffers()1678 void Renderer::downloadGLBuffers()
1679 {
1680 const QVector<Qt3DCore::QNodeId> downloadableHandles = std::move(m_downloadableBuffers);
1681 for (const Qt3DCore::QNodeId &bufferId : downloadableHandles) {
1682 BufferManager *bufferManager = m_nodesManager->bufferManager();
1683 BufferManager::ReadLocker locker(const_cast<const BufferManager *>(bufferManager));
1684 Buffer *buffer = bufferManager->lookupResource(bufferId);
1685 // Buffer could have been destroyed at this point
1686 if (!buffer)
1687 continue;
1688 // locker is protecting us from the buffer being destroy while we're looking
1689 // up its content
1690 const QByteArray content = m_submissionContext->downloadBufferContent(buffer);
1691 m_sendBufferCaptureJob->addRequest(QPair<Qt3DCore::QNodeId, QByteArray>(bufferId, content));
1692 }
1693 }
1694
1695 // Happens in RenderThread context when all RenderViewJobs are done
1696 // Returns the id of the last bound FBO
1697 Renderer::ViewSubmissionResultData
submitRenderViews(const QVector<RHIPassInfo> & rhiPassesInfo)1698 Renderer::submitRenderViews(const QVector<RHIPassInfo> &rhiPassesInfo)
1699 {
1700 QElapsedTimer timer;
1701 quint64 queueElapsed = 0;
1702 timer.start();
1703
1704 quint64 frameElapsed = queueElapsed;
1705 m_lastFrameCorrect.storeRelaxed(1); // everything fine until now.....
1706
1707 qCDebug(Memory) << Q_FUNC_INFO << "rendering frame ";
1708
1709 // We might not want to render on the default FBO
1710 uint lastBoundFBOId = 0; // m_submissionContext->boundFrameBufferObject();
1711 QSurface *surface = nullptr;
1712 QSurface *previousSurface = nullptr;
1713 QSurface *lastUsedSurface = nullptr;
1714
1715 const int rhiPassesCount = rhiPassesInfo.size();
1716
1717 for (int i = 0; i < rhiPassesCount; ++i) {
1718 // Initialize GraphicsContext for drawing
1719 const RHIPassInfo &rhiPassInfo = rhiPassesInfo.at(i);
1720
1721 // Initialize Previous surface the first time we enter this loop
1722 if (i == 0) {
1723 for (const RenderView *rv : rhiPassInfo.rvs) {
1724 previousSurface = rv->surface();
1725 if (previousSurface)
1726 break;
1727 }
1728 }
1729
1730 // Check if using the same surface as the previous RHIPassInfo.
1731 // If not, we have to free up the context from the previous surface
1732 // and make the context current on the new surface
1733 surface = rhiPassInfo.surface;
1734 SurfaceLocker surfaceLock(surface);
1735
1736 // TO DO: Make sure that the surface we are rendering too has not been unset
1737
1738 // For now, if we do not have a surface, skip this rhipassinfo
1739 // TODO: Investigate if it's worth providing a fallback offscreen surface
1740 // to use when surface is null. Or if we should instead expose an
1741 // offscreensurface to Qt3D.
1742 if (!surface || !surfaceLock.isSurfaceValid()) {
1743 m_lastFrameCorrect.storeRelaxed(0);
1744 continue;
1745 }
1746
1747 lastUsedSurface = surface;
1748 const bool surfaceHasChanged = surface != previousSurface;
1749
1750 if (surfaceHasChanged && previousSurface) {
1751 const bool swapBuffers = lastBoundFBOId == m_submissionContext->defaultFBO()
1752 && surfaceLock.isSurfaceValid() && m_shouldSwapBuffers;
1753 // We only call swap buffer if we are sure the previous surface is still valid
1754 m_submissionContext->endDrawing(swapBuffers);
1755 }
1756
1757 if (surfaceHasChanged) {
1758 // If we can't make the context current on the surface, skip to the
1759 // next RenderView. We won't get the full frame but we may get something
1760 if (!m_submissionContext->beginDrawing(surface)) {
1761 qWarning() << "Failed to make OpenGL context current on surface";
1762 m_lastFrameCorrect.storeRelaxed(0);
1763 continue;
1764 }
1765
1766 previousSurface = surface;
1767 // lastBoundFBOId = m_submissionContext->boundFrameBufferObject();
1768 }
1769
1770 // Apply Memory Barrier if needed
1771 // if (renderView->memoryBarrier() != QMemoryBarrier::None)
1772 // qWarning() << "RHI Doesn't support MemoryBarrier";
1773
1774 // Set RenderTarget ...
1775 // Activate RenderTarget
1776 {
1777 m_submissionContext->activateRenderTarget(rhiPassInfo.renderTargetId,
1778 rhiPassInfo.attachmentPack, lastBoundFBOId);
1779 }
1780
1781 // Execute the render commands
1782 if (!executeCommandsSubmission(rhiPassInfo))
1783 m_lastFrameCorrect.storeRelaxed(
1784 0); // something went wrong; make sure to render the next frame!
1785
1786 // executeCommandsSubmission takes care of restoring the stateset to the value
1787 // of gc->currentContext() at the moment it was called (either
1788 // renderViewStateSet or m_defaultRenderStateSet)
1789 // if (!renderView->renderCaptureNodeId().isNull()) {
1790 // RHI_UNIMPLEMENTED;
1791 //* const QRenderCaptureRequest request = renderView->renderCaptureRequest();
1792 //* const QSize size =
1793 //m_submissionContext->renderTargetSize(renderView->surfaceSize());
1794 //* QRect rect(QPoint(0, 0), size);
1795 //* if (!request.rect.isEmpty())
1796 //* rect = rect.intersected(request.rect);
1797 //* QImage image;
1798 //* if (!rect.isEmpty()) {
1799 //* // Bind fbo as read framebuffer
1800 //* m_submissionContext->bindFramebuffer(m_submissionContext->activeFBO(),
1801 //GraphicsHelperInterface::FBORead);
1802 //* image = m_submissionContext->readFramebuffer(rect);
1803 //* } else {
1804 //* qWarning() << "Requested capture rectangle is outside framebuffer";
1805 //* }
1806 //* Render::RenderCapture *renderCapture =
1807 //*
1808 //static_cast<Render::RenderCapture*>(m_nodesManager->frameGraphManager()->lookupNode(renderView->renderCaptureNodeId()));
1809 //* renderCapture->addRenderCapture(request.captureId, image);
1810 //* if
1811 //(!m_pendingRenderCaptureSendRequests.contains(renderView->renderCaptureNodeId()))
1812 //* m_pendingRenderCaptureSendRequests.push_back(renderView->renderCaptureNodeId());
1813 // }
1814
1815 // if (renderView->isDownloadBuffersEnable())
1816 // {
1817 // RHI_UNIMPLEMENTED;
1818 ////* downloadGLBuffers();
1819 // }
1820
1821 // // Perform BlitFramebuffer operations
1822 // if (renderView->hasBlitFramebufferInfo()) {
1823 // RHI_UNIMPLEMENTED;
1824 ////* const auto &blitFramebufferInfo = renderView->blitFrameBufferInfo();
1825 ////* const QNodeId inputTargetId = blitFramebufferInfo.sourceRenderTargetId;
1826 ////* const QNodeId outputTargetId =
1827 ///blitFramebufferInfo.destinationRenderTargetId;
1828 ////* const QRect inputRect = blitFramebufferInfo.sourceRect;
1829 ////* const QRect outputRect = blitFramebufferInfo.destinationRect;
1830 ////* const QRenderTargetOutput::AttachmentPoint inputAttachmentPoint =
1831 ///blitFramebufferInfo.sourceAttachmentPoint;
1832 ////* const QRenderTargetOutput::AttachmentPoint outputAttachmentPoint =
1833 ///blitFramebufferInfo.destinationAttachmentPoint;
1834 ////* const QBlitFramebuffer::InterpolationMethod interpolationMethod =
1835 ///blitFramebufferInfo.interpolationMethod;
1836 ////* m_submissionContext->blitFramebuffer(inputTargetId, outputTargetId,
1837 ///inputRect, outputRect, lastBoundFBOId,
1838 ////* inputAttachmentPoint,
1839 ///outputAttachmentPoint,
1840 ////* interpolationMethod);
1841 // }
1842
1843 frameElapsed = timer.elapsed() - frameElapsed;
1844 qCDebug(Rendering) << Q_FUNC_INFO << "Submitted RHI Passes " << i + 1 << "/"
1845 << rhiPassesCount << "in " << frameElapsed << "ms";
1846 frameElapsed = timer.elapsed();
1847 }
1848
1849 // Bind lastBoundFBOId back. Needed also in threaded mode.
1850 // lastBoundFBOId != m_graphicsContext->activeFBO() when the last FrameGraph leaf
1851 // node/renderView contains RenderTargetSelector/RenderTarget
1852 if (lastBoundFBOId != m_submissionContext->activeFBO()) {
1853 RHI_UNIMPLEMENTED;
1854 // m_submissionContext->bindFramebuffer(lastBoundFBOId,
1855 // GraphicsHelperInterface::FBOReadAndDraw);
1856 }
1857
1858 // Reset state and call doneCurrent if the surface
1859 // is valid and was actually activated
1860 if (lastUsedSurface) {
1861 RHI_UNIMPLEMENTED;
1862 // Reset state to the default state if the last stateset is not the
1863 // defaultRenderStateSet
1864 // if (m_submissionContext->currentStateSet() != m_defaultRenderStateSet)
1865 // m_submissionContext->setCurrentStateSet(m_defaultRenderStateSet);
1866 }
1867
1868 queueElapsed = timer.elapsed() - queueElapsed;
1869 qCDebug(Rendering) << Q_FUNC_INFO << "Submission Completed in " << timer.elapsed() << "ms";
1870
1871 // Stores the necessary information to safely perform
1872 // the last swap buffer call
1873 ViewSubmissionResultData resultData;
1874 resultData.lastBoundFBOId = lastBoundFBOId;
1875 resultData.surface = lastUsedSurface;
1876 return resultData;
1877 }
1878
markDirty(BackendNodeDirtySet changes,BackendNode * node)1879 void Renderer::markDirty(BackendNodeDirtySet changes, BackendNode *node)
1880 {
1881 Q_UNUSED(node)
1882 m_dirtyBits.marked |= changes;
1883 }
1884
dirtyBits()1885 Renderer::BackendNodeDirtySet Renderer::dirtyBits()
1886 {
1887 return m_dirtyBits.marked;
1888 }
1889
1890 #if defined(QT_BUILD_INTERNAL)
clearDirtyBits(BackendNodeDirtySet changes)1891 void Renderer::clearDirtyBits(BackendNodeDirtySet changes)
1892 {
1893 m_dirtyBits.remaining &= ~changes;
1894 m_dirtyBits.marked &= ~changes;
1895 }
1896 #endif
1897
shouldRender() const1898 bool Renderer::shouldRender() const
1899 {
1900 // Only render if something changed during the last frame, or the last frame
1901 // was not rendered successfully (or render-on-demand is disabled)
1902 return (m_settings->renderPolicy() == QRenderSettings::Always || m_dirtyBits.marked != 0
1903 || m_dirtyBits.remaining != 0 || !m_lastFrameCorrect.loadRelaxed());
1904 }
1905
skipNextFrame()1906 void Renderer::skipNextFrame()
1907 {
1908 Q_ASSERT(m_settings->renderPolicy() != QRenderSettings::Always);
1909
1910 // make submitRenderViews() actually run
1911 m_renderQueue->setNoRender();
1912 m_submitRenderViewsSemaphore.release(1);
1913 }
1914
jobsDone(Qt3DCore::QAspectManager * manager)1915 void Renderer::jobsDone(Qt3DCore::QAspectManager *manager)
1916 {
1917 // called in main thread once all jobs are done running
1918
1919 // sync captured renders to frontend
1920 const QVector<Qt3DCore::QNodeId> pendingCaptureIds =
1921 std::move(m_pendingRenderCaptureSendRequests);
1922 for (const Qt3DCore::QNodeId &id : qAsConst(pendingCaptureIds)) {
1923 auto *backend = static_cast<Qt3DRender::Render::RenderCapture *>(
1924 m_nodesManager->frameGraphManager()->lookupNode(id));
1925 backend->syncRenderCapturesToFrontend(manager);
1926 }
1927
1928 // Do we need to notify any texture about property changes?
1929 if (m_updatedTextureProperties.size() > 0)
1930 sendTextureChangesToFrontend(manager);
1931
1932 sendDisablesToFrontend(manager);
1933 }
1934
setPendingEvents(const QList<QPair<QObject *,QMouseEvent>> & mouseEvents,const QList<QKeyEvent> & keyEvents)1935 void Renderer::setPendingEvents(const QList<QPair<QObject *, QMouseEvent>> &mouseEvents,
1936 const QList<QKeyEvent> &keyEvents)
1937 {
1938 QMutexLocker l(&m_frameEventsMutex);
1939 m_frameMouseEvents = mouseEvents;
1940 m_frameKeyEvents = keyEvents;
1941 }
1942
1943 // Jobs we may have to run even if no rendering will happen
preRenderingJobs()1944 QVector<QAspectJobPtr> Renderer::preRenderingJobs()
1945 {
1946 if (m_sendBufferCaptureJob->hasRequests())
1947 return { m_sendBufferCaptureJob };
1948 else
1949 return {};
1950 }
1951
1952 // Waits to be told to create jobs for the next frame
1953 // Called by QRenderAspect jobsToExecute context of QAspectThread
1954 // Returns all the jobs (and with proper dependency chain) required
1955 // for the rendering of the scene
renderBinJobs()1956 QVector<Qt3DCore::QAspectJobPtr> Renderer::renderBinJobs()
1957 {
1958 QVector<QAspectJobPtr> renderBinJobs;
1959
1960 // Remove previous dependencies
1961 m_cleanupJob->removeDependency(QWeakPointer<QAspectJob>());
1962
1963 const BackendNodeDirtySet dirtyBitsForFrame = m_dirtyBits.marked | m_dirtyBits.remaining;
1964 m_dirtyBits.marked = {};
1965 m_dirtyBits.remaining = {};
1966 BackendNodeDirtySet notCleared = {};
1967
1968 // Add jobs
1969 if (dirtyBitsForFrame & AbstractRenderer::TransformDirty) {
1970 renderBinJobs.push_back(m_updateShaderDataTransformJob);
1971 }
1972
1973 // TO DO: Conditionally add if skeletons dirty
1974 renderBinJobs.push_back(m_cleanupJob);
1975
1976 // Jobs to prepare RHI Resource upload
1977 if (dirtyBitsForFrame & AbstractRenderer::BuffersDirty)
1978 renderBinJobs.push_back(m_bufferGathererJob);
1979
1980 if (dirtyBitsForFrame & AbstractRenderer::TexturesDirty)
1981 renderBinJobs.push_back(m_textureGathererJob);
1982
1983 // Layer cache is dependent on layers, layer filters (hence FG structure
1984 // changes) and the enabled flag on entities
1985 const bool entitiesEnabledDirty = dirtyBitsForFrame & AbstractRenderer::EntityEnabledDirty;
1986 const bool frameGraphDirty = dirtyBitsForFrame & AbstractRenderer::FrameGraphDirty;
1987 const bool layersDirty = dirtyBitsForFrame & AbstractRenderer::LayersDirty;
1988 const bool layersCacheNeedsToBeRebuilt = layersDirty || entitiesEnabledDirty || frameGraphDirty;
1989 const bool shadersDirty = dirtyBitsForFrame & AbstractRenderer::ShadersDirty;
1990 const bool materialDirty = dirtyBitsForFrame & AbstractRenderer::MaterialDirty;
1991 const bool lightsDirty = dirtyBitsForFrame & AbstractRenderer::LightsDirty;
1992 const bool computeableDirty = dirtyBitsForFrame & AbstractRenderer::ComputeDirty;
1993 const bool renderableDirty = dirtyBitsForFrame & AbstractRenderer::GeometryDirty;
1994 const bool materialCacheNeedsToBeRebuilt = shadersDirty || materialDirty || frameGraphDirty;
1995 const bool renderCommandsDirty =
1996 materialCacheNeedsToBeRebuilt || renderableDirty || computeableDirty;
1997
1998 // Rebuild Entity Layers list if layers are dirty
1999
2000 if (renderableDirty)
2001 renderBinJobs.push_back(m_renderableEntityFilterJob);
2002
2003 if (computeableDirty)
2004 renderBinJobs.push_back(m_computableEntityFilterJob);
2005
2006 if (lightsDirty)
2007 renderBinJobs.push_back(m_lightGathererJob);
2008
2009 QMutexLocker lock(m_renderQueue->mutex());
2010 if (m_renderQueue->wasReset()) { // Have we rendered yet? (Scene3D case)
2011 // Traverse the current framegraph. For each leaf node create a
2012 // RenderView and set its configuration then create a job to
2013 // populate the RenderView with a set of RenderCommands that get
2014 // their details from the RenderNodes that are visible to the
2015 // Camera selected by the framegraph configuration
2016 if (frameGraphDirty) {
2017 FrameGraphVisitor visitor(m_nodesManager->frameGraphManager());
2018 m_frameGraphLeaves = visitor.traverse(frameGraphRoot());
2019 // Remove leaf nodes that no longer exist from cache
2020 const QList<FrameGraphNode *> keys = m_cache.leafNodeCache.keys();
2021 for (FrameGraphNode *leafNode : keys) {
2022 if (!m_frameGraphLeaves.contains(leafNode))
2023 m_cache.leafNodeCache.remove(leafNode);
2024 }
2025
2026 // Handle single shot subtree enablers
2027 const auto subtreeEnablers = visitor.takeEnablersToDisable();
2028 for (auto *node : subtreeEnablers)
2029 m_updatedDisableSubtreeEnablers.push_back(node->peerId());
2030 }
2031
2032 const int fgBranchCount = m_frameGraphLeaves.size();
2033 for (int i = 0; i < fgBranchCount; ++i) {
2034 FrameGraphNode *leaf = m_frameGraphLeaves.at(i);
2035 RenderViewBuilder builder(leaf, i, this);
2036 // If we have a new RV (wasn't in the cache before, then it contains no cached data)
2037 const bool isNewRV = !m_cache.leafNodeCache.contains(leaf);
2038 builder.setLayerCacheNeedsToBeRebuilt(layersCacheNeedsToBeRebuilt || isNewRV);
2039 builder.setMaterialGathererCacheNeedsToBeRebuilt(materialCacheNeedsToBeRebuilt
2040 || isNewRV);
2041 builder.setRenderCommandCacheNeedsToBeRebuilt(renderCommandsDirty || isNewRV);
2042
2043 builder.prepareJobs();
2044 renderBinJobs.append(builder.buildJobHierachy());
2045 }
2046
2047 // Set target number of RenderViews
2048 m_renderQueue->setTargetRenderViewCount(fgBranchCount);
2049 } else {
2050 // FilterLayerEntityJob is part of the RenderViewBuilder jobs and must be run later
2051 // if none of those jobs are started this frame
2052 notCleared |= AbstractRenderer::EntityEnabledDirty;
2053 notCleared |= AbstractRenderer::FrameGraphDirty;
2054 notCleared |= AbstractRenderer::LayersDirty;
2055 }
2056
2057 if (isRunning() && m_submissionContext->isInitialized()) {
2058 if (dirtyBitsForFrame & AbstractRenderer::TechniquesDirty)
2059 renderBinJobs.push_back(m_filterCompatibleTechniqueJob);
2060 if (dirtyBitsForFrame & AbstractRenderer::ShadersDirty)
2061 renderBinJobs.push_back(m_introspectShaderJob);
2062 } else {
2063 notCleared |= AbstractRenderer::TechniquesDirty;
2064 notCleared |= AbstractRenderer::ShadersDirty;
2065 }
2066
2067 m_dirtyBits.remaining = dirtyBitsForFrame & notCleared;
2068
2069 return renderBinJobs;
2070 }
2071
frameAdvanceService() const2072 QAbstractFrameAdvanceService *Renderer::frameAdvanceService() const
2073 {
2074 return static_cast<Qt3DCore::QAbstractFrameAdvanceService *>(m_vsyncFrameAdvanceService.data());
2075 }
2076
2077 // Called by executeCommands
performDraw(RenderCommand * command)2078 void Renderer::performDraw(RenderCommand *command)
2079 {
2080 QRhiCommandBuffer *cb = m_submissionContext->currentFrameCommandBuffer();
2081 // Indirect Draw Calls
2082 if (command->m_drawIndirect) {
2083 RHI_UNIMPLEMENTED;
2084 } else { // Direct Draw Calls
2085
2086 // TO DO: Add glMulti Draw variants
2087 if (command->m_primitiveType == QGeometryRenderer::Patches) {
2088 RHI_UNIMPLEMENTED;
2089 //* m_submissionContext->setVerticesPerPatch(command->m_verticesPerPatch);
2090 }
2091
2092 if (command->m_primitiveRestartEnabled) {
2093 RHI_UNIMPLEMENTED;
2094 //* m_submissionContext->enablePrimitiveRestart(command->m_restartIndexValue);
2095 }
2096
2097 // TO DO: Add glMulti Draw variants
2098 if (command->m_drawIndexed) {
2099 cb->drawIndexed(command->m_primitiveCount, command->m_instanceCount,
2100 command->m_indexOffset, command->m_indexAttributeByteOffset,
2101 command->m_firstInstance);
2102 } else {
2103 cb->draw(command->m_primitiveCount, command->m_instanceCount, command->m_firstVertex,
2104 command->m_firstInstance);
2105 }
2106 }
2107
2108 #if defined(QT3D_RENDER_ASPECT_RHI_DEBUG)
2109 int err = m_submissionContext->openGLContext()->functions()->glGetError();
2110 if (err)
2111 qCWarning(Rendering) << "GL error after drawing mesh:" << QString::number(err, 16);
2112 #endif
2113
2114 // if (command->m_primitiveRestartEnabled)
2115 // m_submissionContext->disablePrimitiveRestart();
2116 }
2117
performCompute(const RenderView *,RenderCommand * command)2118 void Renderer::performCompute(const RenderView *, RenderCommand *command)
2119 {
2120 RHI_UNIMPLEMENTED;
2121 //* {
2122 //* RHIShader *shader =
2123 //m_RHIResourceManagers->rhiShaderManager()->lookupResource(command->m_shaderId);
2124 //* m_submissionContext->activateShader(shader);
2125 //* }
2126 //* {
2127 //* m_submissionContext->setParameters(command->m_parameterPack);
2128 //* }
2129 //* {
2130 //* m_submissionContext->dispatchCompute(command->m_workGroups[0],
2131 //* command->m_workGroups[1],
2132 //* command->m_workGroups[2]);
2133 //* }
2134 //* // HACK: Reset the compute flag to dirty
2135 //* m_dirtyBits.marked |= AbstractRenderer::ComputeDirty;
2136
2137 //* #if defined(QT3D_RENDER_ASPECT_RHI_DEBUG)
2138 //* int err = m_submissionContext->openGLContext()->functions()->glGetError();
2139 //* if (err)
2140 //* qCWarning(Rendering) << "GL error after drawing mesh:" << QString::number(err, 16);
2141 //* #endif
2142 }
2143
rhiIndexFormat(QAttribute::VertexBaseType type)2144 static auto rhiIndexFormat(QAttribute::VertexBaseType type)
2145 {
2146 switch (type) {
2147 case QAttribute::VertexBaseType ::UnsignedShort:
2148 return QRhiCommandBuffer::IndexUInt16;
2149 case QAttribute::VertexBaseType ::UnsignedInt:
2150 return QRhiCommandBuffer::IndexUInt32;
2151 default:
2152 std::abort();
2153 }
2154 }
2155
uploadBuffersForCommand(QRhiCommandBuffer * cb,const RenderView * rv,RenderCommand & command)2156 bool Renderer::uploadBuffersForCommand(QRhiCommandBuffer *cb, const RenderView *rv,
2157 RenderCommand &command)
2158 {
2159 RHIGraphicsPipeline *graphicsPipeline = command.pipeline;
2160 if (!graphicsPipeline)
2161 return true;
2162
2163 // Create the vertex input description
2164
2165 // Note: we have to bind the buffers here -> which will trigger the actual
2166 // upload, as this is the only place where we know about the usage type of the buffers
2167
2168 const auto geom = command.m_geometry;
2169 const auto &attributes = geom->attributes();
2170 const QRhiVertexInputLayout layout = graphicsPipeline->pipeline()->vertexInputLayout();
2171 const int bindingAttributeCount = std::distance(layout.cbeginBindings(), layout.cendBindings());
2172 command.vertex_input.resize(bindingAttributeCount);
2173
2174 for (Qt3DCore::QNodeId attribute_id : attributes) {
2175 // TODO isn't there a more efficient way than doing three hash lookups ?
2176 Attribute *attrib = m_nodesManager->attributeManager()->lookupResource(attribute_id);
2177 Buffer *buffer = m_nodesManager->bufferManager()->lookupResource(attrib->bufferId());
2178 RHIBuffer *hbuf =
2179 m_RHIResourceManagers->rhiBufferManager()->lookupResource(buffer->peerId());
2180 switch (attrib->attributeType()) {
2181 case QAttribute::VertexAttribute: {
2182 hbuf->bind(&*m_submissionContext, RHIBuffer::Type::ArrayBuffer);
2183 assert(hbuf->rhiBuffer());
2184 // Find Binding for Attribute
2185 const int bindingIndex = graphicsPipeline->bindingIndexForAttribute(attrib->nameId());
2186 // We need to reference a binding, a buffer and an offset which is always = 0
2187 // as Qt3D only assumes interleaved or continuous data but not
2188 // buffer where first half of it is attribute1 and second half attribute2
2189 command.vertex_input[bindingIndex] = { hbuf->rhiBuffer(), 0 };
2190 break;
2191 }
2192 case QAttribute::IndexAttribute: {
2193 hbuf->bind(&*m_submissionContext, RHIBuffer::Type::IndexBuffer);
2194 assert(hbuf->rhiBuffer());
2195 assert(command.indexBuffer == nullptr);
2196
2197 command.indexBuffer = hbuf->rhiBuffer();
2198 command.indexAttribute = attrib;
2199 break;
2200 }
2201 case QAttribute::DrawIndirectAttribute:
2202 RHI_UNIMPLEMENTED;
2203 break;
2204 }
2205 }
2206
2207 for (const BlockToUBO &pack : command.m_parameterPack.uniformBuffers()) {
2208 Buffer *cpuBuffer = nodeManagers()->bufferManager()->lookupResource(pack.m_bufferID);
2209 RHIBuffer *ubo = m_submissionContext->rhiBufferForRenderBuffer(cpuBuffer);
2210 ubo->bind(&*m_submissionContext, RHIBuffer::UniformBuffer);
2211 }
2212
2213 return true;
2214 }
2215
2216 namespace {
printUpload(const UniformValue & value,const QShaderDescription::BlockVariable & member)2217 void printUpload(const UniformValue &value, const QShaderDescription::BlockVariable &member)
2218 {
2219 switch (member.type) {
2220 case QShaderDescription::VariableType::Int:
2221 qDebug() << "Updating" << member.name << "with int data: " << *value.constData<int>()
2222 << " (offset: " << member.offset << ", size: " << member.size << ")";
2223 break;
2224 case QShaderDescription::VariableType::Float:
2225 qDebug() << "Updating" << member.name << "with float data: " << *value.constData<float>()
2226 << " (offset: " << member.offset << ", size: " << member.size << ")";
2227 break;
2228 case QShaderDescription::VariableType::Vec2:
2229 qDebug() << "Updating" << member.name << "with vec2 data: " << value.constData<float>()[0]
2230 << ", " << value.constData<float>()[1] << " (offset: " << member.offset
2231 << ", size: " << member.size << ")";
2232 ;
2233 break;
2234 case QShaderDescription::VariableType::Vec3:
2235 qDebug() << "Updating" << member.name << "with vec3 data: " << value.constData<float>()[0]
2236 << ", " << value.constData<float>()[1] << ", " << value.constData<float>()[2]
2237 << " (offset: " << member.offset << ", size: " << member.size << ")";
2238 ;
2239 break;
2240 case QShaderDescription::VariableType::Vec4:
2241 qDebug() << "Updating" << member.name << "with vec4 data: " << value.constData<float>()[0]
2242 << ", " << value.constData<float>()[1] << ", " << value.constData<float>()[2]
2243 << ", " << value.constData<float>()[3] << " (offset: " << member.offset
2244 << ", size: " << member.size << ")";
2245 ;
2246 break;
2247 default:
2248 qDebug() << "Updating" << member.name << "with data: " << value.constData<char>();
2249 break;
2250 }
2251 }
2252
uploadUniform(SubmissionContext & submissionContext,const PackUniformHash & uniforms,const RHIShader::UBO_Member & uboMember,const QHash<int,RHIGraphicsPipeline::UBOBuffer> & uboBuffers,const QString & uniformName,const QShaderDescription::BlockVariable & member,int arrayOffset=0)2253 void uploadUniform(SubmissionContext &submissionContext, const PackUniformHash &uniforms,
2254 const RHIShader::UBO_Member &uboMember,
2255 const QHash<int, RHIGraphicsPipeline::UBOBuffer> &uboBuffers,
2256 const QString &uniformName, const QShaderDescription::BlockVariable &member,
2257 int arrayOffset = 0)
2258 {
2259 const int uniformNameId = StringToInt::lookupId(uniformName);
2260
2261 if (!uniforms.contains(uniformNameId))
2262 return;
2263
2264 const UniformValue value = uniforms.value(uniformNameId);
2265 const ShaderUniformBlock block = uboMember.block;
2266
2267 // Update UBO with uniform value
2268 Q_ASSERT(uboBuffers.contains(block.m_binding));
2269 const RHIGraphicsPipeline::UBOBuffer &ubo = uboBuffers[block.m_binding];
2270 RHIBuffer *buffer = ubo.buffer;
2271
2272 // TODO we should maybe have this thread_local to not reallocate memory every time
2273 QByteArray rawData;
2274 rawData.resize(member.size);
2275 memcpy(rawData.data(), value.constData<char>(), std::min(value.byteSize(), member.size));
2276 buffer->update(&submissionContext, rawData, member.offset + arrayOffset);
2277
2278 // printUpload(value, member);
2279 }
2280 }
2281
uploadUBOsForCommand(QRhiCommandBuffer * cb,const RenderView * rv,const RenderCommand & command)2282 bool Renderer::uploadUBOsForCommand(QRhiCommandBuffer *cb, const RenderView *rv,
2283 const RenderCommand &command)
2284 {
2285 RHIGraphicsPipeline *pipeline = command.pipeline;
2286 if (!pipeline)
2287 return true;
2288
2289 // Upload UBO data for the Command
2290 QRhiBuffer *commandUBO = pipeline->commandUBO();
2291 m_submissionContext->m_currentUpdates->updateDynamicBuffer(commandUBO, 0, sizeof(CommandUBO),
2292 &command.m_commandUBO);
2293
2294 // We have to update the RV UBO once per graphics pipeline
2295 QRhiBuffer *rvUBO = pipeline->renderViewUBO();
2296 m_submissionContext->m_currentUpdates->updateDynamicBuffer(rvUBO, 0, sizeof(RenderViewUBO),
2297 rv->renderViewUBO());
2298
2299 // Upload UBO for custom parameters
2300 {
2301 RHIShader *shader =
2302 m_RHIResourceManagers->rhiShaderManager()->lookupResource(command.m_shaderId);
2303 if (!shader)
2304 return true;
2305
2306 const QVector<RHIShader::UBO_Member> &uboMembers = shader->uboMembers();
2307 const QHash<int, RHIGraphicsPipeline::UBOBuffer> &uboBuffers = pipeline->ubos();
2308 const ShaderParameterPack ¶meterPack = command.m_parameterPack;
2309 const PackUniformHash &uniforms = parameterPack.uniforms();
2310
2311 // Update Buffer CPU side data based on uniforms being set
2312 for (const RHIShader::UBO_Member &uboMember : uboMembers) {
2313 for (const QShaderDescription::BlockVariable &member : qAsConst(uboMember.members)) {
2314
2315 if (!member.arrayDims.empty()) {
2316 if (!member.structMembers.empty()) {
2317 const int arr0 = member.arrayDims[0];
2318 for (int i = 0; i < arr0; i++) {
2319 for (const QShaderDescription::BlockVariable &structMember :
2320 member.structMembers) {
2321 const QString processedName = member.name + "[" + QString::number(i)
2322 + "]." + structMember.name;
2323 uploadUniform(*m_submissionContext, uniforms, uboMember, uboBuffers,
2324 processedName, structMember, i * member.size / arr0);
2325 }
2326 }
2327 } else {
2328 uploadUniform(*m_submissionContext, uniforms, uboMember, uboBuffers,
2329 member.name, member);
2330 }
2331 } else {
2332 uploadUniform(*m_submissionContext, uniforms, uboMember, uboBuffers,
2333 member.name, member);
2334 }
2335 }
2336 }
2337 // Upload changes to GPU Buffer
2338 for (const RHIGraphicsPipeline::UBOBuffer &ubo : uboBuffers) {
2339 // Binding triggers the upload
2340 ubo.buffer->bind(m_submissionContext.data(), RHIBuffer::UniformBuffer);
2341 }
2342 }
2343 return true;
2344 }
2345
performDraw(QRhiCommandBuffer * cb,const QRhiViewport & vp,const QRhiScissor * scissor,const RenderCommand & command)2346 bool Renderer::performDraw(QRhiCommandBuffer *cb, const QRhiViewport &vp,
2347 const QRhiScissor *scissor, const RenderCommand &command)
2348 {
2349 RHIGraphicsPipeline *pipeline = command.pipeline;
2350 if (!pipeline)
2351 return true;
2352
2353 // Setup the rendering pass
2354 cb->setGraphicsPipeline(pipeline->pipeline());
2355 cb->setViewport(vp);
2356 if (scissor)
2357 cb->setScissor(*scissor);
2358 cb->setShaderResources(pipeline->pipeline()->shaderResourceBindings());
2359
2360 // Send the draw command
2361 if (Q_UNLIKELY(!command.indexBuffer)) {
2362 cb->setVertexInput(0, command.vertex_input.size(), command.vertex_input.data());
2363 cb->draw(command.m_primitiveCount, command.m_instanceCount, command.m_firstVertex,
2364 command.m_firstInstance);
2365 } else {
2366 auto indexFormat = rhiIndexFormat(command.indexAttribute->vertexBaseType());
2367 auto indexOffset = command.indexAttribute->byteOffset();
2368 cb->setVertexInput(0, command.vertex_input.size(), command.vertex_input.data(),
2369 command.indexBuffer, indexOffset, indexFormat);
2370 cb->drawIndexed(command.m_primitiveCount, command.m_instanceCount, command.m_indexOffset,
2371 command.m_indexAttributeByteOffset, command.m_firstInstance);
2372 }
2373 return true;
2374 }
2375
2376 // Called by RenderView->submit() in RenderThread context
2377 // Returns true, if all RenderCommands were sent to the GPU
executeCommandsSubmission(const RHIPassInfo & passInfo)2378 bool Renderer::executeCommandsSubmission(const RHIPassInfo &passInfo)
2379 {
2380 bool allCommandsIssued = true;
2381
2382 const QVector<RenderView *> &renderViews = passInfo.rvs;
2383 QColor clearColor;
2384 QRhiDepthStencilClearValue clearDepthStencil;
2385
2386 // Submit the commands to the underlying graphics API (RHI)
2387 QRhiCommandBuffer *cb = m_submissionContext->currentFrameCommandBuffer();
2388
2389 // Upload data for all RenderCommands
2390 for (RenderView *rv : renderViews) {
2391 // Render drawing commands
2392
2393 QVector<RenderCommand> &commands = rv->commands();
2394
2395 // Upload all the required data to rhi...
2396 for (RenderCommand &command : commands) {
2397 if (command.m_type == RenderCommand::Draw) {
2398 uploadBuffersForCommand(cb, rv, command);
2399 uploadUBOsForCommand(cb, rv, command);
2400 }
2401 }
2402
2403 // Record clear information
2404 if (rv->clearTypes() != QClearBuffers::None) {
2405 clearColor = [=] {
2406 auto col = rv->globalClearColorBufferInfo().clearColor;
2407 return QColor::fromRgbF(col.x(), col.y(), col.z(), col.w());
2408 }();
2409 clearDepthStencil = { rv->clearDepthValue(), (quint32)rv->clearStencilValue() };
2410 }
2411 }
2412 // TO DO: should be moved elsewhere
2413 // Perform compute actions
2414 // cb->beginComputePass(m_submissionContext->m_currentUpdates);
2415 // for (RenderCommand &command : commands) {
2416 // if (command.m_type == RenderCommand::Compute) {
2417 // performCompute(rv, &command);
2418 // }
2419 // }
2420 // cb->endComputePass();
2421 // m_submissionContext->m_currentUpdates =
2422 // m_submissionContext->rhi()->nextResourceUpdateBatch();
2423
2424 // Draw the commands
2425
2426 // TO DO: Retrieve real renderTarget for RHIPassInfo
2427 QRhiRenderTarget *renderTarget = m_submissionContext->currentFrameRenderTarget();
2428
2429 // Begin pass
2430 cb->beginPass(renderTarget, clearColor, clearDepthStencil,
2431 m_submissionContext->m_currentUpdates);
2432
2433 // Per Pass Global States
2434 for (RenderView *rv : renderViews) {
2435 // Viewport
2436 QRhiViewport vp;
2437 QRhiScissor scissor;
2438 bool hasScissor = false;
2439 {
2440 const float x = rv->viewport().x() * rv->surfaceSize().width();
2441 const float y = (1. - rv->viewport().y() - rv->viewport().height())
2442 * rv->surfaceSize().height();
2443 const float w = rv->viewport().width() * rv->surfaceSize().width();
2444 const float h = rv->viewport().height() * rv->surfaceSize().height();
2445 // qDebug() << x << y << w << h;
2446 vp = { x, y, w, h };
2447 }
2448 // Scissoring
2449 {
2450 RenderStateSet *ss = rv->stateSet();
2451 if (ss == nullptr)
2452 ss = m_defaultRenderStateSet;
2453 StateVariant *scissorTestSVariant =
2454 m_submissionContext->getState(ss, StateMask::ScissorStateMask);
2455 if (scissorTestSVariant) {
2456 const ScissorTest *scissorTest =
2457 static_cast<const ScissorTest *>(scissorTestSVariant->constState());
2458 const auto &scissorValues = scissorTest->values();
2459 scissor = { std::get<0>(scissorValues), std::get<1>(scissorValues),
2460 std::get<2>(scissorValues), std::get<3>(scissorValues) };
2461 hasScissor = true;
2462 }
2463 }
2464
2465 // Render drawing commands
2466 const QVector<RenderCommand> &commands = rv->commands();
2467
2468 for (const RenderCommand &command : commands) {
2469 if (command.m_type == RenderCommand::Draw) {
2470 performDraw(cb, vp, hasScissor ? &scissor : nullptr, command);
2471 }
2472 }
2473 }
2474
2475 cb->endPass();
2476 m_submissionContext->m_currentUpdates = m_submissionContext->rhi()->nextResourceUpdateBatch();
2477
2478 return allCommandsIssued;
2479 }
2480
2481 // Erase graphics related resources that may become unused after a frame
cleanGraphicsResources()2482 void Renderer::cleanGraphicsResources()
2483 {
2484 // Remove unused GraphicsPipeline
2485 RHIGraphicsPipelineManager *pipelineManager =
2486 m_RHIResourceManagers->rhiGraphicsPipelineManager();
2487 const std::vector<HRHIGraphicsPipeline> &graphicsPipelinesHandles = pipelineManager->activeHandles();
2488 for (HRHIGraphicsPipeline pipelineHandle : graphicsPipelinesHandles) {
2489 RHIGraphicsPipeline *pipeline = pipelineManager->data(pipelineHandle);
2490 pipeline->decreaseScore();
2491 // Pipeline wasn't used recently, let's destroy it
2492 if (pipeline->score() < 0) {
2493 pipeline->cleanup();
2494 }
2495 }
2496
2497 // Clean buffers
2498 const QVector<Qt3DCore::QNodeId> buffersToRelease =
2499 m_nodesManager->bufferManager()->takeBuffersToRelease();
2500 for (Qt3DCore::QNodeId bufferId : buffersToRelease)
2501 m_submissionContext->releaseBuffer(bufferId);
2502
2503 // When Textures are cleaned up, their id is saved so that they can be
2504 // cleaned up in the render thread
2505 const QVector<Qt3DCore::QNodeId> cleanedUpTextureIds = std::move(m_textureIdsToCleanup);
2506 for (const Qt3DCore::QNodeId textureCleanedUpId : cleanedUpTextureIds)
2507 cleanupTexture(textureCleanedUpId);
2508
2509 // Abandon GL shaders when a Shader node is destroyed Note: We are sure
2510 // that when this gets executed, all scene changes have been received and
2511 // shader nodes updated
2512 const QVector<Qt3DCore::QNodeId> cleanedUpShaderIds =
2513 m_nodesManager->shaderManager()->takeShaderIdsToCleanup();
2514 for (const Qt3DCore::QNodeId shaderCleanedUpId : cleanedUpShaderIds) {
2515 cleanupShader(m_nodesManager->shaderManager()->lookupResource(shaderCleanedUpId));
2516 // We can really release the texture at this point
2517 m_nodesManager->shaderManager()->releaseResource(shaderCleanedUpId);
2518 }
2519 }
2520
contextInfo() const2521 const GraphicsApiFilterData *Renderer::contextInfo() const
2522 {
2523 return m_submissionContext->contextInfo();
2524 }
2525
submissionContext() const2526 SubmissionContext *Renderer::submissionContext() const
2527 {
2528 return m_submissionContext.data();
2529 }
2530
2531 } // namespace Rhi
2532 } // namespace Render
2533 } // namespace Qt3DRender
2534
2535 QT_END_NAMESPACE
2536