1// Copyright (C) 2020 Klaralvdalens Datakonsult AB (KDAB).
2// Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include "renderer_p.h"
6#include "rhirendertarget_p.h"
7
8#include <Qt3DCore/qentity.h>
9#include <Qt3DCore/private/vector_helper_p.h>
10
11#include <Qt3DRender/qmaterial.h>
12#include <Qt3DRender/qmesh.h>
13#include <Qt3DRender/qrenderpass.h>
14#include <Qt3DRender/qshaderprogram.h>
15#include <Qt3DRender/qtechnique.h>
16#include <Qt3DRender/qrenderaspect.h>
17#include <Qt3DRender/qeffect.h>
18#include <Qt3DRender/qrendercapture.h>
19
20#include <Qt3DRender/private/qsceneimporter_p.h>
21#include <Qt3DRender/private/renderstates_p.h>
22#include <Qt3DRender/private/cameraselectornode_p.h>
23#include <Qt3DRender/private/framegraphvisitor_p.h>
24#include <Qt3DRender/private/cameralens_p.h>
25#include <Qt3DRender/private/entity_p.h>
26#include <Qt3DRender/private/renderlogging_p.h>
27#include <Qt3DRender/private/material_p.h>
28#include <Qt3DRender/private/renderpassfilternode_p.h>
29#include <Qt3DRender/private/shader_p.h>
30#include <Qt3DRender/private/buffer_p.h>
31#include <Qt3DRender/private/technique_p.h>
32#include <Qt3DRender/private/scenemanager_p.h>
33#include <Qt3DRender/private/techniquefilternode_p.h>
34#include <Qt3DRender/private/viewportnode_p.h>
35#include <Qt3DRender/private/vsyncframeadvanceservice_p.h>
36#include <Qt3DRender/private/managers_p.h>
37#include <Qt3DRender/private/buffermanager_p.h>
38#include <Qt3DRender/private/nodemanagers_p.h>
39#include <Qt3DRender/private/geometryrenderermanager_p.h>
40#include <Qt3DRender/private/techniquemanager_p.h>
41#include <Qt3DRender/private/platformsurfacefilter_p.h>
42#include <Qt3DRender/private/rendercapture_p.h>
43#include <Qt3DRender/private/updatelevelofdetailjob_p.h>
44#include <Qt3DRender/private/buffercapture_p.h>
45#include <Qt3DRender/private/offscreensurfacehelper_p.h>
46#include <Qt3DRender/private/subtreeenabler_p.h>
47#include <Qt3DRender/private/qshaderprogrambuilder_p.h>
48#include <Qt3DRender/private/qshaderprogram_p.h>
49
50#include <Qt3DRender/qcameralens.h>
51#include <Qt3DCore/private/qabstractaspectjobmanager_p.h>
52#include <Qt3DCore/private/qaspectmanager_p.h>
53#include <Qt3DCore/private/qsysteminformationservice_p.h>
54#include <Qt3DCore/private/qsysteminformationservice_p_p.h>
55#include <Qt3DRender/private/resourceaccessor_p.h>
56#include <Qt3DRender/private/renderlogging_p.h>
57#include <Qt3DRender/private/renderstateset_p.h>
58#include <Qt3DRender/private/setfence_p.h>
59#include <Qt3DRender/private/stringtoint_p.h>
60#include <Qt3DRender/private/qrenderaspect_p.h>
61
62#include <rhibuffer_p.h>
63#include <rhigraphicspipeline_p.h>
64
65#include <rendercommand_p.h>
66#include <renderview_p.h>
67#include <texture_p.h>
68#include <renderviewbuilder_p.h>
69#include <rhiresourcemanagers_p.h>
70#include <commandexecuter_p.h>
71#include <submissioncontext_p.h>
72
73#include <QStack>
74#include <QOffscreenSurface>
75#include <QSurface>
76#include <QElapsedTimer>
77#include <QLibraryInfo>
78#include <QMutexLocker>
79#include <QPluginLoader>
80#include <QDir>
81#include <QUrl>
82#include <QOffscreenSurface>
83#include <QWindow>
84#include <QThread>
85#include <QKeyEvent>
86#include <QMouseEvent>
87
88#include <QtGui/private/qopenglcontext_p.h>
89#include <QGuiApplication>
90#include <Qt3DCore/private/qthreadpooler_p.h>
91
92#include <optional>
93
94QT_BEGIN_NAMESPACE
95
96namespace Qt3DRender {
97namespace Render {
98namespace Rhi {
99
100using RendererCache = Render::RendererCache<RenderCommand>;
101
102namespace {
103
104class CachingLightGatherer : public LightGatherer
105{
106public:
107 CachingLightGatherer(RendererCache *cache) : LightGatherer(), m_cache(cache) { }
108
109 void run() override
110 {
111 LightGatherer::run();
112
113 QMutexLocker lock(m_cache->mutex());
114 m_cache->gatheredLights = lights();
115 m_cache->environmentLight = environmentLight();
116 }
117
118private:
119 RendererCache *m_cache;
120};
121
122class CachingRenderableEntityFilter : public RenderableEntityFilter
123{
124public:
125 CachingRenderableEntityFilter(RendererCache *cache) : RenderableEntityFilter(), m_cache(cache)
126 {
127 }
128
129 void run() override
130 {
131 RenderableEntityFilter::run();
132
133 std::vector<Entity *> selectedEntities = filteredEntities();
134 std::sort(first: selectedEntities.begin(), last: selectedEntities.end());
135
136 QMutexLocker lock(m_cache->mutex());
137 m_cache->renderableEntities = std::move(selectedEntities);
138 }
139
140private:
141 RendererCache *m_cache;
142};
143
144class CachingComputableEntityFilter : public ComputableEntityFilter
145{
146public:
147 CachingComputableEntityFilter(RendererCache *cache) : ComputableEntityFilter(), m_cache(cache)
148 {
149 }
150
151 void run() override
152 {
153 ComputableEntityFilter::run();
154
155 std::vector<Entity *> selectedEntities = filteredEntities();
156 std::sort(first: selectedEntities.begin(), last: selectedEntities.end());
157
158 QMutexLocker lock(m_cache->mutex());
159 m_cache->computeEntities = std::move(selectedEntities);
160 }
161
162private:
163 RendererCache *m_cache;
164};
165
166int locationForAttribute(Attribute *attr, const RHIShader *shader) noexcept
167{
168 const std::vector<ShaderAttribute> &attribInfo = shader->attributes();
169 const auto it = std::find_if(first: attribInfo.begin(), last: attribInfo.end(),
170 pred: [attr](const ShaderAttribute &sAttr) { return attr->nameId() == sAttr.m_nameId; });
171 if (it != attribInfo.end())
172 return it->m_location;
173 return -1;
174}
175
176} // anonymous
177
178/*!
179 \internal
180
181 Renderer shutdown procedure:
182
183 Since the renderer relies on the surface and OpenGLContext to perform its cleanup,
184 it is shutdown when the surface is set to nullptr
185
186 When the surface is set to nullptr this will request the RenderThread to terminate
187 and will prevent createRenderBinJobs from returning a set of jobs as there is nothing
188 more to be rendered.
189
190 In turn, this will call shutdown which will make the OpenGL context current one last time
191 to allow cleanups requiring a call to QOpenGLContext::currentContext to execute properly.
192 At the end of that function, the GraphicsContext is set to null.
193
194 At this point though, the QAspectThread is still running its event loop and will only stop
195 a short while after.
196 */
197
198Renderer::Renderer()
199 : m_services(nullptr),
200 m_aspect(nullptr),
201 m_nodesManager(nullptr),
202 m_renderSceneRoot(nullptr),
203 m_defaultRenderStateSet(nullptr),
204 m_submissionContext(nullptr),
205 m_vsyncFrameAdvanceService(new VSyncFrameAdvanceService(false)),
206 m_waitForInitializationToBeCompleted(0),
207 m_hasBeenInitializedMutex(),
208 m_exposed(0),
209 m_lastFrameCorrect(0),
210 m_glContext(nullptr),
211 m_rhiContext(nullptr),
212 m_time(0),
213 m_settings(nullptr),
214 m_updateShaderDataTransformJob(Render::UpdateShaderDataTransformJobPtr::create()),
215 m_cleanupJob(Render::FrameCleanupJobPtr::create()),
216 m_sendBufferCaptureJob(Render::SendBufferCaptureJobPtr::create()),
217 m_filterCompatibleTechniqueJob(FilterCompatibleTechniqueJobPtr::create()),
218 m_lightGathererJob(new CachingLightGatherer(&m_cache)),
219 m_renderableEntityFilterJob(new CachingRenderableEntityFilter(&m_cache)),
220 m_computableEntityFilterJob(new CachingComputableEntityFilter(&m_cache)),
221 m_bufferGathererJob(SynchronizerJobPtr::create(arguments: [this] { lookForDirtyBuffers(); },
222 arguments: JobTypes::DirtyBufferGathering)),
223 m_textureGathererJob(SynchronizerJobPtr::create(arguments: [this] { lookForDirtyTextures(); },
224 arguments: JobTypes::DirtyTextureGathering)),
225 m_introspectShaderJob(SynchronizerPostFramePtr::create(
226 arguments: [this] { reloadDirtyShaders(); },
227 arguments: [this](Qt3DCore::QAspectManager *m) { sendShaderChangesToFrontend(manager: m); },
228 arguments: JobTypes::DirtyShaderGathering)),
229 m_ownedContext(false),
230 m_RHIResourceManagers(nullptr),
231 m_commandExecuter(new Qt3DRender::DebugRhi::CommandExecuter(this)),
232 m_shouldSwapBuffers(true)
233{
234 std::fill_n(first: m_textureTransform, n: 4, value: 0.f);
235
236 // Set renderer as running - it will wait in the context of the
237 // RenderThread for RenderViews to be submitted
238 m_running.fetchAndStoreOrdered(newValue: 1);
239
240 m_introspectShaderJob->addDependency(dependency: m_filterCompatibleTechniqueJob);
241
242 m_filterCompatibleTechniqueJob->setRenderer(this);
243
244 m_defaultRenderStateSet = new RenderStateSet;
245 m_defaultRenderStateSet->addState(state: StateVariant::createState<DepthTest>(GL_LESS));
246 m_defaultRenderStateSet->addState(state: StateVariant::createState<CullFace>(GL_BACK));
247 m_defaultRenderStateSet->addState(state: StateVariant::createState<ColorMask>(values: true, values: true, values: true, values: true));
248}
249
250Renderer::~Renderer()
251{
252 Q_ASSERT(m_running.fetchAndStoreOrdered(0) == 0);
253
254 delete m_defaultRenderStateSet;
255 delete m_RHIResourceManagers;
256
257 if (!m_ownedContext)
258 QObject::disconnect(m_contextConnection);
259}
260
261void Renderer::dumpInfo() const
262{
263 qDebug() << Q_FUNC_INFO << "t =" << m_time;
264
265 const ShaderManager *shaderManager = m_nodesManager->shaderManager();
266 qDebug() << "=== Shader Manager ===";
267 qDebug() << *shaderManager;
268
269 const TextureManager *textureManager = m_nodesManager->textureManager();
270 qDebug() << "=== Texture Manager ===";
271 qDebug() << *textureManager;
272
273 const TextureImageManager *textureImageManager = m_nodesManager->textureImageManager();
274 qDebug() << "=== Texture Image Manager ===";
275 qDebug() << *textureImageManager;
276}
277
278API Renderer::api() const
279{
280 return API::RHI;
281}
282
283// When Scene3D is driving rendering:
284// - Don't create SwapChains
285// - Use the defaultRenderTarget if no renderTarget specified instead of the
286// swapChain
287// - Use the provided commandBuffer
288void Renderer::setRenderDriver(AbstractRenderer::RenderDriver driver)
289{
290 // This must be called before initialize which creates the submission context
291 Q_ASSERT(!m_submissionContext);
292 m_driver = driver;
293}
294
295AbstractRenderer::RenderDriver Renderer::renderDriver() const
296{
297 return m_driver;
298}
299
300qint64 Renderer::time() const
301{
302 return m_time;
303}
304
305void Renderer::setTime(qint64 time)
306{
307 m_time = time;
308}
309
310void Renderer::setJobsInLastFrame(int jobsInLastFrame)
311{
312 m_jobsInLastFrame = jobsInLastFrame;
313}
314
315void Renderer::setAspect(QRenderAspect *aspect)
316{
317 m_aspect = aspect;
318 m_updateShaderDataTransformJob->addDependency(
319 dependency: QRenderAspectPrivate::get(q: aspect)->m_worldTransformJob);
320}
321
322void Renderer::setNodeManagers(NodeManagers *managers)
323{
324 m_nodesManager = managers;
325 m_RHIResourceManagers = new RHIResourceManagers();
326 m_scene2DResourceAccessor.reset(t: new ResourceAccessor(this, m_nodesManager));
327
328 m_updateShaderDataTransformJob->setManagers(m_nodesManager);
329 m_cleanupJob->setManagers(m_nodesManager);
330 m_filterCompatibleTechniqueJob->setManager(m_nodesManager->techniqueManager());
331 m_sendBufferCaptureJob->setManagers(m_nodesManager);
332 m_lightGathererJob->setManager(m_nodesManager->renderNodesManager());
333 m_renderableEntityFilterJob->setManager(m_nodesManager->renderNodesManager());
334 m_computableEntityFilterJob->setManager(m_nodesManager->renderNodesManager());
335}
336
337void Renderer::setServices(Qt3DCore::QServiceLocator *services)
338{
339 m_services = services;
340
341 m_nodesManager->sceneManager()->setDownloadService(m_services->downloadHelperService());
342}
343
344QRenderAspect *Renderer::aspect() const
345{
346 return m_aspect;
347}
348
349NodeManagers *Renderer::nodeManagers() const
350{
351 return m_nodesManager;
352}
353
354/*!
355 \internal
356
357 Return context which can be used to share resources safely
358 with qt3d main render context.
359*/
360QOpenGLContext *Renderer::shareContext() const
361{
362 return nullptr;
363}
364
365void Renderer::setOpenGLContext(QOpenGLContext *context)
366{
367 m_glContext = context;
368}
369
370void Renderer::setRHIContext(QRhi *ctx)
371{
372 m_rhiContext = ctx;
373}
374
375// Called by Scene3D
376void Renderer::setDefaultRHIRenderTarget(QRhiRenderTarget *defaultTarget)
377{
378 Q_ASSERT(m_submissionContext);
379 m_submissionContext->setDefaultRenderTarget(defaultTarget);
380
381 // When the defaultTarget changes, we have to recreate all QRhiGraphicsPipelines
382 // to ensure they match new DefaultRenderTargetLayout. This is potentially expensive
383 RHIGraphicsPipelineManager *pipelineManager = m_RHIResourceManagers->rhiGraphicsPipelineManager();
384 pipelineManager->releaseAllResources();
385}
386
387// Called by Scene3D
388void Renderer::setRHICommandBuffer(QRhiCommandBuffer *commandBuffer)
389{
390 Q_ASSERT(m_submissionContext);
391 m_submissionContext->setCommandBuffer(commandBuffer);
392}
393
394void Renderer::setScreen(QScreen *scr)
395{
396 m_screen = scr;
397}
398
399QScreen *Renderer::screen() const
400{
401 return m_screen;
402}
403
404bool Renderer::accessOpenGLTexture(Qt3DCore::QNodeId nodeId, QOpenGLTexture **texture,
405 QMutex **lock, bool readonly)
406{
407 RHI_UNIMPLEMENTED;
408 Q_UNUSED(texture);
409
410 Texture *tex = m_nodesManager->textureManager()->lookupResource(id: nodeId);
411 if (!tex)
412 return false;
413
414 RHITexture *glTex = m_RHIResourceManagers->rhiTextureManager()->lookupResource(id: tex->peerId());
415 if (!glTex)
416 return false;
417
418 if (glTex->isDirty())
419 return false;
420
421 if (!readonly)
422 glTex->setExternalRenderingEnabled(true);
423
424 // RHITexture::TextureUpdateInfo texInfo =
425 // glTex->createOrUpdateRhiTexture(m_submissionContext.data()); *texture = texInfo.texture;
426
427 if (!readonly)
428 *lock = glTex->externalRenderingLock();
429
430 return true;
431}
432
433QSharedPointer<RenderBackendResourceAccessor> Renderer::resourceAccessor() const
434{
435 return m_scene2DResourceAccessor;
436}
437
438// Called in RenderThread context by the run method of RenderThread
439// RenderThread has locked the mutex already and unlocks it when this
440// method termintates
441void Renderer::initialize()
442{
443 QMutexLocker lock(&m_hasBeenInitializedMutex);
444 m_submissionContext.reset(other: new SubmissionContext);
445 m_submissionContext->setRenderer(this);
446
447 if (m_driver == AbstractRenderer::Scene3D) {
448 m_submissionContext->setRHIContext(m_rhiContext);
449 m_submissionContext->setDrivenExternally(true);
450 }
451
452 // RHI initialization
453 {
454 qCDebug(Backend) << Q_FUNC_INFO << "Requesting renderer initialize";
455 m_submissionContext->initialize();
456
457 // We need to adapt texture coordinates
458 // m_textureTransform is (a;b) in texCoord = a * texCoord + b
459 if (m_submissionContext->rhi()->isYUpInFramebuffer()) {
460 // OpenGL case - that is what we assume to be the default so we do not change
461 // anything
462 m_textureTransform[0] = 1.f;
463 m_textureTransform[1] = 1.f;
464 m_textureTransform[2] = 0.f;
465 m_textureTransform[3] = 0.f;
466 } else {
467 // Other cases : y = 1 - y
468 m_textureTransform[0] = 1.f;
469 m_textureTransform[1] = -1.f;
470 m_textureTransform[2] = 0.f;
471 m_textureTransform[3] = 1.f;
472 }
473
474 // Awake setScenegraphRoot in case it was waiting
475 m_waitForInitializationToBeCompleted.release(n: 1);
476
477 // Allow the aspect manager to proceed
478 m_vsyncFrameAdvanceService->proceedToNextFrame();
479
480 // Force initial refresh
481 markDirty(changes: AllDirty, node: nullptr);
482 return;
483 }
484}
485
486/*!
487 * \internal
488 *
489 * Signals for the renderer to stop rendering. If a threaded renderer is in use,
490 * the render thread will call releaseGraphicsResources() just before the thread exits.
491 * If rendering synchronously, this function will call releaseGraphicsResources().
492 */
493void Renderer::shutdown()
494{
495 // Ensure we have waited to be fully initialized before trying to shut down
496 // (in case initialization is taking place at the same time)
497 QMutexLocker lock(&m_hasBeenInitializedMutex);
498
499 qCDebug(Backend) << Q_FUNC_INFO << "Requesting renderer shutdown";
500 m_running.storeRelaxed(newValue: 0);
501
502 // We delete any renderqueue that we may not have had time to render
503 // before the surface was destroyed
504 QMutexLocker lockRenderQueue(m_renderQueue.mutex());
505 m_renderQueue.reset();
506 lockRenderQueue.unlock();
507
508 releaseGraphicsResources();
509
510 // Destroy internal managers
511 // This needs to be done before the nodeManager is destroy
512 // as the internal resources might somehow rely on nodeManager resources
513 delete m_RHIResourceManagers;
514 m_RHIResourceManagers = nullptr;
515}
516
517/*!
518 \internal
519
520 When using a threaded renderer this function is called in the context of the
521 RenderThread to do any shutdown and cleanup that needs to be performed in the
522 thread where the OpenGL context lives.
523
524 When using Scene3D or anything that provides a custom QOpenGLContext (not
525 owned by Qt3D) this function is called whenever the signal
526 QOpenGLContext::aboutToBeDestroyed is emitted. In that case this function
527 is called in the context of the emitter's thread.
528*/
529void Renderer::releaseGraphicsResources()
530{
531 // We may get called twice when running inside of a Scene3D. Once when Qt Quick
532 // wants to shutdown, and again when the render aspect gets unregistered. So
533 // check that we haven't already cleaned up before going any further.
534 if (!m_submissionContext)
535 return;
536 m_submissionContext.reset(other: nullptr);
537
538 qCDebug(Backend) << Q_FUNC_INFO << "Renderer properly shutdown";
539}
540
541void Renderer::setSurfaceExposed(bool exposed)
542{
543 qCDebug(Backend) << "Window exposed: " << exposed;
544 m_exposed.fetchAndStoreOrdered(newValue: exposed);
545}
546
547Render::FrameGraphNode *Renderer::frameGraphRoot() const
548{
549 Q_ASSERT(m_settings);
550 if (m_nodesManager && m_nodesManager->frameGraphManager() && m_settings)
551 return m_nodesManager->frameGraphManager()->lookupNode(id: m_settings->activeFrameGraphID());
552 return nullptr;
553}
554
555// QAspectThread context
556// Order of execution :
557// 1) RenderThread is created -> release 1 of m_waitForInitializationToBeCompleted when started
558// 2) setSceneRoot waits to acquire initialization
559// 3) submitRenderView -> check for surface
560// -> make surface current + create proper glHelper if needed
561void Renderer::setSceneRoot(Entity *sgRoot)
562{
563 Q_ASSERT(sgRoot);
564
565 // If initialization hasn't been completed we must wait
566 m_waitForInitializationToBeCompleted.acquire();
567
568 m_renderSceneRoot = sgRoot;
569 if (!m_renderSceneRoot)
570 qCWarning(Backend) << "Failed to build render scene";
571 m_renderSceneRoot->dump();
572 qCDebug(Backend) << Q_FUNC_INFO << "DUMPING SCENE";
573
574 // Set the scene root on the jobs
575 m_cleanupJob->setRoot(m_renderSceneRoot);
576
577 // Set all flags to dirty
578 m_dirtyBits.marked |= AbstractRenderer::AllDirty;
579}
580
581void Renderer::setSettings(RenderSettings *settings)
582{
583 m_settings = settings;
584}
585
586RenderSettings *Renderer::settings() const
587{
588 return m_settings;
589}
590
591// Either called by render if Qt3D is in charge of rendering (in the mainthread)
592// or by QRenderAspectPrivate::render (for Scene3D, potentially from a RenderThread)
593// This will wait until renderQueue is ready or shutdown was requested
594void Renderer::render(bool swapBuffers)
595{
596 Renderer::ViewSubmissionResultData submissionData;
597 bool beganDrawing = false;
598
599 // Blocking until RenderQueue is full
600 const bool canSubmit = waitUntilReadyToSubmit();
601
602 // If it returns false -> we are shutting down
603 if (!canSubmit)
604 return;
605
606 m_shouldSwapBuffers = swapBuffers;
607 const std::vector<Render::Rhi::RenderView *> &renderViews = m_renderQueue.nextFrameQueue();
608 const bool queueIsEmpty = m_renderQueue.targetRenderViewCount() == 0;
609
610 bool mustCleanResources = false;
611
612 // RenderQueue is complete (but that means it may be of size 0)
613 if (!queueIsEmpty) {
614 Qt3DCore::QTaskLogger submissionStatsPart1(m_services->systemInformation(),
615 { JobTypes::FrameSubmissionPart1, 0 },
616 Qt3DCore::QTaskLogger::Submission);
617 Qt3DCore::QTaskLogger submissionStatsPart2(m_services->systemInformation(),
618 { JobTypes::FrameSubmissionPart2, 0 },
619 Qt3DCore::QTaskLogger::Submission);
620
621 std::vector<RHIPassInfo> rhiPassesInfo;
622
623 QSurface *surface = nullptr;
624 for (const RenderView *rv : renderViews) {
625 surface = rv->surface();
626 if (surface)
627 break;
628 }
629
630 // In case we did not draw because e.g. there was no swapchain,
631 // we keep the resource updates from the previous frame.
632 if (!m_submissionContext->m_currentUpdates) {
633 m_submissionContext->m_currentUpdates =
634 m_submissionContext->rhi()->nextResourceUpdateBatch();
635 }
636
637 // 1) Execute commands for buffer uploads, texture updates, shader loading first
638 updateResources();
639
640 rhiPassesInfo = prepareCommandsSubmission(renderViews);
641 // 2) Update Pipelines and copy data into commands to allow concurrent submission
642
643 {
644 // Scoped to destroy surfaceLock
645 SurfaceLocker surfaceLock(surface);
646 const bool surfaceIsValid = (surface && surfaceLock.isSurfaceValid());
647 if (surfaceIsValid) {
648 beganDrawing = m_submissionContext->beginDrawing(surface);
649 if (beganDrawing) {
650 // Purge shader which aren't used any longer
651 static int callCount = 0;
652 ++callCount;
653 const int shaderPurgePeriod = 600;
654 if (callCount % shaderPurgePeriod == 0)
655 m_RHIResourceManagers->rhiShaderManager()->purge();
656 }
657 }
658 }
659
660 // Only try to submit the RenderViews if the preprocessing was successful
661 // This part of the submission is happening in parallel to the RV building for the next
662 // frame
663 if (beganDrawing) {
664 submissionStatsPart1.end(t: submissionStatsPart2.restart());
665
666 // 3) Submit the render commands for frame n (making sure we never reference
667 // something that could be changing) Render using current device state and renderer
668 // configuration
669 submissionData = submitRenderViews(rhiPassesInfo);
670
671 // Perform any required cleanup of the Graphics resources (Buffers deleted, Shader
672 // deleted...)
673 mustCleanResources = true;
674 }
675
676 // Execute the pending shell commands
677 m_commandExecuter->performAsynchronousCommandExecution(views: renderViews);
678
679 // Delete all the RenderViews which will clear the allocators
680 // that were used for their allocation
681 }
682
683 // Perform the last swapBuffers calls after the proceedToNextFrame
684 // as this allows us to gain a bit of time for the preparation of the
685 // next frame
686 // Finish up with last surface used in the list of RenderViews
687 if (beganDrawing) {
688 SurfaceLocker surfaceLock(submissionData.surface);
689 // Finish up with last surface used in the list of RenderViews
690 const bool swapBuffers =
691 true // submissionData.lastBoundFBOId == m_submissionContext->defaultFBO()
692 && surfaceLock.isSurfaceValid()
693 && m_shouldSwapBuffers;
694 m_submissionContext->endDrawing(swapBuffers);
695
696 if (mustCleanResources)
697 cleanGraphicsResources();
698 }
699
700 // Reset RenderQueue and destroy the renderViews
701 m_renderQueue.reset();
702
703 // We allow the RenderTickClock service to proceed to the next frame
704 // In turn this will allow the aspect manager to request a new set of jobs
705 // to be performed for each aspect
706 m_vsyncFrameAdvanceService->proceedToNextFrame();
707}
708
709// Called by RenderViewJobs
710// When the frameQueue is complete and we are using a renderThread
711// we allow the render thread to proceed
712void Renderer::enqueueRenderView(RenderView *renderView, int submitOrder)
713{
714 QMutexLocker locker(m_renderQueue.mutex()); // Prevent out of order execution
715 // We cannot use a lock free primitive here because:
716 // - std::vector is not thread safe
717 // - Even if the insert is made correctly, the isFrameComplete call
718 // could be invalid since depending on the order of execution
719 // the counter could be complete but the renderview not yet added to the
720 // buffer depending on whichever order the cpu decides to process this
721 const bool isQueueComplete = m_renderQueue.queueRenderView(renderView, submissionOrderIndex: submitOrder);
722 locker.unlock(); // We're done protecting the queue at this point
723 if (isQueueComplete) {
724 m_submitRenderViewsSemaphore.release(n: 1);
725 }
726}
727
728bool Renderer::waitUntilReadyToSubmit()
729{
730 // Make sure that we've been told to render before rendering
731 // Prevent ouf of order execution
732 m_submitRenderViewsSemaphore.acquire(n: 1);
733
734 // Check if shutdown has been requested
735 if (m_running.loadRelaxed() == 0)
736 return false;
737
738 // The semaphore should only
739 // be released when the frame queue is complete and there's
740 // something to render
741 // The case of shutdown should have been handled just before
742 Q_ASSERT(m_renderQueue.isFrameQueueComplete());
743 return true;
744}
745
746// Main thread
747QVariant Renderer::executeCommand(const QStringList &args)
748{
749 return m_commandExecuter->executeCommand(args);
750}
751
752QSurfaceFormat Renderer::format()
753{
754 return m_submissionContext->format();
755}
756
757namespace {
758std::optional<QRhiVertexInputAttribute::Format> rhiAttributeType(Attribute *attr) {
759 switch (attr->vertexBaseType()) {
760 case Qt3DCore::QAttribute::Byte:
761 case Qt3DCore::QAttribute::UnsignedByte: {
762 if (attr->vertexSize() == 1)
763 return QRhiVertexInputAttribute::UNormByte;
764 if (attr->vertexSize() == 2)
765 return QRhiVertexInputAttribute::UNormByte2;
766 if (attr->vertexSize() == 4)
767 return QRhiVertexInputAttribute::UNormByte4;
768 break;
769 }
770 case Qt3DCore::QAttribute::UnsignedInt: {
771 if (attr->vertexSize() == 1)
772 return QRhiVertexInputAttribute::UInt;
773 if (attr->vertexSize() == 2)
774 return QRhiVertexInputAttribute::UInt2;
775 if (attr->vertexSize() == 3)
776 return QRhiVertexInputAttribute::UInt3;
777 if (attr->vertexSize() == 4)
778 return QRhiVertexInputAttribute::UInt4;
779 break;
780 }
781 case Qt3DCore::QAttribute::Float: {
782 if (attr->vertexSize() == 1)
783 return QRhiVertexInputAttribute::Float;
784 if (attr->vertexSize() == 2)
785 return QRhiVertexInputAttribute::Float2;
786 if (attr->vertexSize() == 3)
787 return QRhiVertexInputAttribute::Float3;
788 if (attr->vertexSize() >= 4)
789 return QRhiVertexInputAttribute::Float4;
790 break;
791 }
792 default:
793 break;
794 }
795 return std::nullopt;
796}
797}
798
799void Renderer::updateGraphicsPipeline(RenderCommand &cmd, RenderView *rv)
800{
801 if (!cmd.m_rhiShader) {
802 qCWarning(Backend) << "Command has no shader";
803 return;
804 }
805
806 // The Graphics Pipeline defines
807 // - Render State (Depth, Culling, Stencil, Blending)
808 // - Shader Resources Binding
809 // - Shader Vertex Attribute Layout
810
811 // This means we need to have one GraphicsPipeline per
812 // - geometry layout
813 // - material
814 // - state (RV + RC)
815
816 // Try to retrieve existing pipeline
817 // TO DO: Make RenderState part of the Key
818 // as it is likely many geometrys will have the same layout
819 RHIGraphicsPipelineManager *pipelineManager = m_RHIResourceManagers->rhiGraphicsPipelineManager();
820 const int geometryLayoutId = pipelineManager->getIdForAttributeVec(attributesInfo: cmd.m_attributeInfo);
821 const int renderStatesKey = pipelineManager->getIdForRenderStates(stateSet: cmd.m_stateSet);
822 const GraphicsPipelineIdentifier pipelineKey { .geometryLayoutKey: geometryLayoutId, .shader: cmd.m_shaderId, .renderTarget: rv->renderTargetId(), .primitiveType: cmd.m_primitiveType, .renderStatesKey: renderStatesKey };
823 RHIGraphicsPipeline *graphicsPipeline = pipelineManager->lookupResource(id: pipelineKey);
824 if (graphicsPipeline == nullptr) {
825 // Init UBOSet the first time we allocate a new pipeline
826 graphicsPipeline = pipelineManager->getOrCreateResource(id: pipelineKey);
827 graphicsPipeline->setKey(pipelineKey);
828 graphicsPipeline->uboSet()->setResourceManager(m_RHIResourceManagers);
829 graphicsPipeline->uboSet()->setNodeManagers(m_nodesManager);
830 graphicsPipeline->uboSet()->initializeLayout(ctx: m_submissionContext.data(), shader: cmd.m_rhiShader);
831 }
832
833 // Increase score so that we know the pipeline was used for this frame and shouldn't be
834 // destroyed
835 graphicsPipeline->increaseScore();
836
837 // Record command reference in UBOSet
838 graphicsPipeline->uboSet()->addRenderCommand(cmd);
839
840 // Store association between RV and pipeline
841 if (auto& pipelines = m_rvToGraphicsPipelines[rv]; !Qt3DCore::contains(destination: pipelines, element: graphicsPipeline))
842 pipelines.push_back(x: graphicsPipeline);
843
844 // Record RHIGraphicsPipeline into command for later use
845 cmd.pipeline = graphicsPipeline;
846
847 // TO DO: Set to true if geometry, shader or render state dirty
848 bool requiresRebuild = false;
849
850 // Build/Rebuild actual RHI pipeline if required
851 // TO DO: Ensure we find a way to know when the state is dirty to trigger a rebuild
852 if (graphicsPipeline->pipeline() == nullptr || requiresRebuild)
853 buildGraphicsPipelines(graphicsPipeline, rv, command: cmd);
854}
855
856void Renderer::buildGraphicsPipelines(RHIGraphicsPipeline *graphicsPipeline,
857 RenderView *rv,
858 const RenderCommand &cmd)
859{
860 // Note: This is completely stupid but RHI doesn't allow you to build
861 // a QRhiShaderResourceBindings if you don't already have buffers/textures
862 // at hand (where it should only need to know the type/formats ... of the resources)
863 // For that reason we need to provide a RenderCommand
864
865 // Note: we can rebuild add/remove things from the QRhiShaderResourceBindings after having
866 // created the pipeline and rebuild it. Changes should be picked up automatically
867
868 // If a defaultRenderTarget was set (Scene3D) we don't bother retrieving the swapchain
869 // as we have to use the one provided by Scene3D
870 QRhiSwapChain *rhiSwapChain = nullptr;
871 if (!m_submissionContext->defaultRenderTarget()) {
872 const SubmissionContext::SwapChainInfo *swapchain = m_submissionContext->swapChainForSurface(surface: rv->surface());
873 if (!swapchain || !swapchain->swapChain || !swapchain->renderPassDescriptor) {
874 qCWarning(Backend) << "Can't create pipeline, incomplete SwapChain and no default Render Target";
875 return;
876 }
877 rhiSwapChain = swapchain->swapChain;
878 }
879
880 auto onFailure = [&](const char* msg) {
881 qCWarning(Backend) << "Failed to build graphics pipeline:" << msg;
882 };
883
884 PipelineUBOSet *uboSet = graphicsPipeline->uboSet();
885 RHIShader *shader = cmd.m_rhiShader;
886
887 // Setup shaders
888 const QShader& vertexShader = shader->shaderStage(stage: QShader::VertexStage);
889 if (!vertexShader.isValid())
890 return onFailure("Invalid vertex shader");
891
892 const QShader& fragmentShader = shader->shaderStage(stage: QShader::FragmentStage);
893 if (!fragmentShader.isValid())
894 return onFailure("Invalid fragment shader");
895
896 // Set Resource Bindings
897 const std::vector<QRhiShaderResourceBinding> resourceBindings = uboSet->resourceLayout(shader);
898 QRhiShaderResourceBindings *shaderResourceBindings =
899 m_submissionContext->rhi()->newShaderResourceBindings();
900 graphicsPipeline->setShaderResourceBindings(shaderResourceBindings);
901
902 shaderResourceBindings->setBindings(first: resourceBindings.cbegin(), last: resourceBindings.cend());
903 if (!shaderResourceBindings->create())
904 return onFailure("Unable to create resource bindings");
905
906 // Setup attributes
907 const Geometry *geom = cmd.m_geometry.data();
908 QVarLengthArray<QRhiVertexInputBinding, 8> inputBindings;
909 QVarLengthArray<QRhiVertexInputAttribute, 8> rhiAttributes;
910 QHash<int, int> attributeNameToBinding;
911
912 if (!prepareGeometryInputBindings(geometry: geom, shader: cmd.m_rhiShader,
913 inputBindings, rhiAttributes,
914 attributeNameToBinding))
915 return onFailure("Geometry doesn't match expected layout");
916
917 // Create pipeline
918 QRhiGraphicsPipeline *pipeline = m_submissionContext->rhi()->newGraphicsPipeline();
919 graphicsPipeline->setPipeline(pipeline);
920
921 pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vertexShader },
922 { QRhiShaderStage::Fragment, fragmentShader } });
923
924 pipeline->setShaderResourceBindings(shaderResourceBindings);
925
926 auto rhiTopologyFromQt3DTopology = [] (const Qt3DRender::QGeometryRenderer::PrimitiveType t) {
927 switch (t) {
928 case Qt3DRender::QGeometryRenderer::Points:
929 return QRhiGraphicsPipeline::Points;
930
931 case Qt3DRender::QGeometryRenderer::Lines:
932 return QRhiGraphicsPipeline::Lines;
933
934 case Qt3DRender::QGeometryRenderer::LineStrip:
935 return QRhiGraphicsPipeline::LineStrip;
936
937 case Qt3DRender::QGeometryRenderer::Triangles:
938 return QRhiGraphicsPipeline::Triangles;
939
940 case Qt3DRender::QGeometryRenderer::TriangleStrip:
941 return QRhiGraphicsPipeline::TriangleStrip;
942
943 case Qt3DRender::QGeometryRenderer::TriangleFan:
944 return QRhiGraphicsPipeline::TriangleFan;
945
946 case Qt3DRender::QGeometryRenderer::LineLoop: {
947 qWarning(catFunc: Backend) << "LineLoop primitive type is not handled by RHI";
948 return QRhiGraphicsPipeline::Lines;
949 }
950
951 case Qt3DRender::QGeometryRenderer::Patches: {
952 qWarning(catFunc: Backend) << "Patches primitive type is not handled by RHI";
953 return QRhiGraphicsPipeline::Points;
954 }
955
956 case Qt3DRender::QGeometryRenderer::LinesAdjacency:
957 case Qt3DRender::QGeometryRenderer::TrianglesAdjacency:
958 case Qt3DRender::QGeometryRenderer::LineStripAdjacency:
959 case Qt3DRender::QGeometryRenderer::TriangleStripAdjacency: {
960 qWarning(catFunc: Backend) << "Adjancency primitive types are not handled by RHI";
961 return QRhiGraphicsPipeline::Points;
962 }
963 }
964 return QRhiGraphicsPipeline::Points;
965 };
966
967 pipeline->setTopology(rhiTopologyFromQt3DTopology(cmd.m_primitiveType));
968
969 QRhiVertexInputLayout inputLayout;
970 inputLayout.setBindings(first: inputBindings.begin(), last: inputBindings.end());
971 inputLayout.setAttributes(first: rhiAttributes.begin(), last: rhiAttributes.end());
972 pipeline->setVertexInputLayout(inputLayout);
973 graphicsPipeline->setAttributesToBindingHash(attributeNameToBinding);
974
975 // Render States
976 RenderStateSet *renderState = nullptr;
977 {
978 RenderStateSet *globalState =
979 (rv->stateSet() != nullptr) ? rv->stateSet() : m_defaultRenderStateSet;
980
981 // Merge global state into local state
982 RenderStateSet *localState = cmd.m_stateSet.data();
983 if (localState != nullptr) {
984 localState->merge(other: globalState);
985 renderState = localState;
986 } else {
987 renderState = globalState;
988 }
989 }
990 m_submissionContext->applyStateSet(ss: renderState, graphicsPipeline: pipeline);
991
992 // Setup potential texture render target
993 const bool renderTargetIsSet = setupRenderTarget(rv, graphicsPipeline, swapchain: rhiSwapChain);
994 if (!renderTargetIsSet)
995 return onFailure("No Render Target Set");
996
997 if (!pipeline->create())
998 return onFailure("Creation Failed");
999
1000 graphicsPipeline->markComplete();
1001}
1002
1003void Renderer::updateComputePipeline(RenderCommand &cmd, RenderView *rv, int renderViewIndex)
1004{
1005 if (!cmd.m_rhiShader) {
1006 qCWarning(Backend) << "Command has no shader";
1007 return;
1008 }
1009
1010 // Try to retrieve existing pipeline
1011 RHIComputePipelineManager *pipelineManager = m_RHIResourceManagers->rhiComputePipelineManager();
1012 const ComputePipelineIdentifier pipelineKey { .shader: cmd.m_shaderId, .renderViewIndex: renderViewIndex };
1013 RHIComputePipeline *computePipeline = pipelineManager->lookupResource(id: pipelineKey);
1014 if (computePipeline == nullptr) {
1015 // Init UBOSet the first time we allocate a new pipeline
1016 computePipeline = pipelineManager->getOrCreateResource(id: pipelineKey);
1017 computePipeline->setKey(pipelineKey);
1018 computePipeline->uboSet()->setResourceManager(m_RHIResourceManagers);
1019 computePipeline->uboSet()->setNodeManagers(m_nodesManager);
1020 computePipeline->uboSet()->initializeLayout(ctx: m_submissionContext.data(), shader: cmd.m_rhiShader);
1021 }
1022
1023 if (!computePipeline) {
1024 qCWarning(Backend) << "Could not create a compute pipeline";
1025 return;
1026 }
1027
1028 // Increase score so that we know the pipeline was used for this frame and shouldn't be
1029 // destroyed
1030 computePipeline->increaseScore();
1031
1032 // Record command reference in UBOSet
1033 computePipeline->uboSet()->addRenderCommand(cmd);
1034
1035 // Store association between RV and pipeline
1036 if (auto& pipelines = m_rvToComputePipelines[rv]; !Qt3DCore::contains(destination: pipelines, element: computePipeline))
1037 pipelines.push_back(x: computePipeline);
1038
1039 // Record RHIGraphicsPipeline into command for later use
1040 cmd.pipeline = computePipeline;
1041
1042 // TO DO: Set to true if geometry, shader or render state dirty
1043 bool requiredRebuild = false;
1044
1045 // Note: we can rebuild add/remove things from the QRhiShaderResourceBindings after having
1046 // created the pipeline and rebuild it. Changes should be picked up automatically
1047
1048 // Create pipeline if it doesn't exist or needs to be updated
1049 if (computePipeline->pipeline() == nullptr || requiredRebuild)
1050 buildComputePipelines(computePipeline, rv, command: cmd);
1051}
1052
1053void Renderer::buildComputePipelines(RHIComputePipeline *computePipeline,
1054 RenderView *rv,
1055 const RenderCommand &cmd)
1056{
1057 Q_UNUSED(rv);
1058 auto onFailure = [&] {
1059 qCWarning(Backend) << "Failed to build compute pipeline";
1060 };
1061
1062 PipelineUBOSet *uboSet = computePipeline->uboSet();
1063 RHIShader *shader = cmd.m_rhiShader;
1064
1065 // Setup shaders
1066 const QShader& computeShader = cmd.m_rhiShader->shaderStage(stage: QShader::ComputeStage);
1067 if (!computeShader.isValid())
1068 return onFailure();
1069
1070 // Set Resource Bindings
1071 const std::vector<QRhiShaderResourceBinding> resourceBindings = uboSet->resourceLayout(shader);
1072 QRhiShaderResourceBindings *shaderResourceBindings =
1073 m_submissionContext->rhi()->newShaderResourceBindings();
1074 computePipeline->setShaderResourceBindings(shaderResourceBindings);
1075
1076 shaderResourceBindings->setBindings(first: resourceBindings.cbegin(), last: resourceBindings.cend());
1077 if (!shaderResourceBindings->create()) {
1078 return onFailure();
1079 }
1080
1081 // Create pipeline
1082 QRhiComputePipeline *pipeline = m_submissionContext->rhi()->newComputePipeline();
1083 computePipeline->setPipeline(pipeline);
1084
1085 pipeline->setShaderStage(QRhiShaderStage{QRhiShaderStage::Compute, computeShader});
1086 pipeline->setShaderResourceBindings(shaderResourceBindings);
1087
1088 // QRhiComputePiple has no render states
1089
1090 if (!pipeline->create())
1091 return onFailure();
1092}
1093
1094void Renderer::createRenderTarget(RenderTarget *target)
1095{
1096 const Qt3DCore::QNodeId &renderTargetId = target->peerId();
1097 RHIRenderTargetManager *rhiRenderTargetManager = m_RHIResourceManagers->rhiRenderTargetManager();
1098
1099 Q_ASSERT(!m_RHIResourceManagers->rhiRenderTargetManager()->contains(renderTargetId));
1100 RHIRenderTarget *rhiTarget = rhiRenderTargetManager->getOrCreateResource(id: renderTargetId);
1101
1102 RHITextureManager *texman = rhiResourceManagers()->rhiTextureManager();
1103 // TO DO: We use all render targets and ignore the fact that
1104 // QRenderTargetSelector can specify a subset of outputs
1105 // -> We should propably remove that from the frontend API
1106 // as it's hard to handle for us and very unlikely anyone uses that
1107 const AttachmentPack pack = AttachmentPack(target, m_nodesManager->attachmentManager());
1108 QRhiTextureRenderTargetDescription desc;
1109
1110 QSize targetSize{};
1111 int targetSamples{1};
1112 QVarLengthArray<QRhiColorAttachment, 8> rhiAttachments;
1113
1114 bool hasDepthTexture = false;
1115
1116 // Used in case of failure
1117 QVarLengthArray<QRhiResource*> resourcesToClean;
1118 auto cleanAllocatedResources = [&] {
1119 QStringList descDetails;
1120 auto texDetails = [](QRhiTexture *tex) {
1121 return QString("Texture format: %1; flags: %2; samples: %3").arg(a: tex->format()).arg(a: tex->flags()).arg(a: tex->sampleCount());
1122 };
1123 auto bufferDetails = [](QRhiRenderBuffer* buffer) {
1124 return QString("Buffer Type: %1; flags: %2; samples: %3").arg(a: buffer->type()).arg(a: buffer->flags()).arg(a: buffer->sampleCount());
1125 };
1126 const auto itEnd = desc.cendColorAttachments();
1127 for (auto it = desc.cbeginColorAttachments(); it != itEnd; ++it) {
1128 QString attDetails = QString("Layer: %1; Level: %2; ").arg(a: it->layer()).arg(a: it->level());
1129 if (it->texture())
1130 attDetails += texDetails(it->texture());
1131 descDetails << attDetails;
1132 }
1133 if (desc.depthTexture())
1134 descDetails << QString("Depth Texture: %1").arg(a: texDetails(desc.depthTexture()));
1135 if (desc.depthStencilBuffer())
1136 descDetails << QString("Depth Buffer: %1").arg(a: bufferDetails(desc.depthStencilBuffer()));
1137 qCWarning(Backend) << "Failed to create RenderTarget" << renderTargetId << "\n" << descDetails;
1138 for (auto res : resourcesToClean) {
1139 res->destroy();
1140 delete res;
1141 }
1142 };
1143
1144 // Look up attachments to populate the RT description
1145 // Attachments are sorted by attachment point (Color0 is first)
1146 for (const Attachment &attachment : pack.attachments()) {
1147 RHITexture *tex = texman->lookupResource(id: attachment.m_textureUuid);
1148 if (tex && tex->getRhiTexture()) {
1149 auto rhiTex = tex->getRhiTexture();
1150 if (!rhiTex->flags().testFlag(flag: QRhiTexture::RenderTarget) ||
1151 !rhiTex->flags().testFlag(flag: QRhiTexture::UsedAsTransferSource)) {
1152 // UsedAsTransferSource is required if we ever want to read back from the texture
1153 rhiTex->destroy();
1154 rhiTex->setFlags(rhiTex->flags() | QRhiTexture::RenderTarget|QRhiTexture::UsedAsTransferSource);
1155 rhiTex->create();
1156 }
1157 switch (rhiTex->format()) {
1158 case QRhiTexture::Format::D16:
1159 case QRhiTexture::Format::D24:
1160 case QRhiTexture::Format::D24S8:
1161 case QRhiTexture::Format::D32F: {
1162 desc.setDepthTexture(rhiTex);
1163 targetSize = tex->size();
1164 hasDepthTexture = true;
1165 break;
1166 }
1167 default: {
1168 QRhiColorAttachment rhiAtt{rhiTex};
1169 // TODO handle cubemap face
1170 targetSize = tex->size();
1171 targetSamples = tex->properties().samples;
1172
1173 rhiAtt.setLayer(attachment.m_layer);
1174 rhiAtt.setLevel(attachment.m_mipLevel);
1175 rhiAttachments.push_back(t: rhiAtt);
1176
1177 break;
1178 }
1179 }
1180 } else {
1181 cleanAllocatedResources();
1182 return;
1183 }
1184 }
1185
1186 if (targetSize.width() <= 0 || targetSize.height() <= 0) {
1187 cleanAllocatedResources();
1188 return;
1189 }
1190
1191 desc.setColorAttachments(first: rhiAttachments.begin(), last: rhiAttachments.end());
1192
1193 // Potentially create a depth & stencil renderbuffer
1194 QRhiRenderBuffer *ds{};
1195 if (!hasDepthTexture) {
1196 ds = m_submissionContext->rhi()->newRenderBuffer(type: QRhiRenderBuffer::DepthStencil, pixelSize: targetSize, sampleCount: targetSamples);
1197 resourcesToClean << ds;
1198
1199 if (!ds->create()) {
1200 cleanAllocatedResources();
1201 return;
1202 }
1203 desc.setDepthStencilBuffer(ds);
1204 }
1205
1206 // Create the render target
1207 auto rt = m_submissionContext->rhi()->newTextureRenderTarget(desc);
1208 resourcesToClean << rt;
1209
1210 auto rp = rt->newCompatibleRenderPassDescriptor();
1211 resourcesToClean << rp;
1212
1213 rt->setRenderPassDescriptor(rp);
1214
1215 if (!rt->create()) {
1216 cleanAllocatedResources();
1217 rhiRenderTargetManager->releaseResource(id: renderTargetId);
1218 return;
1219 }
1220
1221 rhiTarget->renderTarget = rt;
1222 rhiTarget->renderPassDescriptor = rp;
1223 rhiTarget->depthStencilBuffer = ds;
1224}
1225
1226bool Renderer::setupRenderTarget(RenderView *rv,
1227 RHIGraphicsPipeline *graphicsPipeline,
1228 QRhiSwapChain *swapchain)
1229{
1230 QRhiGraphicsPipeline *rhiPipeline = graphicsPipeline->pipeline();
1231
1232 const auto &managers = *nodeManagers();
1233 auto &renderTargetManager = *managers.renderTargetManager();
1234
1235 auto *renderTarget = renderTargetManager.lookupResource(id: rv->renderTargetId());
1236 if (renderTarget) {
1237 // Render to texture
1238 const Qt3DCore::QNodeId &renderTargetId = renderTarget->peerId();
1239 RHIRenderTargetManager *rhiRenderTargetManager = m_RHIResourceManagers->rhiRenderTargetManager();
1240 RHIRenderTarget *rhiTarget = rhiRenderTargetManager->lookupResource(id: renderTargetId);
1241
1242 if (!rhiTarget || !rhiTarget->renderTarget) {
1243 qWarning(catFunc: Backend) << "Invalid RenderTarget " << renderTargetId << " for Pipeline";
1244 return false;
1245 }
1246
1247 rhiPipeline->setRenderPassDescriptor(rhiTarget->renderPassDescriptor);
1248 rhiPipeline->setSampleCount(rhiTarget->renderTarget->sampleCount());
1249 return true;
1250 } else if (m_submissionContext->defaultRenderTarget()) {
1251 // Use default RenderTarget if set Default FBO set by Scene3D
1252 QRhiRenderTarget *defaultTarget = m_submissionContext->defaultRenderTarget();;
1253 rhiPipeline->setRenderPassDescriptor(defaultTarget->renderPassDescriptor());
1254 rhiPipeline->setSampleCount(defaultTarget->sampleCount());
1255 return true;
1256 } else {
1257 Q_ASSERT(swapchain);
1258 // Render to the default framebuffer on our swapchain
1259 rhiPipeline->setRenderPassDescriptor(swapchain->renderPassDescriptor());
1260 rhiPipeline->setSampleCount(swapchain->sampleCount());
1261 return true;
1262 }
1263}
1264
1265// When this function is called, we must not be processing the commands for frame n+1
1266std::vector<Renderer::RHIPassInfo>
1267Renderer::prepareCommandsSubmission(const std::vector<RenderView *> &renderViews)
1268{
1269 const size_t renderViewCount = renderViews.size();
1270
1271 // Gather all distinct RHIGraphicsPipeline we will use
1272 // For each RenderView, we will need to gather
1273 // -> The RHIGraphicsPipelines being used
1274 // -> The number of RenderCommands used by each pipeline
1275
1276 // This will allows us to generate UBOs based on the number of commands/rv we have
1277 RHIGraphicsPipelineManager *graphicsPipelineManager = m_RHIResourceManagers->rhiGraphicsPipelineManager();
1278 const std::vector<HRHIGraphicsPipeline> &graphicsPipelinesHandles = graphicsPipelineManager->activeHandles();
1279 for (HRHIGraphicsPipeline pipelineHandle : graphicsPipelinesHandles) {
1280 RHIGraphicsPipeline *pipeline = graphicsPipelineManager->data(handle: pipelineHandle);
1281 // Reset PipelineUBOSet
1282 pipeline->uboSet()->clear();
1283 }
1284 RHIComputePipelineManager *computePipelineManager = m_RHIResourceManagers->rhiComputePipelineManager();
1285 const std::vector<HRHIComputePipeline> &computePipelinesHandles = computePipelineManager->activeHandles();
1286 for (HRHIComputePipeline pipelineHandle : computePipelinesHandles) {
1287 RHIComputePipeline *pipeline = computePipelineManager->data(handle: pipelineHandle);
1288 // Reset PipelineUBOSet
1289 pipeline->uboSet()->clear();
1290 }
1291
1292 // Clear any reference between RV and Pipelines we had
1293 // as we are about to rebuild these
1294 m_rvToGraphicsPipelines.clear();
1295 m_rvToComputePipelines.clear();
1296
1297 // We need to have a single RHI RenderPass per RenderTarget
1298 // as creating the pass clears the buffers
1299 // 1) We need to find all adjacents RenderViews that have the same renderTarget
1300 // and submit all of these as part of the same RHI pass
1301 std::vector<RHIPassInfo> rhiPassesInfo;
1302
1303 for (size_t i = 0; i < renderViewCount;) {
1304 std::vector<RenderView *> sameRenderTargetRVs;
1305 RenderView *refRV = renderViews[i];
1306 sameRenderTargetRVs.push_back(x: refRV);
1307
1308 for (i = i + 1; i < renderViewCount; ++i) {
1309 RenderView *curRV = renderViews[i];
1310 if (refRV->renderTargetId() == curRV->renderTargetId()) {
1311 sameRenderTargetRVs.push_back(x: curRV);
1312 } else
1313 break;
1314 }
1315
1316 RHIPassInfo bucket;
1317 bucket.rvs = std::move(sameRenderTargetRVs);
1318 bucket.surface = refRV->surface();
1319 bucket.renderTargetId = refRV->renderTargetId();
1320 rhiPassesInfo.push_back(x: bucket);
1321 }
1322
1323 RHIShaderManager *rhiShaderManager = m_RHIResourceManagers->rhiShaderManager();
1324 // Assign a Graphics Pipeline to each RenderCommand
1325 for (size_t i = 0; i < renderViewCount; ++i) {
1326 RenderView *rv = renderViews[i];
1327
1328 // Handle BlitFrameBufferCase
1329 if (rv->hasBlitFramebufferInfo())
1330 qWarning(catFunc: Backend) << "The RHI backend doesn't support Blit operations. Instead, we recommend drawing a full screen quad with a custom shader and resolving manually.";
1331
1332 rv->forEachCommand(func: [&] (RenderCommand &command) {
1333 // Update/Create GraphicsPipelines
1334 if (command.m_type == RenderCommand::Draw) {
1335 Geometry *rGeometry =
1336 m_nodesManager->data<Geometry, GeometryManager>(handle: command.m_geometry);
1337 GeometryRenderer *rGeometryRenderer =
1338 m_nodesManager->data<GeometryRenderer, GeometryRendererManager>(
1339 handle: command.m_geometryRenderer);
1340
1341 command.m_rhiShader = rhiShaderManager->lookupResource(shaderId: command.m_shaderId);
1342 // By this time shaders should have been loaded
1343 RHIShader *shader = command.m_rhiShader;
1344 if (!shader)
1345 return;
1346
1347 // We should never have inserted a command for which these are null
1348 // in the first place
1349 Q_ASSERT(rGeometry && rGeometryRenderer && shader);
1350
1351 // Unset dirtiness on rGeometryRenderer only
1352 // The rGeometry may be shared by several rGeometryRenderer
1353 // so we cannot unset its dirtiness at this point
1354 if (rGeometryRenderer->isDirty())
1355 rGeometryRenderer->unsetDirty();
1356
1357 updateGraphicsPipeline(cmd&: command, rv);
1358
1359 } else if (command.m_type == RenderCommand::Compute) {
1360 // By this time shaders have been loaded
1361 RHIShader *shader = command.m_rhiShader;
1362 if (!shader)
1363 return;
1364
1365 updateComputePipeline(cmd&: command, rv, renderViewIndex: int(i));
1366 }
1367 });
1368 }
1369
1370 // Now that we know how many pipelines we have and how many RC each pipeline
1371 // has, we can allocate/reallocate UBOs with correct size for each pipelines
1372 for (RenderView *rv : renderViews) {
1373 // Allocate UBOs for pipelines used in current RV
1374 const std::vector<RHIGraphicsPipeline *> &rvGraphicsPipelines = m_rvToGraphicsPipelines[rv];
1375 for (RHIGraphicsPipeline *pipeline : rvGraphicsPipelines)
1376 pipeline->uboSet()->allocateUBOs(ctx: m_submissionContext.data());
1377 // Allocate UBOs for pipelines used in current RV
1378 const std::vector<RHIComputePipeline *> &rvComputePipelines = m_rvToComputePipelines[rv];
1379 for (RHIComputePipeline *pipeline : rvComputePipelines)
1380 pipeline->uboSet()->allocateUBOs(ctx: m_submissionContext.data());
1381 }
1382
1383 // Unset dirtiness on Geometry and Attributes
1384 // Note: we cannot do it in the loop above as we want to be sure that all
1385 // the VAO which reference the geometry/attributes are properly updated
1386 RHI_UNIMPLEMENTED;
1387 for (Attribute *attribute : std::as_const(t&: m_dirtyAttributes))
1388 attribute->unsetDirty();
1389 m_dirtyAttributes.clear();
1390
1391 for (Geometry *geometry : std::as_const(t&: m_dirtyGeometry))
1392 geometry->unsetDirty();
1393 m_dirtyGeometry.clear();
1394
1395 return rhiPassesInfo;
1396}
1397
1398// Executed in a job
1399void Renderer::lookForDirtyBuffers()
1400{
1401 const std::vector<HBuffer> &activeBufferHandles = m_nodesManager->bufferManager()->activeHandles();
1402 for (const HBuffer &handle : activeBufferHandles) {
1403 Buffer *buffer = m_nodesManager->bufferManager()->data(handle);
1404 if (buffer->isDirty())
1405 m_dirtyBuffers.push_back(x: handle);
1406 }
1407}
1408
1409// Called in prepareSubmission
1410void Renderer::lookForDownloadableBuffers()
1411{
1412 m_downloadableBuffers.clear();
1413 const std::vector<HBuffer> &activeBufferHandles = m_nodesManager->bufferManager()->activeHandles();
1414 for (const HBuffer &handle : activeBufferHandles) {
1415 Buffer *buffer = m_nodesManager->bufferManager()->data(handle);
1416 if (buffer->access() & Qt3DCore::QBuffer::Read)
1417 m_downloadableBuffers.push_back(x: buffer->peerId());
1418 }
1419}
1420
1421// Executed in a job
1422void Renderer::lookForDirtyTextures()
1423{
1424 // To avoid having Texture or TextureImage maintain relationships between
1425 // one another, we instead perform a lookup here to check if a texture
1426 // image has been updated to then notify textures referencing the image
1427 // that they need to be updated
1428 TextureImageManager *imageManager = m_nodesManager->textureImageManager();
1429 const std::vector<HTextureImage> &activeTextureImageHandles = imageManager->activeHandles();
1430 Qt3DCore::QNodeIdVector dirtyImageIds;
1431 for (const HTextureImage &handle : activeTextureImageHandles) {
1432 TextureImage *image = imageManager->data(handle);
1433 if (image->isDirty()) {
1434 dirtyImageIds.push_back(t: image->peerId());
1435 image->unsetDirty();
1436 }
1437 }
1438
1439 TextureManager *textureManager = m_nodesManager->textureManager();
1440 const std::vector<HTexture> &activeTextureHandles = textureManager->activeHandles();
1441 for (const HTexture &handle : activeTextureHandles) {
1442 Texture *texture = textureManager->data(handle);
1443 const Qt3DCore::QNodeIdVector imageIds = texture->textureImageIds();
1444
1445 // Does the texture reference any of the dirty texture images?
1446 for (const Qt3DCore::QNodeId &imageId : imageIds) {
1447 if (dirtyImageIds.contains(t: imageId)) {
1448 texture->addDirtyFlag(flags: Texture::DirtyImageGenerators);
1449 break;
1450 }
1451 }
1452
1453 // Dirty meaning that something has changed on the texture
1454 // either properties, parameters, shared texture id, generator or a texture image
1455 if (texture->dirtyFlags() != Texture::NotDirty)
1456 m_dirtyTextures.push_back(x: handle);
1457 // Note: texture dirty flags are reset when actually updating the
1458 // textures in updateGLResources() as resetting flags here would make
1459 // us lose information about what was dirty exactly.
1460 }
1461}
1462
1463// Executed in a job
1464void Renderer::reloadDirtyShaders()
1465{
1466 Q_ASSERT(isRunning());
1467 const std::vector<HTechnique> &activeTechniques =
1468 m_nodesManager->techniqueManager()->activeHandles();
1469 const std::vector<HShaderBuilder> &activeBuilders =
1470 m_nodesManager->shaderBuilderManager()->activeHandles();
1471 for (const HTechnique &techniqueHandle : activeTechniques) {
1472 Technique *technique = m_nodesManager->techniqueManager()->data(handle: techniqueHandle);
1473 // If api of the renderer matches the one from the technique
1474 if (technique->isCompatibleWithRenderer()) {
1475 const auto passIds = technique->renderPasses();
1476 for (const Qt3DCore::QNodeId &passId : passIds) {
1477 RenderPass *renderPass =
1478 m_nodesManager->renderPassManager()->lookupResource(id: passId);
1479 HShader shaderHandle =
1480 m_nodesManager->shaderManager()->lookupHandle(id: renderPass->shaderProgram());
1481 Shader *shader = m_nodesManager->shaderManager()->data(handle: shaderHandle);
1482
1483 ShaderBuilder *shaderBuilder = nullptr;
1484 for (const HShaderBuilder &builderHandle : activeBuilders) {
1485 ShaderBuilder *builder =
1486 m_nodesManager->shaderBuilderManager()->data(handle: builderHandle);
1487 if (builder->shaderProgramId() == shader->peerId()) {
1488 shaderBuilder = builder;
1489 break;
1490 }
1491 }
1492
1493 if (shaderBuilder) {
1494 shaderBuilder->setGraphicsApi(*technique->graphicsApiFilter());
1495
1496 for (int i = 0; i <= QShaderProgram::Compute; i++) {
1497 const auto shaderType = static_cast<QShaderProgram::ShaderType>(i);
1498 if (!shaderBuilder->shaderGraph(type: shaderType).isValid())
1499 continue;
1500
1501 if (shaderBuilder->isShaderCodeDirty(type: shaderType)) {
1502 shaderBuilder->generateCode(type: shaderType);
1503 Qt3DCore::moveAtEnd(destination&: m_shaderBuilderUpdates, source: shaderBuilder->takePendingUpdates());
1504 }
1505
1506 const auto code = shaderBuilder->shaderCode(type: shaderType);
1507 shader->setShaderCode(type: shaderType, code);
1508 }
1509 }
1510
1511 if (shader != nullptr && shader->isDirty()) {
1512 if (!Qt3DCore::contains(destination: m_dirtyShaders, element: shaderHandle))
1513 m_dirtyShaders.push_back(x: shaderHandle);
1514 }
1515 }
1516 }
1517 }
1518}
1519
1520// Executed in job postFrame (in main thread when jobs are done)
1521void Renderer::sendShaderChangesToFrontend(Qt3DCore::QAspectManager *manager)
1522{
1523 Q_ASSERT(isRunning());
1524
1525 // Sync Shader
1526 const std::vector<HShader> &activeShaders = m_nodesManager->shaderManager()->activeHandles();
1527 for (const HShader &handle : activeShaders) {
1528 Shader *s = m_nodesManager->shaderManager()->data(handle);
1529 if (!s)
1530 continue;
1531
1532 if (s->requiresFrontendSync()) {
1533 QShaderProgram *frontend =
1534 static_cast<decltype(frontend)>(manager->lookupNode(id: s->peerId()));
1535 if (frontend) {
1536 QShaderProgramPrivate *dFrontend =
1537 static_cast<decltype(dFrontend)>(Qt3DCore::QNodePrivate::get(q: frontend));
1538 dFrontend->setStatus(s->status());
1539 dFrontend->setLog(s->log());
1540 s->unsetRequiresFrontendSync();
1541 }
1542 }
1543 }
1544
1545 // Sync ShaderBuilder
1546 for (const ShaderBuilderUpdate &update : m_shaderBuilderUpdates) {
1547 QShaderProgramBuilder *builder =
1548 static_cast<decltype(builder)>(manager->lookupNode(id: update.builderId));
1549 if (!builder)
1550 continue;
1551
1552 QShaderProgramBuilderPrivate *dBuilder =
1553 static_cast<decltype(dBuilder)>(Qt3DCore::QNodePrivate::get(q: builder));
1554 dBuilder->setShaderCode(code: update.shaderCode, type: update.shaderType);
1555 }
1556 m_shaderBuilderUpdates.clear();
1557}
1558
1559// Executed in a job postFrame (in main thread when jobs are done)
1560void Renderer::sendTextureChangesToFrontend(Qt3DCore::QAspectManager *manager)
1561{
1562 const std::vector<QPair<Texture::TextureUpdateInfo, Qt3DCore::QNodeIdVector>>
1563 updateTextureProperties = std::move(m_updatedTextureProperties);
1564 for (const auto &pair : updateTextureProperties) {
1565 const Qt3DCore::QNodeIdVector targetIds = pair.second;
1566 for (const Qt3DCore::QNodeId &targetId : targetIds) {
1567 // Lookup texture
1568 Texture *t = m_nodesManager->textureManager()->lookupResource(id: targetId);
1569 // If backend texture is Dirty, some property has changed and the properties we are
1570 // about to send are already outdate
1571 if (t == nullptr || t->dirtyFlags() != Texture::NotDirty)
1572 continue;
1573
1574 QAbstractTexture *texture =
1575 static_cast<QAbstractTexture *>(manager->lookupNode(id: targetId));
1576 if (!texture)
1577 continue;
1578 const TextureProperties &properties = pair.first.properties;
1579
1580 const bool blocked = texture->blockNotifications(block: true);
1581 texture->setWidth(properties.width);
1582 texture->setHeight(properties.height);
1583 texture->setDepth(properties.depth);
1584 texture->setLayers(properties.layers);
1585 texture->setFormat(properties.format);
1586 texture->blockNotifications(block: blocked);
1587
1588 QAbstractTexturePrivate *dTexture =
1589 static_cast<QAbstractTexturePrivate *>(Qt3DCore::QNodePrivate::get(q: texture));
1590 dTexture->setStatus(properties.status);
1591 dTexture->setHandleType(pair.first.handleType);
1592 dTexture->setHandle(pair.first.handle);
1593 }
1594 }
1595}
1596
1597// Executed in a job postFrame (in main thread when jobs done)
1598void Renderer::sendDisablesToFrontend(Qt3DCore::QAspectManager *manager)
1599{
1600 // SubtreeEnabled
1601 const auto updatedDisables = Qt3DCore::moveAndClear(data&: m_updatedDisableSubtreeEnablers);
1602 for (const auto &nodeId : updatedDisables) {
1603 QSubtreeEnabler *frontend = static_cast<decltype(frontend)>(manager->lookupNode(id: nodeId));
1604 frontend->setEnabled(false);
1605 }
1606
1607 // Compute Commands
1608 const std::vector<HComputeCommand> &activeCommands =
1609 m_nodesManager->computeJobManager()->activeHandles();
1610 for (const HComputeCommand &handle : activeCommands) {
1611 ComputeCommand *c = m_nodesManager->computeJobManager()->data(handle);
1612 if (c->hasReachedFrameCount()) {
1613 QComputeCommand *frontend =
1614 static_cast<decltype(frontend)>(manager->lookupNode(id: c->peerId()));
1615 frontend->setEnabled(false);
1616 c->resetHasReachedFrameCount();
1617 }
1618 }
1619}
1620
1621bool Renderer::prepareGeometryInputBindings(const Geometry *geometry, const RHIShader *shader,
1622 QVarLengthArray<QRhiVertexInputBinding, 8> &inputBindings,
1623 QVarLengthArray<QRhiVertexInputAttribute, 8> &rhiAttributes,
1624 QHash<int, int> &attributeNameToBinding)
1625{
1626 // shader requires no attributes
1627 if (shader->attributes().size() == 0)
1628 return true;
1629
1630 // QRhiVertexInputBinding -> specifies the stride of an attribute,
1631 // whether it's per vertex or per instance and the instance divisor
1632
1633 // QRhiVertexInputAttribute -> specifies the format of the attribute
1634 // (offset, type), the shader location and the index of the binding
1635 // QRhiCommandBuffer::VertexInput -> binds a buffer to a binding
1636 struct BufferBinding
1637 {
1638 Qt3DCore::QNodeId bufferId;
1639 uint stride;
1640 QRhiVertexInputBinding::Classification classification;
1641 uint attributeDivisor;
1642 };
1643 std::vector<BufferBinding> uniqueBindings;
1644
1645 const auto &attributesIds = geometry->attributes();
1646
1647 for (Qt3DCore::QNodeId attribute_id : attributesIds) {
1648 Attribute *attrib = m_nodesManager->attributeManager()->lookupResource(id: attribute_id);
1649 if (attrib->attributeType() != Qt3DCore::QAttribute::VertexAttribute)
1650 continue;
1651 const int location = locationForAttribute(attr: attrib, shader);
1652 // In case the shader doesn't use the attribute, we would get no
1653 // location. This is not a failure, just that we provide more attributes
1654 // than required.
1655 if (location == -1)
1656 continue;
1657
1658 const bool isPerInstanceAttr = attrib->divisor() != 0;
1659 const QRhiVertexInputBinding::Classification classification = isPerInstanceAttr
1660 ? QRhiVertexInputBinding::PerInstance
1661 : QRhiVertexInputBinding::PerVertex;
1662
1663 auto getAttributeByteSize = [](const Qt3DCore::QAttribute::VertexBaseType type) {
1664 switch (type) {
1665 case Qt3DCore::QAttribute::Byte:
1666 case Qt3DCore::QAttribute::UnsignedByte:
1667 return 1;
1668 case Qt3DCore::QAttribute::Short:
1669 case Qt3DCore::QAttribute::UnsignedShort:
1670 case Qt3DCore::QAttribute::HalfFloat:
1671 return 2;
1672 case Qt3DCore::QAttribute::Int:
1673 case Qt3DCore::QAttribute::UnsignedInt:
1674 case Qt3DCore::QAttribute::Float:
1675 return 4;
1676 case Qt3DCore::QAttribute::Double:
1677 return 8;
1678 }
1679 return 0;
1680 };
1681
1682 uint byteStride = attrib->byteStride();
1683 const uint vertexTypeByteSize = getAttributeByteSize(attrib->vertexBaseType());
1684 // in GL 0 means tighly packed, we therefore assume a tighly packed
1685 // attribute and compute the stride if that happens
1686 if (byteStride == 0)
1687 byteStride = attrib->vertexSize() * vertexTypeByteSize;
1688
1689 const BufferBinding binding = { .bufferId: attrib->bufferId(), .stride: byteStride,
1690 .classification: classification,
1691 .attributeDivisor: isPerInstanceAttr ? attrib->divisor() : 1U };
1692
1693
1694 const auto it = std::find_if(first: uniqueBindings.begin(), last: uniqueBindings.end(),
1695 pred: [binding](const BufferBinding &a) {
1696 return binding.bufferId == a.bufferId
1697 && binding.stride == a.stride
1698 && binding.classification == a.classification
1699 && binding.attributeDivisor == a.attributeDivisor;
1700 });
1701
1702 int bindingIndex = int(uniqueBindings.size());
1703 if (it == uniqueBindings.end())
1704 uniqueBindings.push_back(x: binding);
1705 else
1706 bindingIndex = std::distance(first: uniqueBindings.begin(), last: it);
1707
1708 const auto attributeType = rhiAttributeType(attr: attrib);
1709 if (!attributeType) {
1710 qCWarning(Backend) << "An attribute type is not supported" << attrib->name() << attrib->vertexBaseType();
1711 return false;
1712 }
1713
1714 // Special Handling for Matrix as Vertex Attributes
1715 // If an attribute has a size > 4 it can only be a matrix based type
1716 // for which we will actually upload several attributes at contiguous
1717 // locations
1718 const uint elementsPerColumn = 4;
1719 const int attributeSpan = std::ceil(x: float(attrib->vertexSize()) / elementsPerColumn);
1720 for (int i = 0; i < attributeSpan; ++i) {
1721 rhiAttributes.push_back(t: { bindingIndex,
1722 location + i,
1723 *attributeType,
1724 attrib->byteOffset() + (i * elementsPerColumn * vertexTypeByteSize)});
1725 }
1726
1727 attributeNameToBinding.insert(key: attrib->nameId(), value: bindingIndex);
1728 }
1729
1730 inputBindings.resize(sz: uniqueBindings.size());
1731 for (int i = 0, m = int(uniqueBindings.size()); i < m; ++i) {
1732 const BufferBinding binding = uniqueBindings.at(n: i);
1733
1734 /*
1735 qDebug() << "binding"
1736 << binding.bufferId
1737 << binding.stride
1738 << binding.classification
1739 << binding.attributeDivisor;
1740 //*/
1741
1742 inputBindings[i] = QRhiVertexInputBinding{ binding.stride, binding.classification,
1743 binding.attributeDivisor };
1744 }
1745
1746 return true;
1747}
1748
1749// Render Thread (or QtQuick RenderThread when using Scene3D)
1750// Scene3D: When using Scene3D rendering, we can't assume that when
1751// updateGLResources is called, the resource handles points to still existing
1752// objects. This is because Scene3D calls doRender independently of whether all
1753// jobs have completed or not which in turn calls proceedToNextFrame under some
1754// conditions. Such conditions are usually met on startup to avoid deadlocks.
1755// proceedToNextFrame triggers the syncChanges calls for the next frame, which
1756// may contain destruction changes targeting resources. When the above
1757// happens, this can result in the dirtyResource vectors containing handles of
1758// objects that may already have been destroyed
1759void Renderer::updateResources()
1760{
1761 {
1762 const std::vector<HBuffer> dirtyBufferHandles = Qt3DCore::moveAndClear(data&: m_dirtyBuffers);
1763 for (const HBuffer &handle : dirtyBufferHandles) {
1764 Buffer *buffer = m_nodesManager->bufferManager()->data(handle);
1765
1766 // Can be null when using Scene3D rendering
1767 if (buffer == nullptr)
1768 continue;
1769
1770 // Forces creation if it doesn't exit
1771 // Also note the binding point doesn't really matter here, we just upload data
1772 if (!m_submissionContext->hasRHIBufferForBuffer(buffer))
1773 m_submissionContext->rhiBufferForRenderBuffer(buf: buffer);
1774 // Update the RHIBuffer data
1775 m_submissionContext->updateBuffer(buffer);
1776 buffer->unsetDirty();
1777 }
1778 }
1779
1780 RHIGraphicsPipelineManager *graphicsPipelineManager = m_RHIResourceManagers->rhiGraphicsPipelineManager();
1781 RHIComputePipelineManager *computePipelineManager = m_RHIResourceManagers->rhiComputePipelineManager();
1782
1783 {
1784 const std::vector<HShader> dirtyShaderHandles = Qt3DCore::moveAndClear(data&: m_dirtyShaders);
1785 ShaderManager *shaderManager = m_nodesManager->shaderManager();
1786 for (const HShader &handle : dirtyShaderHandles) {
1787 Shader *shader = shaderManager->data(handle);
1788
1789 // Can be null when using Scene3D rendering
1790 if (shader == nullptr)
1791 continue;
1792
1793 // Compile shader
1794 m_submissionContext->loadShader(shader, shaderManager,
1795 rhiShaderManager: m_RHIResourceManagers->rhiShaderManager());
1796
1797 // Release pipelines that reference the shaderId
1798 // to ensure they get rebuilt with updated shader
1799 graphicsPipelineManager->releasePipelinesReferencingShader(shaderId: shader->peerId());
1800 computePipelineManager->releasePipelinesReferencingShader(shaderId: shader->peerId());
1801 }
1802 }
1803
1804 std::vector<RHITexture *> updatedRHITextures;
1805
1806 // Create/Update textures. We record the update info to later fill
1807 // m_updatedTextureProperties once we are use the RHITextures have been
1808 // fully created (as creating the RenderTargets below could change existing
1809 // RHITextures)
1810 {
1811 const std::vector<HTexture> activeTextureHandles = Qt3DCore::moveAndClear(data&: m_dirtyTextures);
1812 for (const HTexture &handle : activeTextureHandles) {
1813 Texture *texture = m_nodesManager->textureManager()->data(handle);
1814
1815 // Can be null when using Scene3D rendering
1816 if (texture == nullptr)
1817 continue;
1818
1819 // Create or Update RHITexture (the RHITexture instance is created if required
1820 // and all things that can take place without a GL context are done here)
1821 updateTexture(texture);
1822 }
1823 // We want to upload textures data at this point as the SubmissionThread and
1824 // AspectThread are locked ensuring no races between Texture/TextureImage and
1825 // RHITexture
1826 if (m_submissionContext != nullptr) {
1827 RHITextureManager *rhiTextureManager = m_RHIResourceManagers->rhiTextureManager();
1828 const std::vector<HRHITexture> &rhiTextureHandles = rhiTextureManager->activeHandles();
1829 // Upload texture data
1830 for (const HRHITexture &rhiTextureHandle : rhiTextureHandles) {
1831 RHI_UNIMPLEMENTED;
1832 RHITexture *rhiTexture = rhiTextureManager->data(handle: rhiTextureHandle);
1833
1834 // We create/update the actual RHI texture using the RHI context at this point
1835 const RHITexture::TextureUpdateInfo info =
1836 rhiTexture->createOrUpdateRhiTexture(ctx: m_submissionContext.data());
1837
1838 if (info.wasUpdated) {
1839 // RHITexture creation provides us width/height/format ... information
1840 // for textures which had not initially specified these information
1841 // (TargetAutomatic...) Gather these information and store them to be distributed by
1842 // a change next frame
1843 const Qt3DCore::QNodeIdVector referenceTextureIds = { rhiTextureManager->texNodeIdForRHITexture.value(key: rhiTexture) };
1844 // Store properties and referenceTextureIds
1845 Texture::TextureUpdateInfo updateInfo;
1846 updateInfo.properties = info.properties;
1847 // Record texture updates to notify frontend (we are sure at this stage
1848 // that the internal QRHITexture won't be updated further for this frame
1849 m_updatedTextureProperties.push_back(x: { updateInfo, referenceTextureIds });
1850 updatedRHITextures.push_back(x: rhiTexture);
1851 }
1852 }
1853 }
1854
1855 // Record ids of texture to cleanup while we are still blocking the aspect thread
1856 m_textureIdsToCleanup += m_nodesManager->textureManager()->takeTexturesIdsToCleanup();
1857 }
1858
1859
1860 // Find dirty renderTargets
1861 // -> attachments added/removed
1862 // -> attachments textures updated (new dimensions, format ...)
1863 // -> destroy pipelines associated with dirty renderTargets
1864
1865 // Note: we might end up recreating some of the internal textures when
1866 // creating the RenderTarget as those might have been created above without
1867 // the proper RenderTarget/TransformSource flags
1868 {
1869 RHIRenderTargetManager *rhiRenderTargetManager = m_RHIResourceManagers->rhiRenderTargetManager();
1870 RenderTargetManager *renderTargetManager = m_nodesManager->renderTargetManager();
1871 AttachmentManager *attachmentManager = m_nodesManager->attachmentManager();
1872 const std::vector<HTarget> &activeHandles = renderTargetManager->activeHandles();
1873 for (const HTarget &hTarget : activeHandles) {
1874 const Qt3DCore::QNodeId renderTargetId = hTarget->peerId();
1875 bool isDirty = hTarget->isDirty() || !rhiRenderTargetManager->contains(id: renderTargetId);
1876
1877 // Check dirtiness of attachments if RenderTarget is not dirty
1878 if (!isDirty) {
1879 const Qt3DCore::QNodeIdVector &attachmentIds = hTarget->renderOutputs();
1880 for (const Qt3DCore::QNodeId &attachmentId : attachmentIds) {
1881 RenderTargetOutput *output = attachmentManager->lookupResource(id: attachmentId);
1882
1883 auto it = std::find_if(first: m_updatedTextureProperties.begin(),
1884 last: m_updatedTextureProperties.end(),
1885 pred: [&output] (const QPair<Texture::TextureUpdateInfo, Qt3DCore::QNodeIdVector> &updateData) {
1886 const Qt3DCore::QNodeIdVector &referencedTextureIds = updateData.second;
1887 return referencedTextureIds.contains(t: output->textureUuid());
1888 });
1889 // Attachment references a texture which was updated
1890 isDirty = (it != m_updatedTextureProperties.end());
1891 }
1892 }
1893
1894 if (isDirty) {
1895 hTarget->unsetDirty();
1896 // We need to destroy the render target and the pipelines associated with it
1897 // so that they can be recreated
1898 // If the RT was never created, the 2 lines below are noop
1899 graphicsPipelineManager->releasePipelinesReferencingRenderTarget(renderTargetId);
1900 rhiRenderTargetManager->releaseResource(id: renderTargetId);
1901
1902 // Create RenderTarget
1903 createRenderTarget(target: hTarget.data());
1904 }
1905 }
1906 }
1907
1908
1909 // Note: we can only retrieve the internal QRhiResource handle to set on
1910 // the frontend nodes after we are sure we are no going to modify the
1911 // QRhiTextures (which happens when we create the Textures or the
1912 // RenderTargets)
1913 {
1914 for (size_t i = 0, m = m_updatedTextureProperties.size(); i < m; ++i) {
1915 auto &updateInfoPair = m_updatedTextureProperties[i];
1916 RHITexture *rhiTexture = updatedRHITextures[i];
1917 QRhiTexture *qRhiTexture = rhiTexture->getRhiTexture();
1918 Texture::TextureUpdateInfo &updateInfo = updateInfoPair.first;
1919 updateInfo.handleType = QAbstractTexture::RHITextureId;
1920 updateInfo.handle = qRhiTexture ? QVariant(qRhiTexture->nativeTexture().object) : QVariant();
1921 }
1922 }
1923
1924 // Record list of buffer that might need uploading
1925 lookForDownloadableBuffers();
1926}
1927
1928// Render Thread
1929void Renderer::updateTexture(Texture *texture)
1930{
1931 RHI_UNIMPLEMENTED;
1932 // Check that the current texture images are still in place, if not, do not update
1933 const bool isValid = texture->isValid(manager: m_nodesManager->textureImageManager());
1934 if (!isValid) {
1935 qCWarning(Backend) << "QTexture referencing invalid QTextureImages";
1936 return;
1937 }
1938
1939 // All textures are unique, if you instanciate twice the exact same texture
1940 // this will create 2 identical GLTextures, no sharing will take place
1941
1942 // Try to find the associated RHITexture for the backend Texture
1943 RHITextureManager *rhiTextureManager = m_RHIResourceManagers->rhiTextureManager();
1944 RHITexture *rhiTexture = rhiTextureManager->lookupResource(id: texture->peerId());
1945
1946 // No RHITexture associated yet -> create it
1947 if (rhiTexture == nullptr) {
1948 rhiTexture = rhiTextureManager->getOrCreateResource(id: texture->peerId());
1949 rhiTextureManager->texNodeIdForRHITexture.insert(key: rhiTexture, value: texture->peerId());
1950 }
1951
1952 // Update RHITexture to match Texture instance
1953 const Texture::DirtyFlags dirtyFlags = texture->dirtyFlags();
1954 if (dirtyFlags.testFlag(flag: Texture::DirtySharedTextureId))
1955 rhiTexture->setSharedTextureId(texture->sharedTextureId());
1956
1957 if (dirtyFlags.testFlag(flag: Texture::DirtyProperties))
1958 rhiTexture->setProperties(texture->properties());
1959
1960 if (dirtyFlags.testFlag(flag: Texture::DirtyParameters))
1961 rhiTexture->setParameters(texture->parameters());
1962
1963 // Will make the texture requestUpload
1964 if (dirtyFlags.testFlag(flag: Texture::DirtyImageGenerators)) {
1965 const Qt3DCore::QNodeIdVector textureImageIds = texture->textureImageIds();
1966 std::vector<RHITexture::Image> images;
1967 images.reserve(n: textureImageIds.size());
1968 // TODO: Move this into RHITexture directly
1969 for (const Qt3DCore::QNodeId &textureImageId : textureImageIds) {
1970 const TextureImage *img =
1971 m_nodesManager->textureImageManager()->lookupResource(id: textureImageId);
1972 if (img == nullptr) {
1973 qCWarning(Backend) << "invalid TextureImage handle";
1974 } else {
1975 RHITexture::Image glImg { .generator: img->dataGenerator(), .layer: img->layer(), .mipLevel: img->mipLevel(),
1976 .face: img->face() };
1977 images.push_back(x: glImg);
1978 }
1979 }
1980 rhiTexture->setImages(images);
1981 }
1982
1983 // Will make the texture requestUpload
1984 if (dirtyFlags.testFlag(flag: Texture::DirtyDataGenerator))
1985 rhiTexture->setGenerator(texture->dataGenerator());
1986
1987 // Will make the texture requestUpload
1988 if (dirtyFlags.testFlag(flag: Texture::DirtyPendingDataUpdates))
1989 rhiTexture->addTextureDataUpdates(updates: texture->takePendingTextureDataUpdates());
1990
1991 // Unset the dirty flag on the texture
1992 texture->unsetDirty();
1993}
1994
1995// Render Thread
1996void Renderer::cleanupTexture(Qt3DCore::QNodeId cleanedUpTextureId)
1997{
1998 RHITextureManager *rhiTextureManager = m_RHIResourceManagers->rhiTextureManager();
1999 RHITexture *glTexture = rhiTextureManager->lookupResource(id: cleanedUpTextureId);
2000
2001 // Destroying the RHITexture implicitely also destroy the GL resources
2002 if (glTexture != nullptr) {
2003 rhiTextureManager->releaseResource(id: cleanedUpTextureId);
2004 rhiTextureManager->texNodeIdForRHITexture.remove(key: glTexture);
2005 }
2006}
2007
2008// Render Thread
2009void Renderer::cleanupShader(const Shader *shader)
2010{
2011 RHIShaderManager *rhiShaderManager = m_RHIResourceManagers->rhiShaderManager();
2012 RHIShader *glShader = rhiShaderManager->lookupResource(shaderId: shader->peerId());
2013
2014 if (glShader != nullptr)
2015 rhiShaderManager->abandon(apiShader: glShader, shader);
2016}
2017
2018void Renderer::cleanupRenderTarget(const Qt3DCore::QNodeId &renderTargetId)
2019{
2020 RHIRenderTargetManager *rhiRenderTargetManager = m_RHIResourceManagers->rhiRenderTargetManager();
2021
2022 rhiRenderTargetManager->releaseResource(id: renderTargetId);
2023}
2024
2025// Called by SubmitRenderView
2026void Renderer::downloadRHIBuffers()
2027{
2028 const std::vector<Qt3DCore::QNodeId> downloadableHandles = Qt3DCore::moveAndClear(data&: m_downloadableBuffers);
2029 for (const Qt3DCore::QNodeId &bufferId : downloadableHandles) {
2030 BufferManager *bufferManager = m_nodesManager->bufferManager();
2031 BufferManager::ReadLocker locker(const_cast<const BufferManager *>(bufferManager));
2032 Buffer *buffer = bufferManager->lookupResource(id: bufferId);
2033 // Buffer could have been destroyed at this point
2034 if (!buffer)
2035 continue;
2036 // locker is protecting us from the buffer being destroy while we're looking
2037 // up its content
2038 const QByteArray content = m_submissionContext->downloadBufferContent(buffer);
2039 m_sendBufferCaptureJob->addRequest(request: QPair<Qt3DCore::QNodeId, QByteArray>(bufferId, content));
2040 }
2041}
2042
2043// Happens in RenderThread context when all RenderViewJobs are done
2044// Returns the id of the last bound FBO
2045Renderer::ViewSubmissionResultData
2046Renderer::submitRenderViews(const std::vector<RHIPassInfo> &rhiPassesInfo)
2047{
2048 QElapsedTimer timer;
2049 quint64 queueElapsed = 0;
2050 timer.start();
2051
2052 quint64 frameElapsed = queueElapsed;
2053 m_lastFrameCorrect.storeRelaxed(newValue: 1); // everything fine until now.....
2054
2055 qCDebug(Memory) << Q_FUNC_INFO << "rendering frame ";
2056
2057 // We might not want to render on the default FBO
2058 QSurface *surface = nullptr;
2059 QSurface *previousSurface = nullptr;
2060 QSurface *lastUsedSurface = nullptr;
2061
2062 const size_t rhiPassesCount = rhiPassesInfo.size();
2063
2064 for (size_t i = 0; i < rhiPassesCount; ++i) {
2065 // Initialize GraphicsContext for drawing
2066 const RHIPassInfo &rhiPassInfo = rhiPassesInfo.at(n: i);
2067
2068 // Initialize Previous surface the first time we enter this loop
2069 if (i == 0) {
2070 for (const RenderView *rv : rhiPassInfo.rvs) {
2071 previousSurface = rv->surface();
2072 if (previousSurface)
2073 break;
2074 }
2075 }
2076
2077 // Check if using the same surface as the previous RHIPassInfo.
2078 // If not, we have to free up the context from the previous surface
2079 // and make the context current on the new surface
2080 surface = rhiPassInfo.surface;
2081 SurfaceLocker surfaceLock(surface);
2082
2083 // TO DO: Make sure that the surface we are rendering too has not been unset
2084
2085 // For now, if we do not have a surface, skip this rhipassinfo
2086 // TODO: Investigate if it's worth providing a fallback offscreen surface
2087 // to use when surface is null. Or if we should instead expose an
2088 // offscreensurface to Qt3D.
2089 if (!surface || !surfaceLock.isSurfaceValid()) {
2090 m_lastFrameCorrect.storeRelaxed(newValue: 0);
2091 continue;
2092 }
2093
2094 lastUsedSurface = surface;
2095 const bool surfaceHasChanged = surface != previousSurface;
2096
2097 if (surfaceHasChanged && previousSurface) {
2098 // TO DO: Warn that this likely won't work with Scene3D
2099
2100 // TODO what should be the swapBuffers condition for RHI ?
2101 // lastRenderTarget == swapChain->renderTarget or something like that ?
2102 const bool swapBuffers = surfaceLock.isSurfaceValid() && m_shouldSwapBuffers;
2103 // We only call swap buffer if we are sure the previous surface is still valid
2104 m_submissionContext->endDrawing(swapBuffers);
2105 }
2106
2107 if (surfaceHasChanged) {
2108 // TO DO: Warn that this likely won't work with Scene3D
2109
2110 // If we can't make the context current on the surface, skip to the
2111 // next RenderView. We won't get the full frame but we may get something
2112 if (!m_submissionContext->beginDrawing(surface)) {
2113 qCWarning(Backend) << "Failed to make RHI context current on surface";
2114 m_lastFrameCorrect.storeRelaxed(newValue: 0);
2115 continue;
2116 }
2117
2118 previousSurface = surface;
2119 }
2120
2121 // Execute the render commands
2122 if (!executeCommandsSubmission(passInfo: rhiPassInfo))
2123 m_lastFrameCorrect.storeRelaxed(
2124 newValue: 0); // something went wrong; make sure to render the next frame!
2125
2126 frameElapsed = timer.elapsed() - frameElapsed;
2127 qCDebug(Rendering) << Q_FUNC_INFO << "Submitted RHI Passes " << i + 1 << "/"
2128 << rhiPassesCount << "in " << frameElapsed << "ms";
2129 frameElapsed = timer.elapsed();
2130 }
2131
2132 //* TODO: Shouldn't be needed with RHI ? as FBOs, etc.. are per-pipeline
2133 //* // Bind lastBoundFBOId back. Needed also in threaded mode.
2134 //* // lastBoundFBOId != m_graphicsContext->activeFBO() when the last FrameGraph leaf
2135 //* // node/renderView contains RenderTargetSelector/RenderTarget
2136 //* if (lastBoundFBOId != m_submissionContext->activeFBO()) {
2137 //* RHI_UNIMPLEMENTED;
2138 //* // m_submissionContext->bindFramebuffer(lastBoundFBOId,
2139 //* // GraphicsHelperInterface::FBOReadAndDraw);
2140 //* }
2141
2142 queueElapsed = timer.elapsed() - queueElapsed;
2143 qCDebug(Rendering) << Q_FUNC_INFO << "Submission Completed in " << timer.elapsed() << "ms";
2144
2145 // Stores the necessary information to safely perform
2146 // the last swap buffer call
2147 ViewSubmissionResultData resultData;
2148 resultData.surface = lastUsedSurface;
2149 return resultData;
2150}
2151
2152void Renderer::markDirty(BackendNodeDirtySet changes, BackendNode *node)
2153{
2154 Q_UNUSED(node);
2155 m_dirtyBits.marked |= changes;
2156}
2157
2158Renderer::BackendNodeDirtySet Renderer::dirtyBits()
2159{
2160 return m_dirtyBits.marked;
2161}
2162
2163#if defined(QT_BUILD_INTERNAL)
2164void Renderer::clearDirtyBits(BackendNodeDirtySet changes)
2165{
2166 m_dirtyBits.remaining &= ~changes;
2167 m_dirtyBits.marked &= ~changes;
2168}
2169#endif
2170
2171bool Renderer::shouldRender() const
2172{
2173 // Only render if something changed during the last frame, or the last frame
2174 // was not rendered successfully (or render-on-demand is disabled)
2175 return (m_settings->renderPolicy() == QRenderSettings::Always || m_dirtyBits.marked != 0
2176 || m_dirtyBits.remaining != 0 || !m_lastFrameCorrect.loadRelaxed());
2177}
2178
2179void Renderer::skipNextFrame()
2180{
2181 Q_ASSERT(m_settings->renderPolicy() != QRenderSettings::Always);
2182
2183 // make submitRenderViews() actually run
2184 m_renderQueue.setNoRender();
2185 m_submitRenderViewsSemaphore.release(n: 1);
2186}
2187
2188void Renderer::jobsDone(Qt3DCore::QAspectManager *manager)
2189{
2190 // called in main thread once all jobs are done running
2191
2192 // sync captured renders to frontend
2193 QMutexLocker lock(&m_pendingRenderCaptureSendRequestsMutex);
2194 const std::vector<Qt3DCore::QNodeId> pendingCaptureIds =
2195 Qt3DCore::moveAndClear(data&: m_pendingRenderCaptureSendRequests);
2196 lock.unlock();
2197 for (const Qt3DCore::QNodeId &id : std::as_const(t: pendingCaptureIds)) {
2198 auto *backend = static_cast<Qt3DRender::Render::RenderCapture *>(
2199 m_nodesManager->frameGraphManager()->lookupNode(id));
2200 backend->syncRenderCapturesToFrontend(manager);
2201 }
2202
2203 // Do we need to notify any texture about property changes?
2204 if (m_updatedTextureProperties.size() > 0)
2205 sendTextureChangesToFrontend(manager);
2206
2207 sendDisablesToFrontend(manager);
2208}
2209
2210bool Renderer::processMouseEvent(QObject *object, QMouseEvent *event)
2211{
2212 Q_UNUSED(object);
2213 Q_UNUSED(event);
2214 return false;
2215}
2216
2217bool Renderer::processKeyEvent(QObject *object, QKeyEvent *event)
2218{
2219 Q_UNUSED(object);
2220 Q_UNUSED(event);
2221 return false;
2222}
2223
2224// Jobs we may have to run even if no rendering will happen
2225std::vector<Qt3DCore::QAspectJobPtr> Renderer::preRenderingJobs()
2226{
2227 if (m_sendBufferCaptureJob->hasRequests())
2228 return { m_sendBufferCaptureJob };
2229 else
2230 return {};
2231}
2232
2233// Waits to be told to create jobs for the next frame
2234// Called by QRenderAspect jobsToExecute context of QAspectThread
2235// Returns all the jobs (and with proper dependency chain) required
2236// for the rendering of the scene
2237std::vector<Qt3DCore::QAspectJobPtr> Renderer::renderBinJobs()
2238{
2239 std::vector<Qt3DCore::QAspectJobPtr> renderBinJobs;
2240
2241 // Remove previous dependencies
2242 m_cleanupJob->removeDependency(dependency: QWeakPointer<Qt3DCore::QAspectJob>());
2243
2244 const bool dirtyParametersForCurrentFrame = m_dirtyBits.marked & AbstractRenderer::ParameterDirty;
2245 const BackendNodeDirtySet dirtyBitsForFrame = m_dirtyBits.marked | m_dirtyBits.remaining;
2246 m_dirtyBits.marked = {};
2247 m_dirtyBits.remaining = {};
2248 BackendNodeDirtySet notCleared = {};
2249
2250 // Add jobs
2251 if (dirtyBitsForFrame & AbstractRenderer::TransformDirty) {
2252 renderBinJobs.push_back(x: m_updateShaderDataTransformJob);
2253 }
2254
2255 // TO DO: Conditionally add if skeletons dirty
2256 renderBinJobs.push_back(x: m_cleanupJob);
2257
2258 // Jobs to prepare RHI Resource upload
2259 if (dirtyBitsForFrame & AbstractRenderer::BuffersDirty)
2260 renderBinJobs.push_back(x: m_bufferGathererJob);
2261
2262 if (dirtyBitsForFrame & AbstractRenderer::TexturesDirty)
2263 renderBinJobs.push_back(x: m_textureGathererJob);
2264
2265 // Layer cache is dependent on layers, layer filters (hence FG structure
2266 // changes) and the enabled flag on entities
2267 const bool entitiesEnabledDirty = dirtyBitsForFrame & AbstractRenderer::EntityEnabledDirty;
2268 const bool frameGraphDirty = dirtyBitsForFrame & AbstractRenderer::FrameGraphDirty;
2269 const bool layersDirty = dirtyBitsForFrame & AbstractRenderer::LayersDirty;
2270 const bool layersCacheNeedsToBeRebuilt = layersDirty || entitiesEnabledDirty || frameGraphDirty;
2271 const bool shadersDirty = dirtyBitsForFrame & AbstractRenderer::ShadersDirty;
2272 const bool materialDirty = dirtyBitsForFrame & AbstractRenderer::MaterialDirty;
2273 const bool lightsDirty = dirtyBitsForFrame & AbstractRenderer::LightsDirty;
2274 const bool computeableDirty = dirtyBitsForFrame & AbstractRenderer::ComputeDirty;
2275 const bool renderableDirty = dirtyBitsForFrame & AbstractRenderer::GeometryDirty;
2276 const bool materialCacheNeedsToBeRebuilt = shadersDirty || materialDirty || frameGraphDirty;
2277 const bool renderCommandsDirty =
2278 materialCacheNeedsToBeRebuilt || renderableDirty || computeableDirty;
2279
2280 // Rebuild Entity Layers list if layers are dirty
2281
2282 if (renderableDirty)
2283 renderBinJobs.push_back(x: m_renderableEntityFilterJob);
2284
2285 if (computeableDirty)
2286 renderBinJobs.push_back(x: m_computableEntityFilterJob);
2287
2288 if (lightsDirty)
2289 renderBinJobs.push_back(x: m_lightGathererJob);
2290
2291 QMutexLocker lock(m_renderQueue.mutex());
2292 if (m_renderQueue.wasReset()) { // Have we rendered yet? (Scene3D case)
2293 // Traverse the current framegraph. For each leaf node create a
2294 // RenderView and set its configuration then create a job to
2295 // populate the RenderView with a set of RenderCommands that get
2296 // their details from the RenderNodes that are visible to the
2297 // Camera selected by the framegraph configuration
2298 if (frameGraphDirty) {
2299 FrameGraphVisitor visitor(m_nodesManager->frameGraphManager());
2300 m_frameGraphLeaves = visitor.traverse(root: frameGraphRoot());
2301 // Remove leaf nodes that no longer exist from cache
2302 const QList<FrameGraphNode *> keys = m_cache.leafNodeCache.keys();
2303 for (FrameGraphNode *leafNode : keys) {
2304 if (std::find(first: m_frameGraphLeaves.begin(),
2305 last: m_frameGraphLeaves.end(),
2306 val: leafNode) == m_frameGraphLeaves.end())
2307 m_cache.leafNodeCache.remove(key: leafNode);
2308 }
2309
2310 // Handle single shot subtree enablers
2311 const auto subtreeEnablers = visitor.takeEnablersToDisable();
2312 for (auto *node : subtreeEnablers)
2313 m_updatedDisableSubtreeEnablers.push_back(x: node->peerId());
2314 }
2315
2316 int idealThreadCount = Qt3DCore::QAspectJobManager::idealThreadCount();
2317
2318 const size_t fgBranchCount = m_frameGraphLeaves.size();
2319 if (fgBranchCount > 1) {
2320 int workBranches = int(fgBranchCount);
2321 for (auto leaf: m_frameGraphLeaves)
2322 if (leaf->nodeType() == FrameGraphNode::NoDraw)
2323 --workBranches;
2324
2325 if (idealThreadCount > 4 && workBranches)
2326 idealThreadCount = qMax(a: 4, b: idealThreadCount / workBranches);
2327 }
2328
2329 for (size_t i = 0; i < fgBranchCount; ++i) {
2330 FrameGraphNode *leaf = m_frameGraphLeaves.at(n: i);
2331 RenderViewBuilder builder(leaf, int(i), this);
2332 builder.setOptimalJobCount(leaf->nodeType() == FrameGraphNode::NoDraw ? 1 : idealThreadCount);
2333
2334 // If we have a new RV (wasn't in the cache before, then it contains no cached data)
2335 const bool isNewRV = !m_cache.leafNodeCache.contains(key: leaf);
2336 builder.setLayerCacheNeedsToBeRebuilt(layersCacheNeedsToBeRebuilt || isNewRV);
2337 builder.setMaterialGathererCacheNeedsToBeRebuilt(materialCacheNeedsToBeRebuilt
2338 || isNewRV);
2339 builder.setRenderCommandCacheNeedsToBeRebuilt(renderCommandsDirty || isNewRV);
2340 builder.setLightCacheNeedsToBeRebuilt(lightsDirty);
2341
2342 // Insert leaf into cache
2343 if (isNewRV) {
2344 m_cache.leafNodeCache[leaf] = {};
2345 }
2346
2347 builder.prepareJobs();
2348 Qt3DCore::moveAtEnd(destination&: renderBinJobs, source: builder.buildJobHierachy());
2349 }
2350
2351 // Set target number of RenderViews
2352 m_renderQueue.setTargetRenderViewCount(int(fgBranchCount));
2353 } else {
2354 // FilterLayerEntityJob is part of the RenderViewBuilder jobs and must be run later
2355 // if none of those jobs are started this frame
2356 notCleared |= AbstractRenderer::EntityEnabledDirty;
2357 notCleared |= AbstractRenderer::FrameGraphDirty;
2358 notCleared |= AbstractRenderer::LayersDirty;
2359 }
2360
2361 if (isRunning() && m_submissionContext->isInitialized()) {
2362 if (dirtyBitsForFrame & AbstractRenderer::TechniquesDirty)
2363 renderBinJobs.push_back(x: m_filterCompatibleTechniqueJob);
2364 if (dirtyBitsForFrame & AbstractRenderer::ShadersDirty)
2365 renderBinJobs.push_back(x: m_introspectShaderJob);
2366 } else {
2367 notCleared |= AbstractRenderer::TechniquesDirty;
2368 notCleared |= AbstractRenderer::ShadersDirty;
2369 }
2370
2371 m_dirtyBits.remaining = dirtyBitsForFrame & notCleared;
2372
2373 // Dirty Parameters might need 2 frames to react if the parameter references a texture
2374 if (dirtyParametersForCurrentFrame)
2375 m_dirtyBits.remaining |= AbstractRenderer::ParameterDirty;
2376
2377 return renderBinJobs;
2378}
2379
2380Qt3DCore::QAbstractFrameAdvanceService *Renderer::frameAdvanceService() const
2381{
2382 return static_cast<Qt3DCore::QAbstractFrameAdvanceService *>(m_vsyncFrameAdvanceService.data());
2383}
2384
2385bool Renderer::performCompute(QRhiCommandBuffer *cb, RenderCommand &command)
2386{
2387 RHIComputePipeline *pipeline = command.pipeline.compute();
2388 if (!pipeline)
2389 return true;
2390 cb->setComputePipeline(pipeline->pipeline());
2391
2392 if (!setBindingAndShaderResourcesForCommand(cb, command, uboSet: pipeline->uboSet()))
2393 return false;
2394
2395 const std::vector<QRhiCommandBuffer::DynamicOffset> offsets = pipeline->uboSet()->offsets(command);
2396 cb->setShaderResources(srb: command.shaderResourceBindings,
2397 dynamicOffsetCount: int(offsets.size()),
2398 dynamicOffsets: offsets.data());
2399
2400 cb->dispatch(x: command.m_workGroups[0], y: command.m_workGroups[1], z: command.m_workGroups[2]);
2401 m_dirtyBits.marked |= AbstractRenderer::ComputeDirty;
2402 return true;
2403}
2404
2405static auto rhiIndexFormat(Qt3DCore::QAttribute::VertexBaseType type)
2406{
2407 switch (type) {
2408 case Qt3DCore::QAttribute::VertexBaseType ::UnsignedShort:
2409 return QRhiCommandBuffer::IndexUInt16;
2410 case Qt3DCore::QAttribute::VertexBaseType ::UnsignedInt:
2411 return QRhiCommandBuffer::IndexUInt32;
2412 default:
2413 std::abort();
2414 }
2415}
2416
2417bool Renderer::uploadBuffersForCommand(QRhiCommandBuffer *cb, const RenderView *rv,
2418 RenderCommand &command)
2419{
2420 Q_UNUSED(cb);
2421 Q_UNUSED(rv);
2422
2423 struct
2424 {
2425 Renderer &self;
2426 RenderCommand &command;
2427 bool operator()(RHIGraphicsPipeline* pipeline) const noexcept {
2428 if (!pipeline)
2429 return true;
2430
2431 return self.uploadBuffersForCommand(graphics: pipeline, command);
2432 }
2433 bool operator()(RHIComputePipeline* pipeline) const noexcept {
2434 if (!pipeline)
2435 return true;
2436
2437 return self.uploadBuffersForCommand(compute: pipeline, command);
2438 }
2439 bool operator()(std::monostate) {
2440 return false;
2441 }
2442 } vis{.self: *this, .command: command};
2443
2444 if (!command.pipeline.visit(f&: vis))
2445 return false;
2446
2447 for (const BlockToUBO &pack : command.m_parameterPack.uniformBuffers()) {
2448 Buffer *cpuBuffer = nodeManagers()->bufferManager()->lookupResource(id: pack.m_bufferID);
2449 RHIBuffer *ubo = m_submissionContext->rhiBufferForRenderBuffer(buf: cpuBuffer);
2450 if (!ubo->bind(ctx: &*m_submissionContext, t: RHIBuffer::UniformBuffer))
2451 return false;
2452 }
2453 for (const BlockToSSBO &pack : command.m_parameterPack.shaderStorageBuffers()) {
2454 Buffer *cpuBuffer = nodeManagers()->bufferManager()->lookupResource(id: pack.m_bufferID);
2455 RHIBuffer *ubo = m_submissionContext->rhiBufferForRenderBuffer(buf: cpuBuffer);
2456 if (!ubo->bind(ctx: &*m_submissionContext, t: RHIBuffer::ShaderStorageBuffer))
2457 return false;
2458 }
2459
2460 return true;
2461}
2462
2463bool Renderer::uploadBuffersForCommand(RHIGraphicsPipeline* graphicsPipeline, RenderCommand &command)
2464{
2465 // Create the vertex input description
2466
2467 // Note: we have to bind the buffers here -> which will trigger the actual
2468 // upload, as this is the only place where we know about the usage type of the buffers
2469
2470 const auto geom = command.m_geometry;
2471 const auto &attributes = geom->attributes();
2472 const QRhiVertexInputLayout layout = graphicsPipeline->pipeline()->vertexInputLayout();
2473 const int bindingAttributeCount = std::distance(first: layout.cbeginBindings(), last: layout.cendBindings());
2474 command.vertex_input.resize(sz: bindingAttributeCount);
2475
2476 for (Qt3DCore::QNodeId attribute_id : attributes) {
2477 // TODO isn't there a more efficient way than doing three hash lookups ?
2478 Attribute *attrib = m_nodesManager->attributeManager()->lookupResource(id: attribute_id);
2479 Buffer *buffer = m_nodesManager->bufferManager()->lookupResource(id: attrib->bufferId());
2480 RHIBuffer *hbuf = m_RHIResourceManagers->rhiBufferManager()->lookupResource(id: buffer->peerId());
2481 switch (attrib->attributeType()) {
2482 case Qt3DCore::QAttribute::VertexAttribute: {
2483 if (!hbuf->bind(ctx: &*m_submissionContext, t: RHIBuffer::Type((int)RHIBuffer::Type::ArrayBuffer | (int)RHIBuffer::Type::ShaderStorageBuffer)))
2484 return false;
2485 assert(hbuf->rhiBuffer());
2486 // Find Binding for Attribute
2487 const int bindingIndex = graphicsPipeline->bindingIndexForAttribute(attributeNameId: attrib->nameId());
2488 // We need to reference a binding, a buffer and an offset which is always = 0
2489 // as Qt3D only assumes interleaved or continuous data but not
2490 // buffer where first half of it is attribute1 and second half attribute2
2491 if (bindingIndex != -1)
2492 command.vertex_input[bindingIndex] = { hbuf->rhiBuffer(), 0 };
2493 break;
2494 }
2495 case Qt3DCore::QAttribute::IndexAttribute: {
2496 if (!hbuf->bind(ctx: &*m_submissionContext, t: RHIBuffer::Type::IndexBuffer))
2497 return false;
2498 assert(hbuf->rhiBuffer());
2499
2500 command.indexBuffer = hbuf->rhiBuffer();
2501 command.indexAttribute = attrib;
2502 break;
2503 }
2504 case Qt3DCore::QAttribute::DrawIndirectAttribute:
2505 RHI_UNIMPLEMENTED;
2506 break;
2507 }
2508 }
2509
2510 return true;
2511}
2512
2513bool Renderer::uploadBuffersForCommand(RHIComputePipeline* computePipeline, RenderCommand &command)
2514{
2515 Q_UNUSED(computePipeline);
2516 Q_UNUSED(command);
2517 return true;
2518}
2519
2520bool Renderer::performDraw(QRhiCommandBuffer *cb, const QRhiViewport &vp,
2521 const QRhiScissor *scissor, RenderCommand &command)
2522{
2523 RHIGraphicsPipeline *pipeline = command.pipeline.graphics();
2524 if (!pipeline || !pipeline->isComplete())
2525 return true;
2526
2527 // Setup the rendering pass
2528 cb->setGraphicsPipeline(pipeline->pipeline());
2529 cb->setViewport(vp);
2530 if (scissor)
2531 cb->setScissor(*scissor);
2532
2533 if (!setBindingAndShaderResourcesForCommand(cb, command, uboSet: pipeline->uboSet()))
2534 return false;
2535
2536 // Send the draw command
2537 if (Q_UNLIKELY(!command.indexBuffer)) {
2538 cb->setVertexInput(startBinding: 0, bindingCount: command.vertex_input.size(), bindings: command.vertex_input.data());
2539 cb->draw(vertexCount: command.m_primitiveCount, instanceCount: command.m_instanceCount, firstVertex: command.m_firstVertex,
2540 firstInstance: command.m_firstInstance);
2541 } else {
2542 auto indexFormat = rhiIndexFormat(type: command.indexAttribute->vertexBaseType());
2543 auto indexOffset = command.indexAttribute->byteOffset();
2544 cb->setVertexInput(startBinding: 0, bindingCount: command.vertex_input.size(), bindings: command.vertex_input.data(),
2545 indexBuf: command.indexBuffer, indexOffset, indexFormat);
2546 cb->drawIndexed(indexCount: command.m_primitiveCount, instanceCount: command.m_instanceCount, firstIndex: command.m_indexOffset,
2547 vertexOffset: command.m_indexAttributeByteOffset, firstInstance: command.m_firstInstance);
2548 }
2549 return true;
2550}
2551
2552bool Renderer::setBindingAndShaderResourcesForCommand(QRhiCommandBuffer *cb,
2553 RenderCommand &command,
2554 PipelineUBOSet *uboSet)
2555{
2556 // We need to create new resource bindings for each RC as each RC might potentially
2557 // have different textures or reference custom UBOs (if using Parameters with UBOs directly).
2558 // TO DO: We could propably check for texture and use the UBO set default ShaderResourceBindings
2559 // if we only have UBOs with offsets
2560 bool needsRecreate = false;
2561 if (command.shaderResourceBindings == nullptr) {
2562 command.shaderResourceBindings = m_submissionContext->rhi()->newShaderResourceBindings();
2563 needsRecreate = true;
2564 }
2565
2566 // TO DO: Improve this to only perform when required
2567 const std::vector<QRhiShaderResourceBinding> &resourcesBindings = uboSet->resourceBindings(command);
2568 if (command.resourcesBindings != resourcesBindings) {
2569 command.resourcesBindings = std::move(resourcesBindings);
2570 command.shaderResourceBindings->setBindings(first: command.resourcesBindings.cbegin(), last: command.resourcesBindings.cend());
2571 needsRecreate = true;
2572 }
2573
2574 if (needsRecreate && !command.shaderResourceBindings->create()) {
2575 qCWarning(Backend) << "Failed to create ShaderResourceBindings";
2576 return false;
2577 }
2578 const std::vector<QRhiCommandBuffer::DynamicOffset> offsets = uboSet->offsets(command);
2579
2580 cb->setShaderResources(srb: command.shaderResourceBindings,
2581 dynamicOffsetCount: int(offsets.size()),
2582 dynamicOffsets: offsets.data());
2583 return true;
2584}
2585
2586// Called by RenderView->submit() in RenderThread context
2587// Returns true, if all RenderCommands were sent to the GPU
2588bool Renderer::executeCommandsSubmission(const RHIPassInfo &passInfo)
2589{
2590 bool allCommandsIssued = true;
2591
2592 const std::vector<RenderView *> &renderViews = passInfo.rvs;
2593 QColor clearColor;
2594 QRhiDepthStencilClearValue clearDepthStencil;
2595
2596 // Submit the commands to the underlying graphics API (RHI)
2597 QRhiCommandBuffer *cb = m_submissionContext->currentFrameCommandBuffer();
2598
2599 // Upload data for all RenderCommands
2600 for (RenderView *rv : renderViews) {
2601 // Render drawing commands
2602
2603 // Upload UBOs for pipelines used in current RV
2604 const std::vector<RHIGraphicsPipeline *> &rvGraphicsPipelines = m_rvToGraphicsPipelines[rv];
2605 for (RHIGraphicsPipeline *pipeline : rvGraphicsPipelines)
2606 pipeline->uboSet()->uploadUBOs(ctx: m_submissionContext.data(), rv);
2607
2608 const std::vector<RHIComputePipeline *> &rvComputePipelines = m_rvToComputePipelines[rv];
2609 for (RHIComputePipeline *pipeline : rvComputePipelines)
2610 pipeline->uboSet()->uploadUBOs(ctx: m_submissionContext.data(), rv);
2611
2612 // Upload Buffers for Commands
2613 rv->forEachCommand(func: [&] (RenderCommand &command) {
2614 if (Q_UNLIKELY(!command.isValid()))
2615 return;
2616
2617 if (!uploadBuffersForCommand(cb, rv, command)) {
2618 // Something went wrong trying to upload buffers
2619 // -> likely that frontend buffer has no initial data
2620 qCWarning(Backend) << "Failed to upload buffers";
2621 // Prevents further processing which could be catastrophic
2622 command.m_isValid = false;
2623 }
2624 });
2625
2626 // Record clear information
2627 if (rv->clearTypes() != QClearBuffers::None) {
2628 clearColor = [=] {
2629 auto col = rv->globalClearColorBufferInfo().clearColor;
2630 return QColor::fromRgbF(r: col.x(), g: col.y(), b: col.z(), a: col.w());
2631 }();
2632 clearDepthStencil = { rv->clearDepthValue(), (quint32)rv->clearStencilValue() };
2633 }
2634 }
2635
2636 // Lookup the render target
2637 QRhiRenderTarget *rhiRenderTarget{};
2638 {
2639 const auto &managers = *m_RHIResourceManagers;
2640 auto &renderTargetManager = *managers.rhiRenderTargetManager();
2641 auto *renderTarget = renderTargetManager.lookupResource(id: passInfo.renderTargetId);
2642
2643 if (renderTarget)
2644 rhiRenderTarget = renderTarget->renderTarget;
2645 else if (m_submissionContext->defaultRenderTarget())
2646 rhiRenderTarget = m_submissionContext->defaultRenderTarget();
2647 else
2648 rhiRenderTarget = m_submissionContext->currentSwapChain()->currentFrameRenderTarget();
2649 }
2650
2651 auto executeDrawRenderView = [&] (RenderView* rv) {
2652 // Viewport
2653 QRhiViewport vp;
2654 QRhiScissor scissor;
2655 bool hasScissor = false;
2656 {
2657 const QSize surfaceSize = rhiRenderTarget->pixelSize();
2658
2659 const float x = rv->viewport().x() * surfaceSize.width();
2660 const float y = (1. - rv->viewport().y() - rv->viewport().height()) * surfaceSize.height();
2661 const float w = rv->viewport().width() * surfaceSize.width();
2662 const float h = rv->viewport().height() * surfaceSize.height();
2663 // qDebug() << surfaceSize << x << y << w << h;
2664 vp = { x, y, w, h };
2665 }
2666 // Scissoring
2667 {
2668 RenderStateSet *ss = rv->stateSet();
2669 if (ss == nullptr)
2670 ss = m_defaultRenderStateSet;
2671 StateVariant *scissorTestSVariant =
2672 m_submissionContext->getState(ss, type: StateMask::ScissorStateMask);
2673 if (scissorTestSVariant) {
2674 const ScissorTest *scissorTest =
2675 static_cast<const ScissorTest *>(scissorTestSVariant->constState());
2676 const auto &scissorValues = scissorTest->values();
2677 scissor = { std::get<0>(t: scissorValues), std::get<1>(t: scissorValues),
2678 std::get<2>(t: scissorValues), std::get<3>(t: scissorValues) };
2679 hasScissor = true;
2680 }
2681 }
2682
2683 // Render drawing commands
2684 rv->forEachCommand(func: [&] (RenderCommand &command) {
2685 if (Q_UNLIKELY(!command.isValid()))
2686 return;
2687
2688 Q_ASSERT (command.m_type == RenderCommand::Draw);
2689 performDraw(cb, vp, scissor: hasScissor ? &scissor : nullptr, command);
2690 });
2691 };
2692
2693 auto executeComputeRenderView = [&] (RenderView* rv) {
2694 rv->forEachCommand(func: [&] (RenderCommand &command) {
2695 if (Q_UNLIKELY(!command.isValid()))
2696 return;
2697
2698 Q_ASSERT (command.m_type == RenderCommand::Compute);
2699 performCompute(cb, command);
2700 });
2701 };
2702
2703 bool inCompute = false;
2704 bool inDraw = false;
2705
2706 // All the RVs in the current passinfo target the same RenderTarget
2707 // A single beginPass should take place, unless Computes RVs are intermingled
2708 QRhiResourceUpdateBatch *inPassUpdates = nullptr;
2709 static const bool supportsCompute = m_submissionContext->rhi()->isFeatureSupported(feature: QRhi::Compute);
2710
2711 // Per Pass Global States
2712 for (RenderView *rv : renderViews) {
2713 if (rv->isCompute()) {
2714 // If we were running draw calls we stop the draw pass
2715 if (inDraw) {
2716 cb->endPass(resourceUpdates: m_submissionContext->m_currentUpdates);
2717 m_submissionContext->m_currentUpdates = m_submissionContext->rhi()->nextResourceUpdateBatch();
2718 inDraw = false;
2719 }
2720
2721 // There is also the possibility where we weren't either in a compute or draw pass (for the first RV)
2722 // hence why these conditions are like this
2723 if (supportsCompute) {
2724 if (!inCompute) {
2725 cb->beginComputePass(resourceUpdates: m_submissionContext->m_currentUpdates);
2726 inCompute = true;
2727 }
2728 executeComputeRenderView(rv);
2729 } else {
2730 qWarning() << "RHI backend doesn't support Compute";
2731 }
2732 } else {
2733 // Same logic than above but reversed
2734 if (inCompute) {
2735 cb->endComputePass(resourceUpdates: m_submissionContext->m_currentUpdates);
2736 m_submissionContext->m_currentUpdates = m_submissionContext->rhi()->nextResourceUpdateBatch();
2737 inCompute = false;
2738 }
2739
2740 if (!inDraw) {
2741 if (rhiRenderTarget == nullptr) {
2742 qWarning(catFunc: Backend) << "Invalid Render Target for pass, skipping";
2743 inDraw = false;
2744 continue;
2745 }
2746 cb->beginPass(rt: rhiRenderTarget, colorClearValue: clearColor, depthStencilClearValue: clearDepthStencil, resourceUpdates: m_submissionContext->m_currentUpdates);
2747 inDraw = true;
2748 }
2749
2750 executeDrawRenderView(rv);
2751
2752 const Qt3DCore::QNodeId renderCaptureId = rv->renderCaptureNodeId();
2753 if (!renderCaptureId.isNull()) {
2754 const QRenderCaptureRequest request = rv->renderCaptureRequest();
2755 QRhiRenderTarget *rhiTarget = m_submissionContext->defaultRenderTarget();
2756 RHIRenderTarget *target = nullptr;
2757
2758 if (rv->renderTargetId()) {
2759 RHIRenderTargetManager *targetManager = m_RHIResourceManagers->rhiRenderTargetManager();
2760 target = targetManager->lookupResource(id: rv->renderTargetId());
2761 if (target != nullptr)
2762 rhiTarget = target->renderTarget;
2763 }
2764 // Use FBO size if RV has one
2765 const QSize size = rhiTarget ? rhiTarget->pixelSize() : rv->surfaceSize();
2766
2767 QRect rect(QPoint(0, 0), size);
2768 if (!request.rect.isEmpty())
2769 rect = rect.intersected(other: request.rect);
2770 if (!rect.isEmpty()) {
2771 // Bind fbo as read framebuffer
2772 QRhiReadbackResult *readBackResult = new QRhiReadbackResult;
2773 readBackResult->completed = [this, readBackResult, renderCaptureId, request] () {
2774 const QImage::Format fmt = QImage::Format_RGBA8888_Premultiplied; // fits QRhiTexture::RGBA8
2775 const uchar *p = reinterpret_cast<const uchar *>(readBackResult->data.constData());
2776 const QImage image(p, readBackResult->pixelSize.width(), readBackResult->pixelSize.height(), fmt, [] (void *ptr) {
2777 delete static_cast<QRhiReadbackResult *>(ptr);
2778 }, readBackResult);
2779
2780 Render::RenderCapture *renderCapture = static_cast<Render::RenderCapture*>(m_nodesManager->frameGraphManager()->lookupNode(id: renderCaptureId));
2781 renderCapture->addRenderCapture(captureId: request.captureId, image);
2782 QMutexLocker lock(&m_pendingRenderCaptureSendRequestsMutex);
2783 if (!Qt3DCore::contains(destination: m_pendingRenderCaptureSendRequests, element: renderCaptureId))
2784 m_pendingRenderCaptureSendRequests.push_back(x: renderCaptureId);
2785 };
2786
2787 QRhiReadbackDescription readbackDesc;
2788 if (rhiTarget) {
2789 // First texture should be Attachment 0
2790 QRhiTextureRenderTarget *textureRenderTarget = static_cast<QRhiTextureRenderTarget *>(rhiTarget);
2791 const QRhiTextureRenderTargetDescription &desc = textureRenderTarget->description();
2792 const QRhiColorAttachment *color0Att = desc.colorAttachmentAt(index: 0);
2793 readbackDesc.setTexture(color0Att->texture());
2794 }
2795 inPassUpdates = m_submissionContext->rhi()->nextResourceUpdateBatch();
2796 inPassUpdates->readBackTexture(rb: readbackDesc, result: readBackResult);
2797 } else {
2798 qCWarning(Backend) << "Requested capture rectangle is outside framebuffer";
2799 }
2800 }
2801 }
2802
2803 if (rv->isDownloadBuffersEnable())
2804 downloadRHIBuffers();
2805 }
2806
2807 if (Q_LIKELY(inDraw))
2808 cb->endPass(resourceUpdates: inPassUpdates);
2809 else if (inCompute)
2810 cb->endComputePass();
2811
2812 m_submissionContext->m_currentUpdates = m_submissionContext->rhi()->nextResourceUpdateBatch();
2813
2814 return allCommandsIssued;
2815}
2816
2817namespace {
2818
2819template<typename Manager>
2820void removeUnusedPipelines(Manager *manager)
2821{
2822 const auto &pipelinesHandles = manager->activeHandles();
2823 std::vector<typename Manager::Handle> pipelinesToCleanup;
2824 // Store pipelines to cleanup in a temporary vector so that we can use a
2825 // ref on the activeHandles vector. Calling releaseResources modifies the
2826 // activeHandles vector which we want to avoid while iterating over it.
2827 for (const auto &pipelineHandle : pipelinesHandles) {
2828 auto *pipeline = pipelineHandle.data();
2829 Q_ASSERT(pipeline);
2830 pipeline->decreaseScore();
2831 // Pipeline wasn't used recently, let's destroy it
2832 if (pipeline->score() < 0) {
2833 pipelinesToCleanup.push_back(pipelineHandle);
2834 }
2835 }
2836
2837 // Release Pipelines marked for cleanup
2838 for (const auto &pipelineHandle : pipelinesToCleanup) {
2839 manager->releaseResource(pipelineHandle->key());
2840 }
2841}
2842
2843} // anonymous
2844
2845// Erase graphics related resources that may become unused after a frame
2846void Renderer::cleanGraphicsResources()
2847{
2848 // Remove unused Pipeline
2849 RHIGraphicsPipelineManager *graphicsPipelineManager = m_RHIResourceManagers->rhiGraphicsPipelineManager();
2850 RHIComputePipelineManager *computePipelineManager = m_RHIResourceManagers->rhiComputePipelineManager();
2851
2852 removeUnusedPipelines(manager: graphicsPipelineManager);
2853 removeUnusedPipelines(manager: computePipelineManager);
2854
2855 // Clean buffers
2856 const QList<Qt3DCore::QNodeId> buffersToRelease =
2857 m_nodesManager->bufferManager()->takeBuffersToRelease();
2858 for (Qt3DCore::QNodeId bufferId : buffersToRelease)
2859 m_submissionContext->releaseBuffer(bufferId);
2860
2861 const RHIBufferManager *bufferManager = m_RHIResourceManagers->rhiBufferManager();
2862 const std::vector<HRHIBuffer> &activeBufferHandles = bufferManager->activeHandles();
2863 // Release internal QRhiBuffer that might have been orphaned
2864 for (const HRHIBuffer &bufferH : activeBufferHandles)
2865 bufferH->destroyOrphaned();
2866
2867 // When Textures are cleaned up, their id is saved so that they can be
2868 // cleaned up in the render thread
2869 const QList<Qt3DCore::QNodeId> cleanedUpTextureIds = Qt3DCore::moveAndClear(data&: m_textureIdsToCleanup);
2870 for (const Qt3DCore::QNodeId &textureCleanedUpId : cleanedUpTextureIds)
2871 cleanupTexture(cleanedUpTextureId: textureCleanedUpId);
2872
2873 // Abandon GL shaders when a Shader node is destroyed Note: We are sure
2874 // that when this gets executed, all scene changes have been received and
2875 // shader nodes updated
2876 const QList<Qt3DCore::QNodeId> cleanedUpShaderIds =
2877 m_nodesManager->shaderManager()->takeShaderIdsToCleanup();
2878 for (const Qt3DCore::QNodeId &shaderCleanedUpId : cleanedUpShaderIds) {
2879 cleanupShader(shader: m_nodesManager->shaderManager()->lookupResource(id: shaderCleanedUpId));
2880 // We can really release the texture at this point
2881 m_nodesManager->shaderManager()->releaseResource(id: shaderCleanedUpId);
2882
2883 // Release pipelines that were referencing shader
2884 graphicsPipelineManager->releasePipelinesReferencingShader(shaderId: shaderCleanedUpId);
2885 computePipelineManager->releasePipelinesReferencingShader(shaderId: shaderCleanedUpId);
2886 }
2887
2888 const QList<Qt3DCore::QNodeId> cleanedUpRenderTargetIds =
2889 m_nodesManager->renderTargetManager()->takeRenderTargetIdsToCleanup();
2890 for (const Qt3DCore::QNodeId &renderTargetCleanedUpId : cleanedUpRenderTargetIds) {
2891 cleanupRenderTarget(renderTargetId: renderTargetCleanedUpId);
2892 m_nodesManager->renderTargetManager()->releaseResource(id: renderTargetCleanedUpId);
2893 // Release pipelines that were referencing renderTarget
2894 graphicsPipelineManager->releasePipelinesReferencingRenderTarget(renderTargetId: renderTargetCleanedUpId);
2895 }
2896}
2897
2898const GraphicsApiFilterData *Renderer::contextInfo() const
2899{
2900 return m_submissionContext->contextInfo();
2901}
2902
2903SubmissionContext *Renderer::submissionContext() const
2904{
2905 return m_submissionContext.data();
2906}
2907
2908} // namespace Rhi
2909} // namespace Render
2910} // namespace Qt3DRender
2911
2912QT_END_NAMESPACE
2913

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