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::Int: {
771 if (attr->vertexSize() == 1)
772 return QRhiVertexInputAttribute::SInt;
773 if (attr->vertexSize() == 2)
774 return QRhiVertexInputAttribute::SInt2;
775 if (attr->vertexSize() == 3)
776 return QRhiVertexInputAttribute::SInt3;
777 if (attr->vertexSize() == 4)
778 return QRhiVertexInputAttribute::SInt4;
779 break;
780 }
781 case Qt3DCore::QAttribute::UnsignedInt: {
782 if (attr->vertexSize() == 1)
783 return QRhiVertexInputAttribute::UInt;
784 if (attr->vertexSize() == 2)
785 return QRhiVertexInputAttribute::UInt2;
786 if (attr->vertexSize() == 3)
787 return QRhiVertexInputAttribute::UInt3;
788 if (attr->vertexSize() == 4)
789 return QRhiVertexInputAttribute::UInt4;
790 break;
791 }
792 case Qt3DCore::QAttribute::HalfFloat: {
793 if (attr->vertexSize() == 1)
794 return QRhiVertexInputAttribute::Half;
795 if (attr->vertexSize() == 2)
796 return QRhiVertexInputAttribute::Half2;
797 if (attr->vertexSize() == 3)
798 return QRhiVertexInputAttribute::Half3;
799 if (attr->vertexSize() >= 4)
800 return QRhiVertexInputAttribute::Half4;
801 break;
802 }
803 case Qt3DCore::QAttribute::Float: {
804 if (attr->vertexSize() == 1)
805 return QRhiVertexInputAttribute::Float;
806 if (attr->vertexSize() == 2)
807 return QRhiVertexInputAttribute::Float2;
808 if (attr->vertexSize() == 3)
809 return QRhiVertexInputAttribute::Float3;
810 if (attr->vertexSize() >= 4)
811 return QRhiVertexInputAttribute::Float4;
812 break;
813 }
814 default:
815 break;
816 }
817 return std::nullopt;
818}
819}
820
821void Renderer::updateGraphicsPipeline(RenderCommand &cmd, RenderView *rv)
822{
823 if (!cmd.m_rhiShader) {
824 qCWarning(Backend) << "Command has no shader";
825 return;
826 }
827
828 // The Graphics Pipeline defines
829 // - Render State (Depth, Culling, Stencil, Blending)
830 // - Shader Resources Binding
831 // - Shader Vertex Attribute Layout
832
833 // This means we need to have one GraphicsPipeline per
834 // - geometry layout
835 // - material
836 // - state (RV + RC)
837
838 // Try to retrieve existing pipeline
839 // TO DO: Make RenderState part of the Key
840 // as it is likely many geometrys will have the same layout
841 RHIGraphicsPipelineManager *pipelineManager = m_RHIResourceManagers->rhiGraphicsPipelineManager();
842 const int geometryLayoutId = pipelineManager->getIdForAttributeVec(attributesInfo: cmd.m_attributeInfo);
843 const int renderStatesKey = pipelineManager->getIdForRenderStates(stateSet: cmd.m_stateSet);
844 const GraphicsPipelineIdentifier pipelineKey { .geometryLayoutKey: geometryLayoutId, .shader: cmd.m_shaderId, .renderTarget: rv->renderTargetId(), .primitiveType: cmd.m_primitiveType, .renderStatesKey: renderStatesKey };
845 RHIGraphicsPipeline *graphicsPipeline = pipelineManager->lookupResource(id: pipelineKey);
846 if (graphicsPipeline == nullptr) {
847 // Init UBOSet the first time we allocate a new pipeline
848 graphicsPipeline = pipelineManager->getOrCreateResource(id: pipelineKey);
849 graphicsPipeline->setKey(pipelineKey);
850 graphicsPipeline->uboSet()->setResourceManager(m_RHIResourceManagers);
851 graphicsPipeline->uboSet()->setNodeManagers(m_nodesManager);
852 graphicsPipeline->uboSet()->initializeLayout(ctx: m_submissionContext.data(), shader: cmd.m_rhiShader);
853 }
854
855 // Increase score so that we know the pipeline was used for this frame and shouldn't be
856 // destroyed
857 graphicsPipeline->increaseScore();
858
859 // Record command reference in UBOSet
860 graphicsPipeline->uboSet()->addRenderCommand(cmd);
861
862 // Store association between RV and pipeline
863 if (auto& pipelines = m_rvToGraphicsPipelines[rv]; !Qt3DCore::contains(destination: pipelines, element: graphicsPipeline))
864 pipelines.push_back(x: graphicsPipeline);
865
866 // Record RHIGraphicsPipeline into command for later use
867 cmd.pipeline = graphicsPipeline;
868
869 // TO DO: Set to true if geometry, shader or render state dirty
870 bool requiresRebuild = false;
871
872 // Build/Rebuild actual RHI pipeline if required
873 // TO DO: Ensure we find a way to know when the state is dirty to trigger a rebuild
874 if (graphicsPipeline->pipeline() == nullptr || requiresRebuild)
875 buildGraphicsPipelines(graphicsPipeline, rv, command: cmd);
876}
877
878void Renderer::buildGraphicsPipelines(RHIGraphicsPipeline *graphicsPipeline,
879 RenderView *rv,
880 const RenderCommand &cmd)
881{
882 // Note: This is completely stupid but RHI doesn't allow you to build
883 // a QRhiShaderResourceBindings if you don't already have buffers/textures
884 // at hand (where it should only need to know the type/formats ... of the resources)
885 // For that reason we need to provide a RenderCommand
886
887 // Note: we can rebuild add/remove things from the QRhiShaderResourceBindings after having
888 // created the pipeline and rebuild it. Changes should be picked up automatically
889
890 // If a defaultRenderTarget was set (Scene3D) we don't bother retrieving the swapchain
891 // as we have to use the one provided by Scene3D
892 QRhiSwapChain *rhiSwapChain = nullptr;
893 if (!m_submissionContext->defaultRenderTarget()) {
894 const SubmissionContext::SwapChainInfo *swapchain = m_submissionContext->swapChainForSurface(surface: rv->surface());
895 if (!swapchain || !swapchain->swapChain || !swapchain->renderPassDescriptor) {
896 qCWarning(Backend) << "Can't create pipeline, incomplete SwapChain and no default Render Target";
897 return;
898 }
899 rhiSwapChain = swapchain->swapChain;
900 }
901
902 auto onFailure = [&](const char* msg) {
903 qCWarning(Backend) << "Failed to build graphics pipeline:" << msg;
904 };
905
906 PipelineUBOSet *uboSet = graphicsPipeline->uboSet();
907 RHIShader *shader = cmd.m_rhiShader;
908
909 // Setup shaders
910 const QShader& vertexShader = shader->shaderStage(stage: QShader::VertexStage);
911 if (!vertexShader.isValid())
912 return onFailure("Invalid vertex shader");
913
914 const QShader& fragmentShader = shader->shaderStage(stage: QShader::FragmentStage);
915 if (!fragmentShader.isValid())
916 return onFailure("Invalid fragment shader");
917
918 // Set Resource Bindings
919 const std::vector<QRhiShaderResourceBinding> resourceBindings = uboSet->resourceLayout(shader);
920 QRhiShaderResourceBindings *shaderResourceBindings =
921 m_submissionContext->rhi()->newShaderResourceBindings();
922 graphicsPipeline->setShaderResourceBindings(shaderResourceBindings);
923
924 shaderResourceBindings->setBindings(first: resourceBindings.cbegin(), last: resourceBindings.cend());
925 if (!shaderResourceBindings->create())
926 return onFailure("Unable to create resource bindings");
927
928 // Setup attributes
929 const Geometry *geom = cmd.m_geometry.data();
930 QVarLengthArray<QRhiVertexInputBinding, 8> inputBindings;
931 QVarLengthArray<QRhiVertexInputAttribute, 8> rhiAttributes;
932 QHash<int, int> attributeNameToBinding;
933
934 if (!prepareGeometryInputBindings(geometry: geom, shader: cmd.m_rhiShader,
935 inputBindings, rhiAttributes,
936 attributeNameToBinding))
937 return onFailure("Geometry doesn't match expected layout");
938
939 // Create pipeline
940 QRhiGraphicsPipeline *pipeline = m_submissionContext->rhi()->newGraphicsPipeline();
941 graphicsPipeline->setPipeline(pipeline);
942
943 pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vertexShader },
944 { QRhiShaderStage::Fragment, fragmentShader } });
945
946 pipeline->setShaderResourceBindings(shaderResourceBindings);
947
948 auto rhiTopologyFromQt3DTopology = [] (const Qt3DRender::QGeometryRenderer::PrimitiveType t) {
949 switch (t) {
950 case Qt3DRender::QGeometryRenderer::Points:
951 return QRhiGraphicsPipeline::Points;
952
953 case Qt3DRender::QGeometryRenderer::Lines:
954 return QRhiGraphicsPipeline::Lines;
955
956 case Qt3DRender::QGeometryRenderer::LineStrip:
957 return QRhiGraphicsPipeline::LineStrip;
958
959 case Qt3DRender::QGeometryRenderer::Triangles:
960 return QRhiGraphicsPipeline::Triangles;
961
962 case Qt3DRender::QGeometryRenderer::TriangleStrip:
963 return QRhiGraphicsPipeline::TriangleStrip;
964
965 case Qt3DRender::QGeometryRenderer::TriangleFan:
966 return QRhiGraphicsPipeline::TriangleFan;
967
968 case Qt3DRender::QGeometryRenderer::LineLoop: {
969 qWarning(catFunc: Backend) << "LineLoop primitive type is not handled by RHI";
970 return QRhiGraphicsPipeline::Lines;
971 }
972
973 case Qt3DRender::QGeometryRenderer::Patches: {
974 qWarning(catFunc: Backend) << "Patches primitive type is not handled by RHI";
975 return QRhiGraphicsPipeline::Points;
976 }
977
978 case Qt3DRender::QGeometryRenderer::LinesAdjacency:
979 case Qt3DRender::QGeometryRenderer::TrianglesAdjacency:
980 case Qt3DRender::QGeometryRenderer::LineStripAdjacency:
981 case Qt3DRender::QGeometryRenderer::TriangleStripAdjacency: {
982 qWarning(catFunc: Backend) << "Adjancency primitive types are not handled by RHI";
983 return QRhiGraphicsPipeline::Points;
984 }
985 }
986 return QRhiGraphicsPipeline::Points;
987 };
988
989 pipeline->setTopology(rhiTopologyFromQt3DTopology(cmd.m_primitiveType));
990
991 QRhiVertexInputLayout inputLayout;
992 inputLayout.setBindings(first: inputBindings.begin(), last: inputBindings.end());
993 inputLayout.setAttributes(first: rhiAttributes.begin(), last: rhiAttributes.end());
994 pipeline->setVertexInputLayout(inputLayout);
995 graphicsPipeline->setAttributesToBindingHash(attributeNameToBinding);
996
997 // Render States
998 RenderStateSet *renderState = nullptr;
999 {
1000 RenderStateSet *globalState =
1001 (rv->stateSet() != nullptr) ? rv->stateSet() : m_defaultRenderStateSet;
1002
1003 // Merge global state into local state
1004 RenderStateSet *localState = cmd.m_stateSet.data();
1005 if (localState != nullptr) {
1006 localState->merge(other: globalState);
1007 renderState = localState;
1008 } else {
1009 renderState = globalState;
1010 }
1011 }
1012 m_submissionContext->applyStateSet(ss: renderState, graphicsPipeline: pipeline);
1013
1014 // Setup potential texture render target
1015 const bool renderTargetIsSet = setupRenderTarget(rv, graphicsPipeline, swapchain: rhiSwapChain);
1016 if (!renderTargetIsSet)
1017 return onFailure("No Render Target Set");
1018
1019 if (!pipeline->create())
1020 return onFailure("Creation Failed");
1021
1022 graphicsPipeline->markComplete();
1023}
1024
1025void Renderer::updateComputePipeline(RenderCommand &cmd, RenderView *rv, int renderViewIndex)
1026{
1027 if (!cmd.m_rhiShader) {
1028 qCWarning(Backend) << "Command has no shader";
1029 return;
1030 }
1031
1032 // Try to retrieve existing pipeline
1033 RHIComputePipelineManager *pipelineManager = m_RHIResourceManagers->rhiComputePipelineManager();
1034 const ComputePipelineIdentifier pipelineKey { .shader: cmd.m_shaderId, .renderViewIndex: renderViewIndex };
1035 RHIComputePipeline *computePipeline = pipelineManager->lookupResource(id: pipelineKey);
1036 if (computePipeline == nullptr) {
1037 // Init UBOSet the first time we allocate a new pipeline
1038 computePipeline = pipelineManager->getOrCreateResource(id: pipelineKey);
1039 computePipeline->setKey(pipelineKey);
1040 computePipeline->uboSet()->setResourceManager(m_RHIResourceManagers);
1041 computePipeline->uboSet()->setNodeManagers(m_nodesManager);
1042 computePipeline->uboSet()->initializeLayout(ctx: m_submissionContext.data(), shader: cmd.m_rhiShader);
1043 }
1044
1045 if (!computePipeline) {
1046 qCWarning(Backend) << "Could not create a compute pipeline";
1047 return;
1048 }
1049
1050 // Increase score so that we know the pipeline was used for this frame and shouldn't be
1051 // destroyed
1052 computePipeline->increaseScore();
1053
1054 // Record command reference in UBOSet
1055 computePipeline->uboSet()->addRenderCommand(cmd);
1056
1057 // Store association between RV and pipeline
1058 if (auto& pipelines = m_rvToComputePipelines[rv]; !Qt3DCore::contains(destination: pipelines, element: computePipeline))
1059 pipelines.push_back(x: computePipeline);
1060
1061 // Record RHIGraphicsPipeline into command for later use
1062 cmd.pipeline = computePipeline;
1063
1064 // TO DO: Set to true if geometry, shader or render state dirty
1065 bool requiredRebuild = false;
1066
1067 // Note: we can rebuild add/remove things from the QRhiShaderResourceBindings after having
1068 // created the pipeline and rebuild it. Changes should be picked up automatically
1069
1070 // Create pipeline if it doesn't exist or needs to be updated
1071 if (computePipeline->pipeline() == nullptr || requiredRebuild)
1072 buildComputePipelines(computePipeline, rv, command: cmd);
1073}
1074
1075void Renderer::buildComputePipelines(RHIComputePipeline *computePipeline,
1076 RenderView *rv,
1077 const RenderCommand &cmd)
1078{
1079 Q_UNUSED(rv);
1080 auto onFailure = [&] {
1081 qCWarning(Backend) << "Failed to build compute pipeline";
1082 };
1083
1084 PipelineUBOSet *uboSet = computePipeline->uboSet();
1085 RHIShader *shader = cmd.m_rhiShader;
1086
1087 // Setup shaders
1088 const QShader& computeShader = cmd.m_rhiShader->shaderStage(stage: QShader::ComputeStage);
1089 if (!computeShader.isValid())
1090 return onFailure();
1091
1092 // Set Resource Bindings
1093 const std::vector<QRhiShaderResourceBinding> resourceBindings = uboSet->resourceLayout(shader);
1094 QRhiShaderResourceBindings *shaderResourceBindings =
1095 m_submissionContext->rhi()->newShaderResourceBindings();
1096 computePipeline->setShaderResourceBindings(shaderResourceBindings);
1097
1098 shaderResourceBindings->setBindings(first: resourceBindings.cbegin(), last: resourceBindings.cend());
1099 if (!shaderResourceBindings->create()) {
1100 return onFailure();
1101 }
1102
1103 // Create pipeline
1104 QRhiComputePipeline *pipeline = m_submissionContext->rhi()->newComputePipeline();
1105 computePipeline->setPipeline(pipeline);
1106
1107 pipeline->setShaderStage(QRhiShaderStage{QRhiShaderStage::Compute, computeShader});
1108 pipeline->setShaderResourceBindings(shaderResourceBindings);
1109
1110 // QRhiComputePiple has no render states
1111
1112 if (!pipeline->create())
1113 return onFailure();
1114}
1115
1116void Renderer::createRenderTarget(RenderTarget *target)
1117{
1118 const Qt3DCore::QNodeId &renderTargetId = target->peerId();
1119 RHIRenderTargetManager *rhiRenderTargetManager = m_RHIResourceManagers->rhiRenderTargetManager();
1120
1121 Q_ASSERT(!m_RHIResourceManagers->rhiRenderTargetManager()->contains(renderTargetId));
1122 RHIRenderTarget *rhiTarget = rhiRenderTargetManager->getOrCreateResource(id: renderTargetId);
1123
1124 RHITextureManager *texman = rhiResourceManagers()->rhiTextureManager();
1125 // TO DO: We use all render targets and ignore the fact that
1126 // QRenderTargetSelector can specify a subset of outputs
1127 // -> We should propably remove that from the frontend API
1128 // as it's hard to handle for us and very unlikely anyone uses that
1129 const AttachmentPack pack = AttachmentPack(target, m_nodesManager->attachmentManager());
1130 QRhiTextureRenderTargetDescription desc;
1131
1132 QSize targetSize{};
1133 int targetSamples{1};
1134 QVarLengthArray<QRhiColorAttachment, 8> rhiAttachments;
1135
1136 bool hasDepthTexture = false;
1137
1138 // Used in case of failure
1139 QVarLengthArray<QRhiResource*> resourcesToClean;
1140 auto cleanAllocatedResources = [&] {
1141 QStringList descDetails;
1142 auto texDetails = [](QRhiTexture *tex) {
1143 return QString("Texture format: %1; flags: %2; samples: %3").arg(a: tex->format()).arg(a: tex->flags()).arg(a: tex->sampleCount());
1144 };
1145 auto bufferDetails = [](QRhiRenderBuffer* buffer) {
1146 return QString("Buffer Type: %1; flags: %2; samples: %3").arg(a: buffer->type()).arg(a: buffer->flags()).arg(a: buffer->sampleCount());
1147 };
1148 const auto itEnd = desc.cendColorAttachments();
1149 for (auto it = desc.cbeginColorAttachments(); it != itEnd; ++it) {
1150 QString attDetails = QString("Layer: %1; Level: %2; ").arg(a: it->layer()).arg(a: it->level());
1151 if (it->texture())
1152 attDetails += texDetails(it->texture());
1153 descDetails << attDetails;
1154 }
1155 if (desc.depthTexture())
1156 descDetails << QString("Depth Texture: %1").arg(a: texDetails(desc.depthTexture()));
1157 if (desc.depthStencilBuffer())
1158 descDetails << QString("Depth Buffer: %1").arg(a: bufferDetails(desc.depthStencilBuffer()));
1159 qCWarning(Backend) << "Failed to create RenderTarget" << renderTargetId << "\n" << descDetails;
1160 for (auto res : resourcesToClean) {
1161 res->destroy();
1162 delete res;
1163 }
1164 };
1165
1166 RHIRenderTarget::BackBuffer backBuffer = RHIRenderTarget::BackBuffer::None;
1167
1168 // Look up attachments to populate the RT description
1169 // Attachments are sorted by attachment point (Color0 is first)
1170 for (const Attachment &attachment : pack.attachments()) {
1171
1172 if (attachment.m_point == QRenderTargetOutput::Left || attachment.m_point == QRenderTargetOutput::Right) {
1173 backBuffer = attachment.m_point == QRenderTargetOutput::Left ? RHIRenderTarget::BackBuffer::Left : RHIRenderTarget::BackBuffer::Right;
1174 break;
1175 }
1176
1177 RHITexture *tex = texman->lookupResource(id: attachment.m_textureUuid);
1178 if (tex && tex->getRhiTexture()) {
1179 auto rhiTex = tex->getRhiTexture();
1180 if (!rhiTex->flags().testFlag(flag: QRhiTexture::RenderTarget) ||
1181 !rhiTex->flags().testFlag(flag: QRhiTexture::UsedAsTransferSource)) {
1182 // UsedAsTransferSource is required if we ever want to read back from the texture
1183 rhiTex->destroy();
1184 rhiTex->setFlags(rhiTex->flags() | QRhiTexture::RenderTarget|QRhiTexture::UsedAsTransferSource);
1185 rhiTex->create();
1186 }
1187 switch (rhiTex->format()) {
1188 case QRhiTexture::Format::D16:
1189 case QRhiTexture::Format::D24:
1190 case QRhiTexture::Format::D24S8:
1191 case QRhiTexture::Format::D32F: {
1192 desc.setDepthTexture(rhiTex);
1193 targetSize = tex->size();
1194 hasDepthTexture = true;
1195 break;
1196 }
1197 default: {
1198 QRhiColorAttachment rhiAtt{rhiTex};
1199 // TODO handle cubemap face
1200 targetSize = tex->size();
1201 targetSamples = tex->properties().samples;
1202
1203 rhiAtt.setLayer(attachment.m_layer);
1204 rhiAtt.setLevel(attachment.m_mipLevel);
1205 rhiAttachments.push_back(t: rhiAtt);
1206
1207 break;
1208 }
1209 }
1210 } else {
1211 cleanAllocatedResources();
1212 return;
1213 }
1214 }
1215
1216 rhiTarget->backBuffer = backBuffer;
1217 // If we are targeting one of the back buffers directly, don't create an offscreen RT
1218 if (backBuffer != RHIRenderTarget::BackBuffer::None)
1219 return;
1220
1221 // Otherwise, create QRhiRenderBuffer and associated resources
1222 if (targetSize.width() <= 0 || targetSize.height() <= 0) {
1223 cleanAllocatedResources();
1224 return;
1225 }
1226
1227 desc.setColorAttachments(first: rhiAttachments.begin(), last: rhiAttachments.end());
1228
1229 // Potentially create a depth & stencil renderbuffer
1230 QRhiRenderBuffer *ds{};
1231 if (!hasDepthTexture) {
1232 ds = m_submissionContext->rhi()->newRenderBuffer(type: QRhiRenderBuffer::DepthStencil, pixelSize: targetSize, sampleCount: targetSamples);
1233 resourcesToClean << ds;
1234
1235 if (!ds->create()) {
1236 cleanAllocatedResources();
1237 return;
1238 }
1239 desc.setDepthStencilBuffer(ds);
1240 }
1241
1242 // Create the render target
1243 auto rt = m_submissionContext->rhi()->newTextureRenderTarget(desc);
1244 resourcesToClean << rt;
1245
1246 auto rp = rt->newCompatibleRenderPassDescriptor();
1247 resourcesToClean << rp;
1248
1249 rt->setRenderPassDescriptor(rp);
1250
1251 if (!rt->create()) {
1252 cleanAllocatedResources();
1253 rhiRenderTargetManager->releaseResource(id: renderTargetId);
1254 return;
1255 }
1256
1257 rhiTarget->renderTarget = rt;
1258 rhiTarget->renderPassDescriptor = rp;
1259 rhiTarget->depthStencilBuffer = ds;
1260}
1261
1262bool Renderer::setupRenderTarget(RenderView *rv,
1263 RHIGraphicsPipeline *graphicsPipeline,
1264 QRhiSwapChain *swapchain)
1265{
1266 QRhiGraphicsPipeline *rhiPipeline = graphicsPipeline->pipeline();
1267
1268 const auto &managers = *nodeManagers();
1269 auto &renderTargetManager = *managers.renderTargetManager();
1270
1271 auto useSwapchainForPipeline = [&]() {
1272 Q_ASSERT(swapchain);
1273 rhiPipeline->setRenderPassDescriptor(swapchain->renderPassDescriptor());
1274 rhiPipeline->setSampleCount(swapchain->sampleCount());
1275 };
1276
1277 auto *renderTarget = renderTargetManager.lookupResource(id: rv->renderTargetId());
1278 if (renderTarget) {
1279 // Render to texture
1280 const Qt3DCore::QNodeId &renderTargetId = renderTarget->peerId();
1281 RHIRenderTargetManager *rhiRenderTargetManager = m_RHIResourceManagers->rhiRenderTargetManager();
1282 RHIRenderTarget *rhiTarget = rhiRenderTargetManager->lookupResource(id: renderTargetId);
1283
1284 if (!rhiTarget) {
1285 qWarning(catFunc: Backend) << "Invalid RenderTarget " << renderTargetId << " for Pipeline";
1286 return false;
1287 }
1288
1289 // The RenderTarget we reference might actually be referencing a swapchain back buffer
1290 if (rhiTarget->backBuffer != RHIRenderTarget::BackBuffer::None) {
1291 // Render to the default framebuffer on our swapchain
1292 useSwapchainForPipeline();
1293 } else {
1294 if (!rhiTarget->renderTarget) {
1295 qWarning(catFunc: Backend) << "Invalid RenderTarget " << renderTargetId << " for Pipeline";
1296 return false;
1297 }
1298 rhiPipeline->setRenderPassDescriptor(rhiTarget->renderPassDescriptor);
1299 rhiPipeline->setSampleCount(rhiTarget->renderTarget->sampleCount());
1300 }
1301 return true;
1302
1303 } else if (m_submissionContext->defaultRenderTarget()) {
1304 // Use default RenderTarget if set Default FBO set by Scene3D
1305 QRhiRenderTarget *defaultTarget = m_submissionContext->defaultRenderTarget();;
1306 rhiPipeline->setRenderPassDescriptor(defaultTarget->renderPassDescriptor());
1307 rhiPipeline->setSampleCount(defaultTarget->sampleCount());
1308 return true;
1309 } else {
1310 // Render to the default framebuffer on our swapchain
1311 useSwapchainForPipeline();
1312 return true;
1313 }
1314}
1315
1316// When this function is called, we must not be processing the commands for frame n+1
1317std::vector<Renderer::RHIPassInfo>
1318Renderer::prepareCommandsSubmission(const std::vector<RenderView *> &renderViews)
1319{
1320 const size_t renderViewCount = renderViews.size();
1321
1322 // Gather all distinct RHIGraphicsPipeline we will use
1323 // For each RenderView, we will need to gather
1324 // -> The RHIGraphicsPipelines being used
1325 // -> The number of RenderCommands used by each pipeline
1326
1327 // This will allows us to generate UBOs based on the number of commands/rv we have
1328 RHIGraphicsPipelineManager *graphicsPipelineManager = m_RHIResourceManagers->rhiGraphicsPipelineManager();
1329 const std::vector<HRHIGraphicsPipeline> &graphicsPipelinesHandles = graphicsPipelineManager->activeHandles();
1330 for (HRHIGraphicsPipeline pipelineHandle : graphicsPipelinesHandles) {
1331 RHIGraphicsPipeline *pipeline = graphicsPipelineManager->data(handle: pipelineHandle);
1332 // Reset PipelineUBOSet
1333 pipeline->uboSet()->clear();
1334 }
1335 RHIComputePipelineManager *computePipelineManager = m_RHIResourceManagers->rhiComputePipelineManager();
1336 const std::vector<HRHIComputePipeline> &computePipelinesHandles = computePipelineManager->activeHandles();
1337 for (HRHIComputePipeline pipelineHandle : computePipelinesHandles) {
1338 RHIComputePipeline *pipeline = computePipelineManager->data(handle: pipelineHandle);
1339 // Reset PipelineUBOSet
1340 pipeline->uboSet()->clear();
1341 }
1342
1343 // Clear any reference between RV and Pipelines we had
1344 // as we are about to rebuild these
1345 m_rvToGraphicsPipelines.clear();
1346 m_rvToComputePipelines.clear();
1347
1348 // We need to have a single RHI RenderPass per RenderTarget
1349 // as creating the pass clears the buffers
1350 // 1) We need to find all adjacents RenderViews that have the same renderTarget
1351 // and submit all of these as part of the same RHI pass
1352 std::vector<RHIPassInfo> rhiPassesInfo;
1353
1354 for (size_t i = 0; i < renderViewCount;) {
1355 std::vector<RenderView *> sameRenderTargetRVs;
1356 RenderView *refRV = renderViews[i];
1357 sameRenderTargetRVs.push_back(x: refRV);
1358
1359 for (i = i + 1; i < renderViewCount; ++i) {
1360 RenderView *curRV = renderViews[i];
1361 if (refRV->renderTargetId() == curRV->renderTargetId()) {
1362 sameRenderTargetRVs.push_back(x: curRV);
1363 } else
1364 break;
1365 }
1366
1367 RHIPassInfo bucket;
1368 bucket.rvs = std::move(sameRenderTargetRVs);
1369 bucket.surface = refRV->surface();
1370 bucket.renderTargetId = refRV->renderTargetId();
1371 rhiPassesInfo.push_back(x: bucket);
1372 }
1373
1374 RHIShaderManager *rhiShaderManager = m_RHIResourceManagers->rhiShaderManager();
1375 // Assign a Graphics Pipeline to each RenderCommand
1376 for (size_t i = 0; i < renderViewCount; ++i) {
1377 RenderView *rv = renderViews[i];
1378
1379 // Handle BlitFrameBufferCase
1380 if (rv->hasBlitFramebufferInfo())
1381 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.";
1382
1383 rv->forEachCommand(func: [&] (RenderCommand &command) {
1384 // Update/Create GraphicsPipelines
1385 if (command.m_type == RenderCommand::Draw) {
1386 Geometry *rGeometry =
1387 m_nodesManager->data<Geometry, GeometryManager>(handle: command.m_geometry);
1388 GeometryRenderer *rGeometryRenderer =
1389 m_nodesManager->data<GeometryRenderer, GeometryRendererManager>(
1390 handle: command.m_geometryRenderer);
1391
1392 command.m_rhiShader = rhiShaderManager->lookupResource(shaderId: command.m_shaderId);
1393 // By this time shaders should have been loaded
1394 RHIShader *shader = command.m_rhiShader;
1395 if (!shader)
1396 return;
1397
1398 // We should never have inserted a command for which these are null
1399 // in the first place
1400 Q_ASSERT(rGeometry && rGeometryRenderer && shader);
1401
1402 // Unset dirtiness on rGeometryRenderer only
1403 // The rGeometry may be shared by several rGeometryRenderer
1404 // so we cannot unset its dirtiness at this point
1405 if (rGeometryRenderer->isDirty())
1406 rGeometryRenderer->unsetDirty();
1407
1408 updateGraphicsPipeline(cmd&: command, rv);
1409
1410 } else if (command.m_type == RenderCommand::Compute) {
1411 // By this time shaders have been loaded
1412 RHIShader *shader = command.m_rhiShader;
1413 if (!shader)
1414 return;
1415
1416 updateComputePipeline(cmd&: command, rv, renderViewIndex: int(i));
1417 }
1418 });
1419 }
1420
1421 // Now that we know how many pipelines we have and how many RC each pipeline
1422 // has, we can allocate/reallocate UBOs with correct size for each pipelines
1423 for (RenderView *rv : renderViews) {
1424 // Allocate UBOs for pipelines used in current RV
1425 const std::vector<RHIGraphicsPipeline *> &rvGraphicsPipelines = m_rvToGraphicsPipelines[rv];
1426 for (RHIGraphicsPipeline *pipeline : rvGraphicsPipelines)
1427 pipeline->uboSet()->allocateUBOs(ctx: m_submissionContext.data());
1428 // Allocate UBOs for pipelines used in current RV
1429 const std::vector<RHIComputePipeline *> &rvComputePipelines = m_rvToComputePipelines[rv];
1430 for (RHIComputePipeline *pipeline : rvComputePipelines)
1431 pipeline->uboSet()->allocateUBOs(ctx: m_submissionContext.data());
1432 }
1433
1434 // Unset dirtiness on Geometry and Attributes
1435 // Note: we cannot do it in the loop above as we want to be sure that all
1436 // the VAO which reference the geometry/attributes are properly updated
1437 RHI_UNIMPLEMENTED;
1438 for (Attribute *attribute : std::as_const(t&: m_dirtyAttributes))
1439 attribute->unsetDirty();
1440 m_dirtyAttributes.clear();
1441
1442 for (Geometry *geometry : std::as_const(t&: m_dirtyGeometry))
1443 geometry->unsetDirty();
1444 m_dirtyGeometry.clear();
1445
1446 return rhiPassesInfo;
1447}
1448
1449// Executed in a job
1450void Renderer::lookForDirtyBuffers()
1451{
1452 const std::vector<HBuffer> &activeBufferHandles = m_nodesManager->bufferManager()->activeHandles();
1453 for (const HBuffer &handle : activeBufferHandles) {
1454 Buffer *buffer = m_nodesManager->bufferManager()->data(handle);
1455 if (buffer->isDirty())
1456 m_dirtyBuffers.push_back(x: handle);
1457 }
1458}
1459
1460// Called in prepareSubmission
1461void Renderer::lookForDownloadableBuffers()
1462{
1463 m_downloadableBuffers.clear();
1464 const std::vector<HBuffer> &activeBufferHandles = m_nodesManager->bufferManager()->activeHandles();
1465 for (const HBuffer &handle : activeBufferHandles) {
1466 Buffer *buffer = m_nodesManager->bufferManager()->data(handle);
1467 if (buffer->access() & Qt3DCore::QBuffer::Read)
1468 m_downloadableBuffers.push_back(x: buffer->peerId());
1469 }
1470}
1471
1472// Executed in a job
1473void Renderer::lookForDirtyTextures()
1474{
1475 // To avoid having Texture or TextureImage maintain relationships between
1476 // one another, we instead perform a lookup here to check if a texture
1477 // image has been updated to then notify textures referencing the image
1478 // that they need to be updated
1479 TextureImageManager *imageManager = m_nodesManager->textureImageManager();
1480 const std::vector<HTextureImage> &activeTextureImageHandles = imageManager->activeHandles();
1481 Qt3DCore::QNodeIdVector dirtyImageIds;
1482 for (const HTextureImage &handle : activeTextureImageHandles) {
1483 TextureImage *image = imageManager->data(handle);
1484 if (image->isDirty()) {
1485 dirtyImageIds.push_back(t: image->peerId());
1486 image->unsetDirty();
1487 }
1488 }
1489
1490 TextureManager *textureManager = m_nodesManager->textureManager();
1491 const std::vector<HTexture> &activeTextureHandles = textureManager->activeHandles();
1492 for (const HTexture &handle : activeTextureHandles) {
1493 Texture *texture = textureManager->data(handle);
1494 const Qt3DCore::QNodeIdVector imageIds = texture->textureImageIds();
1495
1496 // Does the texture reference any of the dirty texture images?
1497 for (const Qt3DCore::QNodeId &imageId : imageIds) {
1498 if (dirtyImageIds.contains(t: imageId)) {
1499 texture->addDirtyFlag(flags: Texture::DirtyImageGenerators);
1500 break;
1501 }
1502 }
1503
1504 // Dirty meaning that something has changed on the texture
1505 // either properties, parameters, shared texture id, generator or a texture image
1506 if (texture->dirtyFlags() != Texture::NotDirty)
1507 m_dirtyTextures.push_back(x: handle);
1508 // Note: texture dirty flags are reset when actually updating the
1509 // textures in updateGLResources() as resetting flags here would make
1510 // us lose information about what was dirty exactly.
1511 }
1512}
1513
1514// Executed in a job
1515void Renderer::reloadDirtyShaders()
1516{
1517 Q_ASSERT(isRunning());
1518 const std::vector<HTechnique> &activeTechniques =
1519 m_nodesManager->techniqueManager()->activeHandles();
1520 const std::vector<HShaderBuilder> &activeBuilders =
1521 m_nodesManager->shaderBuilderManager()->activeHandles();
1522 for (const HTechnique &techniqueHandle : activeTechniques) {
1523 Technique *technique = m_nodesManager->techniqueManager()->data(handle: techniqueHandle);
1524 // If api of the renderer matches the one from the technique
1525 if (technique->isCompatibleWithRenderer()) {
1526 const auto passIds = technique->renderPasses();
1527 for (const Qt3DCore::QNodeId &passId : passIds) {
1528 RenderPass *renderPass =
1529 m_nodesManager->renderPassManager()->lookupResource(id: passId);
1530 HShader shaderHandle =
1531 m_nodesManager->shaderManager()->lookupHandle(id: renderPass->shaderProgram());
1532 Shader *shader = m_nodesManager->shaderManager()->data(handle: shaderHandle);
1533
1534 ShaderBuilder *shaderBuilder = nullptr;
1535 for (const HShaderBuilder &builderHandle : activeBuilders) {
1536 ShaderBuilder *builder =
1537 m_nodesManager->shaderBuilderManager()->data(handle: builderHandle);
1538 if (builder->shaderProgramId() == shader->peerId()) {
1539 shaderBuilder = builder;
1540 break;
1541 }
1542 }
1543
1544 if (shaderBuilder) {
1545 shaderBuilder->setGraphicsApi(*technique->graphicsApiFilter());
1546
1547 for (int i = 0; i <= QShaderProgram::Compute; i++) {
1548 const auto shaderType = static_cast<QShaderProgram::ShaderType>(i);
1549 if (!shaderBuilder->shaderGraph(type: shaderType).isValid())
1550 continue;
1551
1552 if (shaderBuilder->isShaderCodeDirty(type: shaderType)) {
1553 shaderBuilder->generateCode(type: shaderType);
1554 Qt3DCore::moveAtEnd(destination&: m_shaderBuilderUpdates, source: shaderBuilder->takePendingUpdates());
1555 }
1556
1557 const auto code = shaderBuilder->shaderCode(type: shaderType);
1558 shader->setShaderCode(type: shaderType, code);
1559 }
1560 }
1561
1562 if (shader != nullptr && shader->isDirty()) {
1563 if (!Qt3DCore::contains(destination: m_dirtyShaders, element: shaderHandle))
1564 m_dirtyShaders.push_back(x: shaderHandle);
1565 }
1566 }
1567 }
1568 }
1569}
1570
1571// Executed in job postFrame (in main thread when jobs are done)
1572void Renderer::sendShaderChangesToFrontend(Qt3DCore::QAspectManager *manager)
1573{
1574 Q_ASSERT(isRunning());
1575
1576 // Sync Shader
1577 const std::vector<HShader> &activeShaders = m_nodesManager->shaderManager()->activeHandles();
1578 for (const HShader &handle : activeShaders) {
1579 Shader *s = m_nodesManager->shaderManager()->data(handle);
1580 if (!s)
1581 continue;
1582
1583 if (s->requiresFrontendSync()) {
1584 QShaderProgram *frontend =
1585 static_cast<decltype(frontend)>(manager->lookupNode(id: s->peerId()));
1586 if (frontend) {
1587 QShaderProgramPrivate *dFrontend =
1588 static_cast<decltype(dFrontend)>(Qt3DCore::QNodePrivate::get(q: frontend));
1589 dFrontend->setStatus(s->status());
1590 dFrontend->setLog(s->log());
1591 s->unsetRequiresFrontendSync();
1592 }
1593 }
1594 }
1595
1596 // Sync ShaderBuilder
1597 for (const ShaderBuilderUpdate &update : m_shaderBuilderUpdates) {
1598 QShaderProgramBuilder *builder =
1599 static_cast<decltype(builder)>(manager->lookupNode(id: update.builderId));
1600 if (!builder)
1601 continue;
1602
1603 QShaderProgramBuilderPrivate *dBuilder =
1604 static_cast<decltype(dBuilder)>(Qt3DCore::QNodePrivate::get(q: builder));
1605 dBuilder->setShaderCode(code: update.shaderCode, type: update.shaderType);
1606 }
1607 m_shaderBuilderUpdates.clear();
1608}
1609
1610// Executed in a job postFrame (in main thread when jobs are done)
1611void Renderer::sendTextureChangesToFrontend(Qt3DCore::QAspectManager *manager)
1612{
1613 const std::vector<QPair<Texture::TextureUpdateInfo, Qt3DCore::QNodeIdVector>>
1614 updateTextureProperties = std::move(m_updatedTextureProperties);
1615 for (const auto &pair : updateTextureProperties) {
1616 const Qt3DCore::QNodeIdVector targetIds = pair.second;
1617 for (const Qt3DCore::QNodeId &targetId : targetIds) {
1618 // Lookup texture
1619 Texture *t = m_nodesManager->textureManager()->lookupResource(id: targetId);
1620 // If backend texture is Dirty, some property has changed and the properties we are
1621 // about to send are already outdate
1622 if (t == nullptr || t->dirtyFlags() != Texture::NotDirty)
1623 continue;
1624
1625 QAbstractTexture *texture =
1626 static_cast<QAbstractTexture *>(manager->lookupNode(id: targetId));
1627 if (!texture)
1628 continue;
1629 const TextureProperties &properties = pair.first.properties;
1630
1631 const bool blocked = texture->blockNotifications(block: true);
1632 texture->setWidth(properties.width);
1633 texture->setHeight(properties.height);
1634 texture->setDepth(properties.depth);
1635 texture->setLayers(properties.layers);
1636 texture->setFormat(properties.format);
1637 texture->blockNotifications(block: blocked);
1638
1639 QAbstractTexturePrivate *dTexture =
1640 static_cast<QAbstractTexturePrivate *>(Qt3DCore::QNodePrivate::get(q: texture));
1641 dTexture->setStatus(properties.status);
1642 dTexture->setHandleType(pair.first.handleType);
1643 dTexture->setHandle(pair.first.handle);
1644 }
1645 }
1646}
1647
1648// Executed in a job postFrame (in main thread when jobs done)
1649void Renderer::sendDisablesToFrontend(Qt3DCore::QAspectManager *manager)
1650{
1651 // SubtreeEnabled
1652 const auto updatedDisables = Qt3DCore::moveAndClear(data&: m_updatedDisableSubtreeEnablers);
1653 for (const auto &nodeId : updatedDisables) {
1654 QSubtreeEnabler *frontend = static_cast<decltype(frontend)>(manager->lookupNode(id: nodeId));
1655 frontend->setEnabled(false);
1656 }
1657
1658 // Compute Commands
1659 const std::vector<HComputeCommand> &activeCommands =
1660 m_nodesManager->computeJobManager()->activeHandles();
1661 for (const HComputeCommand &handle : activeCommands) {
1662 ComputeCommand *c = m_nodesManager->computeJobManager()->data(handle);
1663 if (c->hasReachedFrameCount()) {
1664 QComputeCommand *frontend =
1665 static_cast<decltype(frontend)>(manager->lookupNode(id: c->peerId()));
1666 frontend->setEnabled(false);
1667 c->resetHasReachedFrameCount();
1668 }
1669 }
1670}
1671
1672bool Renderer::prepareGeometryInputBindings(const Geometry *geometry, const RHIShader *shader,
1673 QVarLengthArray<QRhiVertexInputBinding, 8> &inputBindings,
1674 QVarLengthArray<QRhiVertexInputAttribute, 8> &rhiAttributes,
1675 QHash<int, int> &attributeNameToBinding)
1676{
1677 // shader requires no attributes
1678 if (shader->attributes().empty())
1679 return true;
1680
1681 // QRhiVertexInputBinding -> specifies the stride of an attribute,
1682 // whether it's per vertex or per instance and the instance divisor
1683
1684 // QRhiVertexInputAttribute -> specifies the format of the attribute
1685 // (offset, type), the shader location and the index of the binding
1686 // QRhiCommandBuffer::VertexInput -> binds a buffer to a binding
1687 struct BufferBinding
1688 {
1689 Qt3DCore::QNodeId bufferId;
1690 uint stride;
1691 QRhiVertexInputBinding::Classification classification;
1692 uint attributeDivisor;
1693 };
1694 std::vector<BufferBinding> uniqueBindings;
1695
1696 const auto &attributesIds = geometry->attributes();
1697
1698 for (Qt3DCore::QNodeId attribute_id : attributesIds) {
1699 Attribute *attrib = m_nodesManager->attributeManager()->lookupResource(id: attribute_id);
1700 if (attrib->attributeType() != Qt3DCore::QAttribute::VertexAttribute)
1701 continue;
1702 const int location = locationForAttribute(attr: attrib, shader);
1703 // In case the shader doesn't use the attribute, we would get no
1704 // location. This is not a failure, just that we provide more attributes
1705 // than required.
1706 if (location == -1)
1707 continue;
1708
1709 const bool isPerInstanceAttr = attrib->divisor() != 0;
1710 const QRhiVertexInputBinding::Classification classification = isPerInstanceAttr
1711 ? QRhiVertexInputBinding::PerInstance
1712 : QRhiVertexInputBinding::PerVertex;
1713
1714 auto getAttributeByteSize = [](const Qt3DCore::QAttribute::VertexBaseType type) {
1715 switch (type) {
1716 case Qt3DCore::QAttribute::Byte:
1717 case Qt3DCore::QAttribute::UnsignedByte:
1718 return 1;
1719 case Qt3DCore::QAttribute::Short:
1720 case Qt3DCore::QAttribute::UnsignedShort:
1721 case Qt3DCore::QAttribute::HalfFloat:
1722 return 2;
1723 case Qt3DCore::QAttribute::Int:
1724 case Qt3DCore::QAttribute::UnsignedInt:
1725 case Qt3DCore::QAttribute::Float:
1726 return 4;
1727 case Qt3DCore::QAttribute::Double:
1728 return 8;
1729 }
1730 return 0;
1731 };
1732
1733 uint byteStride = attrib->byteStride();
1734 const uint vertexTypeByteSize = getAttributeByteSize(attrib->vertexBaseType());
1735 // in GL 0 means tighly packed, we therefore assume a tighly packed
1736 // attribute and compute the stride if that happens
1737 if (byteStride == 0)
1738 byteStride = attrib->vertexSize() * vertexTypeByteSize;
1739
1740 const BufferBinding binding = { .bufferId: attrib->bufferId(), .stride: byteStride,
1741 .classification: classification,
1742 .attributeDivisor: isPerInstanceAttr ? attrib->divisor() : 1U };
1743
1744
1745 const auto it = std::find_if(first: uniqueBindings.begin(), last: uniqueBindings.end(),
1746 pred: [binding](const BufferBinding &a) {
1747 return binding.bufferId == a.bufferId
1748 && binding.stride == a.stride
1749 && binding.classification == a.classification
1750 && binding.attributeDivisor == a.attributeDivisor;
1751 });
1752
1753 int bindingIndex = int(uniqueBindings.size());
1754 if (it == uniqueBindings.end())
1755 uniqueBindings.push_back(x: binding);
1756 else
1757 bindingIndex = std::distance(first: uniqueBindings.begin(), last: it);
1758
1759 const auto attributeType = rhiAttributeType(attr: attrib);
1760 if (!attributeType) {
1761 qCWarning(Backend) << "An attribute type is not supported" << attrib->name() << attrib->vertexBaseType();
1762 return false;
1763 }
1764
1765 // Special Handling for Matrix as Vertex Attributes
1766 // If an attribute has a size > 4 it can only be a matrix based type
1767 // for which we will actually upload several attributes at contiguous
1768 // locations
1769 const uint elementsPerColumn = 4;
1770 const int attributeSpan = std::ceil(x: float(attrib->vertexSize()) / elementsPerColumn);
1771 for (int i = 0; i < attributeSpan; ++i) {
1772 rhiAttributes.push_back(t: { bindingIndex,
1773 location + i,
1774 *attributeType,
1775 attrib->byteOffset() + (i * elementsPerColumn * vertexTypeByteSize)});
1776 }
1777
1778 attributeNameToBinding.insert(key: attrib->nameId(), value: bindingIndex);
1779 }
1780
1781 inputBindings.resize(sz: uniqueBindings.size());
1782 for (int i = 0, m = int(uniqueBindings.size()); i < m; ++i) {
1783 const BufferBinding binding = uniqueBindings.at(n: i);
1784
1785 /*
1786 qDebug() << "binding"
1787 << binding.bufferId
1788 << binding.stride
1789 << binding.classification
1790 << binding.attributeDivisor;
1791 //*/
1792
1793 inputBindings[i] = QRhiVertexInputBinding{ binding.stride, binding.classification,
1794 binding.attributeDivisor };
1795 }
1796
1797 return true;
1798}
1799
1800// Render Thread (or QtQuick RenderThread when using Scene3D)
1801// Scene3D: When using Scene3D rendering, we can't assume that when
1802// updateGLResources is called, the resource handles points to still existing
1803// objects. This is because Scene3D calls doRender independently of whether all
1804// jobs have completed or not which in turn calls proceedToNextFrame under some
1805// conditions. Such conditions are usually met on startup to avoid deadlocks.
1806// proceedToNextFrame triggers the syncChanges calls for the next frame, which
1807// may contain destruction changes targeting resources. When the above
1808// happens, this can result in the dirtyResource vectors containing handles of
1809// objects that may already have been destroyed
1810void Renderer::updateResources()
1811{
1812 {
1813 const std::vector<HBuffer> dirtyBufferHandles = Qt3DCore::moveAndClear(data&: m_dirtyBuffers);
1814 for (const HBuffer &handle : dirtyBufferHandles) {
1815 Buffer *buffer = m_nodesManager->bufferManager()->data(handle);
1816
1817 // Can be null when using Scene3D rendering
1818 if (buffer == nullptr)
1819 continue;
1820
1821 // Forces creation if it doesn't exit
1822 // Also note the binding point doesn't really matter here, we just upload data
1823 if (!m_submissionContext->hasRHIBufferForBuffer(buffer))
1824 m_submissionContext->rhiBufferForRenderBuffer(buf: buffer);
1825 // Update the RHIBuffer data
1826 m_submissionContext->updateBuffer(buffer);
1827 buffer->unsetDirty();
1828 }
1829 }
1830
1831 RHIGraphicsPipelineManager *graphicsPipelineManager = m_RHIResourceManagers->rhiGraphicsPipelineManager();
1832 RHIComputePipelineManager *computePipelineManager = m_RHIResourceManagers->rhiComputePipelineManager();
1833
1834 {
1835 const std::vector<HShader> dirtyShaderHandles = Qt3DCore::moveAndClear(data&: m_dirtyShaders);
1836 ShaderManager *shaderManager = m_nodesManager->shaderManager();
1837 for (const HShader &handle : dirtyShaderHandles) {
1838 Shader *shader = shaderManager->data(handle);
1839
1840 // Can be null when using Scene3D rendering
1841 if (shader == nullptr)
1842 continue;
1843
1844 // Compile shader
1845 m_submissionContext->loadShader(shader, shaderManager,
1846 rhiShaderManager: m_RHIResourceManagers->rhiShaderManager());
1847
1848 // Release pipelines that reference the shaderId
1849 // to ensure they get rebuilt with updated shader
1850 graphicsPipelineManager->releasePipelinesReferencingShader(shaderId: shader->peerId());
1851 computePipelineManager->releasePipelinesReferencingShader(shaderId: shader->peerId());
1852 }
1853 }
1854
1855 std::vector<RHITexture *> updatedRHITextures;
1856
1857 // Create/Update textures. We record the update info to later fill
1858 // m_updatedTextureProperties once we are use the RHITextures have been
1859 // fully created (as creating the RenderTargets below could change existing
1860 // RHITextures)
1861 {
1862 const std::vector<HTexture> activeTextureHandles = Qt3DCore::moveAndClear(data&: m_dirtyTextures);
1863 for (const HTexture &handle : activeTextureHandles) {
1864 Texture *texture = m_nodesManager->textureManager()->data(handle);
1865
1866 // Can be null when using Scene3D rendering
1867 if (texture == nullptr)
1868 continue;
1869
1870 // Create or Update RHITexture (the RHITexture instance is created if required
1871 // and all things that can take place without a GL context are done here)
1872 updateTexture(texture);
1873 }
1874 // We want to upload textures data at this point as the SubmissionThread and
1875 // AspectThread are locked ensuring no races between Texture/TextureImage and
1876 // RHITexture
1877 if (m_submissionContext != nullptr) {
1878 RHITextureManager *rhiTextureManager = m_RHIResourceManagers->rhiTextureManager();
1879 const std::vector<HRHITexture> &rhiTextureHandles = rhiTextureManager->activeHandles();
1880 // Upload texture data
1881 for (const HRHITexture &rhiTextureHandle : rhiTextureHandles) {
1882 RHI_UNIMPLEMENTED;
1883 RHITexture *rhiTexture = rhiTextureManager->data(handle: rhiTextureHandle);
1884
1885 // We create/update the actual RHI texture using the RHI context at this point
1886 const RHITexture::TextureUpdateInfo info =
1887 rhiTexture->createOrUpdateRhiTexture(ctx: m_submissionContext.data());
1888
1889 if (info.wasUpdated) {
1890 // RHITexture creation provides us width/height/format ... information
1891 // for textures which had not initially specified these information
1892 // (TargetAutomatic...) Gather these information and store them to be distributed by
1893 // a change next frame
1894 const Qt3DCore::QNodeIdVector referenceTextureIds = { rhiTextureManager->texNodeIdForRHITexture.value(key: rhiTexture) };
1895 // Store properties and referenceTextureIds
1896 Texture::TextureUpdateInfo updateInfo;
1897 updateInfo.properties = info.properties;
1898 // Record texture updates to notify frontend (we are sure at this stage
1899 // that the internal QRHITexture won't be updated further for this frame
1900 m_updatedTextureProperties.push_back(x: { updateInfo, referenceTextureIds });
1901 updatedRHITextures.push_back(x: rhiTexture);
1902 }
1903 }
1904 }
1905
1906 // Record ids of texture to cleanup while we are still blocking the aspect thread
1907 m_textureIdsToCleanup += m_nodesManager->textureManager()->takeTexturesIdsToCleanup();
1908 }
1909
1910
1911 // Find dirty renderTargets
1912 // -> attachments added/removed
1913 // -> attachments textures updated (new dimensions, format ...)
1914 // -> destroy pipelines associated with dirty renderTargets
1915
1916 // Note: we might end up recreating some of the internal textures when
1917 // creating the RenderTarget as those might have been created above without
1918 // the proper RenderTarget/TransformSource flags
1919 {
1920 RHIRenderTargetManager *rhiRenderTargetManager = m_RHIResourceManagers->rhiRenderTargetManager();
1921 RenderTargetManager *renderTargetManager = m_nodesManager->renderTargetManager();
1922 AttachmentManager *attachmentManager = m_nodesManager->attachmentManager();
1923 const std::vector<HTarget> &activeHandles = renderTargetManager->activeHandles();
1924 for (const HTarget &hTarget : activeHandles) {
1925 const Qt3DCore::QNodeId renderTargetId = hTarget->peerId();
1926 bool isDirty = hTarget->isDirty() || !rhiRenderTargetManager->contains(id: renderTargetId);
1927
1928 // Check dirtiness of attachments if RenderTarget is not dirty
1929 if (!isDirty) {
1930 const Qt3DCore::QNodeIdVector &attachmentIds = hTarget->renderOutputs();
1931 for (const Qt3DCore::QNodeId &attachmentId : attachmentIds) {
1932 RenderTargetOutput *output = attachmentManager->lookupResource(id: attachmentId);
1933
1934 auto it = std::find_if(first: m_updatedTextureProperties.begin(),
1935 last: m_updatedTextureProperties.end(),
1936 pred: [&output] (const QPair<Texture::TextureUpdateInfo, Qt3DCore::QNodeIdVector> &updateData) {
1937 const Qt3DCore::QNodeIdVector &referencedTextureIds = updateData.second;
1938 return referencedTextureIds.contains(t: output->textureUuid());
1939 });
1940 // Attachment references a texture which was updated
1941 isDirty = (it != m_updatedTextureProperties.end());
1942 }
1943 }
1944
1945 if (isDirty) {
1946 hTarget->unsetDirty();
1947 // We need to destroy the render target and the pipelines associated with it
1948 // so that they can be recreated
1949 // If the RT was never created, the 2 lines below are noop
1950 graphicsPipelineManager->releasePipelinesReferencingRenderTarget(renderTargetId);
1951 rhiRenderTargetManager->releaseResource(id: renderTargetId);
1952
1953 // Create RenderTarget
1954 createRenderTarget(target: hTarget.data());
1955 }
1956 }
1957 }
1958
1959
1960 // Note: we can only retrieve the internal QRhiResource handle to set on
1961 // the frontend nodes after we are sure we are no going to modify the
1962 // QRhiTextures (which happens when we create the Textures or the
1963 // RenderTargets)
1964 {
1965 for (size_t i = 0, m = m_updatedTextureProperties.size(); i < m; ++i) {
1966 auto &updateInfoPair = m_updatedTextureProperties[i];
1967 RHITexture *rhiTexture = updatedRHITextures[i];
1968 QRhiTexture *qRhiTexture = rhiTexture->getRhiTexture();
1969 Texture::TextureUpdateInfo &updateInfo = updateInfoPair.first;
1970 updateInfo.handleType = QAbstractTexture::RHITextureId;
1971 updateInfo.handle = qRhiTexture ? QVariant(qRhiTexture->nativeTexture().object) : QVariant();
1972 }
1973 }
1974
1975 // Record list of buffer that might need uploading
1976 lookForDownloadableBuffers();
1977}
1978
1979// Render Thread
1980void Renderer::updateTexture(Texture *texture)
1981{
1982 RHI_UNIMPLEMENTED;
1983 // Check that the current texture images are still in place, if not, do not update
1984 const bool isValid = texture->isValid(manager: m_nodesManager->textureImageManager());
1985 if (!isValid) {
1986 qCWarning(Backend) << "QTexture referencing invalid QTextureImages";
1987 return;
1988 }
1989
1990 // All textures are unique, if you instanciate twice the exact same texture
1991 // this will create 2 identical GLTextures, no sharing will take place
1992
1993 // Try to find the associated RHITexture for the backend Texture
1994 RHITextureManager *rhiTextureManager = m_RHIResourceManagers->rhiTextureManager();
1995 RHITexture *rhiTexture = rhiTextureManager->lookupResource(id: texture->peerId());
1996
1997 // No RHITexture associated yet -> create it
1998 if (rhiTexture == nullptr) {
1999 rhiTexture = rhiTextureManager->getOrCreateResource(id: texture->peerId());
2000 rhiTextureManager->texNodeIdForRHITexture.insert(key: rhiTexture, value: texture->peerId());
2001 }
2002
2003 // Update RHITexture to match Texture instance
2004 const Texture::DirtyFlags dirtyFlags = texture->dirtyFlags();
2005 if (dirtyFlags.testFlag(flag: Texture::DirtySharedTextureId))
2006 rhiTexture->setSharedTextureId(texture->sharedTextureId());
2007
2008 if (dirtyFlags.testFlag(flag: Texture::DirtyProperties))
2009 rhiTexture->setProperties(texture->properties());
2010
2011 if (dirtyFlags.testFlag(flag: Texture::DirtyParameters))
2012 rhiTexture->setParameters(texture->parameters());
2013
2014 // Will make the texture requestUpload
2015 if (dirtyFlags.testFlag(flag: Texture::DirtyImageGenerators)) {
2016 const Qt3DCore::QNodeIdVector textureImageIds = texture->textureImageIds();
2017 std::vector<RHITexture::Image> images;
2018 images.reserve(n: textureImageIds.size());
2019 // TODO: Move this into RHITexture directly
2020 for (const Qt3DCore::QNodeId &textureImageId : textureImageIds) {
2021 const TextureImage *img =
2022 m_nodesManager->textureImageManager()->lookupResource(id: textureImageId);
2023 if (img == nullptr) {
2024 qCWarning(Backend) << "invalid TextureImage handle";
2025 } else {
2026 RHITexture::Image glImg { .generator: img->dataGenerator(), .layer: img->layer(), .mipLevel: img->mipLevel(),
2027 .face: img->face() };
2028 images.push_back(x: glImg);
2029 }
2030 }
2031 rhiTexture->setImages(images);
2032 }
2033
2034 // Will make the texture requestUpload
2035 if (dirtyFlags.testFlag(flag: Texture::DirtyDataGenerator))
2036 rhiTexture->setGenerator(texture->dataGenerator());
2037
2038 // Will make the texture requestUpload
2039 if (dirtyFlags.testFlag(flag: Texture::DirtyPendingDataUpdates))
2040 rhiTexture->addTextureDataUpdates(updates: texture->takePendingTextureDataUpdates());
2041
2042 // Unset the dirty flag on the texture
2043 texture->unsetDirty();
2044}
2045
2046// Render Thread
2047void Renderer::cleanupTexture(Qt3DCore::QNodeId cleanedUpTextureId)
2048{
2049 RHITextureManager *rhiTextureManager = m_RHIResourceManagers->rhiTextureManager();
2050 RHITexture *glTexture = rhiTextureManager->lookupResource(id: cleanedUpTextureId);
2051
2052 // Destroying the RHITexture implicitely also destroy the GL resources
2053 if (glTexture != nullptr) {
2054 rhiTextureManager->releaseResource(id: cleanedUpTextureId);
2055 rhiTextureManager->texNodeIdForRHITexture.remove(key: glTexture);
2056 }
2057}
2058
2059// Render Thread
2060void Renderer::cleanupShader(const Shader *shader)
2061{
2062 RHIShaderManager *rhiShaderManager = m_RHIResourceManagers->rhiShaderManager();
2063 RHIShader *glShader = rhiShaderManager->lookupResource(shaderId: shader->peerId());
2064
2065 if (glShader != nullptr)
2066 rhiShaderManager->abandon(apiShader: glShader, shader);
2067}
2068
2069void Renderer::cleanupRenderTarget(const Qt3DCore::QNodeId &renderTargetId)
2070{
2071 RHIRenderTargetManager *rhiRenderTargetManager = m_RHIResourceManagers->rhiRenderTargetManager();
2072
2073 rhiRenderTargetManager->releaseResource(id: renderTargetId);
2074}
2075
2076// Called by SubmitRenderView
2077void Renderer::downloadRHIBuffers()
2078{
2079 const std::vector<Qt3DCore::QNodeId> downloadableHandles = Qt3DCore::moveAndClear(data&: m_downloadableBuffers);
2080 for (const Qt3DCore::QNodeId &bufferId : downloadableHandles) {
2081 BufferManager *bufferManager = m_nodesManager->bufferManager();
2082 BufferManager::ReadLocker locker(const_cast<const BufferManager *>(bufferManager));
2083 Buffer *buffer = bufferManager->lookupResource(id: bufferId);
2084 // Buffer could have been destroyed at this point
2085 if (!buffer)
2086 continue;
2087 // locker is protecting us from the buffer being destroy while we're looking
2088 // up its content
2089 const QByteArray content = m_submissionContext->downloadBufferContent(buffer);
2090 m_sendBufferCaptureJob->addRequest(request: QPair<Qt3DCore::QNodeId, QByteArray>(bufferId, content));
2091 }
2092}
2093
2094// Happens in RenderThread context when all RenderViewJobs are done
2095// Returns the id of the last bound FBO
2096Renderer::ViewSubmissionResultData
2097Renderer::submitRenderViews(const std::vector<RHIPassInfo> &rhiPassesInfo)
2098{
2099 QElapsedTimer timer;
2100 quint64 queueElapsed = 0;
2101 timer.start();
2102
2103 quint64 frameElapsed = queueElapsed;
2104 m_lastFrameCorrect.storeRelaxed(newValue: 1); // everything fine until now.....
2105
2106 qCDebug(Memory) << Q_FUNC_INFO << "rendering frame ";
2107
2108 // We might not want to render on the default FBO
2109 QSurface *surface = nullptr;
2110 QSurface *previousSurface = nullptr;
2111 QSurface *lastUsedSurface = nullptr;
2112
2113 const size_t rhiPassesCount = rhiPassesInfo.size();
2114
2115 for (size_t i = 0; i < rhiPassesCount; ++i) {
2116 // Initialize GraphicsContext for drawing
2117 const RHIPassInfo &rhiPassInfo = rhiPassesInfo.at(n: i);
2118
2119 // Initialize Previous surface the first time we enter this loop
2120 if (i == 0) {
2121 for (const RenderView *rv : rhiPassInfo.rvs) {
2122 previousSurface = rv->surface();
2123 if (previousSurface)
2124 break;
2125 }
2126 }
2127
2128 // Check if using the same surface as the previous RHIPassInfo.
2129 // If not, we have to free up the context from the previous surface
2130 // and make the context current on the new surface
2131 surface = rhiPassInfo.surface;
2132 SurfaceLocker surfaceLock(surface);
2133
2134 // TO DO: Make sure that the surface we are rendering too has not been unset
2135
2136 // For now, if we do not have a surface, skip this rhipassinfo
2137 // TODO: Investigate if it's worth providing a fallback offscreen surface
2138 // to use when surface is null. Or if we should instead expose an
2139 // offscreensurface to Qt3D.
2140 if (!surface || !surfaceLock.isSurfaceValid()) {
2141 m_lastFrameCorrect.storeRelaxed(newValue: 0);
2142 continue;
2143 }
2144
2145 lastUsedSurface = surface;
2146 const bool surfaceHasChanged = surface != previousSurface;
2147
2148 if (surfaceHasChanged && previousSurface) {
2149 // TO DO: Warn that this likely won't work with Scene3D
2150
2151 // TODO what should be the swapBuffers condition for RHI ?
2152 // lastRenderTarget == swapChain->renderTarget or something like that ?
2153 const bool swapBuffers = surfaceLock.isSurfaceValid() && m_shouldSwapBuffers;
2154 // We only call swap buffer if we are sure the previous surface is still valid
2155 m_submissionContext->endDrawing(swapBuffers);
2156 }
2157
2158 if (surfaceHasChanged) {
2159 // TO DO: Warn that this likely won't work with Scene3D
2160
2161 // If we can't make the context current on the surface, skip to the
2162 // next RenderView. We won't get the full frame but we may get something
2163 if (!m_submissionContext->beginDrawing(surface)) {
2164 qCWarning(Backend) << "Failed to make RHI context current on surface";
2165 m_lastFrameCorrect.storeRelaxed(newValue: 0);
2166 continue;
2167 }
2168
2169 previousSurface = surface;
2170 }
2171
2172 // Execute the render commands
2173 if (!executeCommandsSubmission(passInfo: rhiPassInfo))
2174 m_lastFrameCorrect.storeRelaxed(
2175 newValue: 0); // something went wrong; make sure to render the next frame!
2176
2177 frameElapsed = timer.elapsed() - frameElapsed;
2178 qCDebug(Rendering) << Q_FUNC_INFO << "Submitted RHI Passes " << i + 1 << "/"
2179 << rhiPassesCount << "in " << frameElapsed << "ms";
2180 frameElapsed = timer.elapsed();
2181 }
2182
2183 //* TODO: Shouldn't be needed with RHI ? as FBOs, etc.. are per-pipeline
2184 //* // Bind lastBoundFBOId back. Needed also in threaded mode.
2185 //* // lastBoundFBOId != m_graphicsContext->activeFBO() when the last FrameGraph leaf
2186 //* // node/renderView contains RenderTargetSelector/RenderTarget
2187 //* if (lastBoundFBOId != m_submissionContext->activeFBO()) {
2188 //* RHI_UNIMPLEMENTED;
2189 //* // m_submissionContext->bindFramebuffer(lastBoundFBOId,
2190 //* // GraphicsHelperInterface::FBOReadAndDraw);
2191 //* }
2192
2193 queueElapsed = timer.elapsed() - queueElapsed;
2194 qCDebug(Rendering) << Q_FUNC_INFO << "Submission Completed in " << timer.elapsed() << "ms";
2195
2196 // Stores the necessary information to safely perform
2197 // the last swap buffer call
2198 ViewSubmissionResultData resultData;
2199 resultData.surface = lastUsedSurface;
2200 return resultData;
2201}
2202
2203void Renderer::markDirty(BackendNodeDirtySet changes, BackendNode *node)
2204{
2205 Q_UNUSED(node);
2206 m_dirtyBits.marked |= changes;
2207}
2208
2209Renderer::BackendNodeDirtySet Renderer::dirtyBits()
2210{
2211 return m_dirtyBits.marked;
2212}
2213
2214#if defined(QT_BUILD_INTERNAL)
2215void Renderer::clearDirtyBits(BackendNodeDirtySet changes)
2216{
2217 m_dirtyBits.remaining &= ~changes;
2218 m_dirtyBits.marked &= ~changes;
2219}
2220#endif
2221
2222bool Renderer::shouldRender() const
2223{
2224 // Only render if something changed during the last frame, or the last frame
2225 // was not rendered successfully (or render-on-demand is disabled)
2226 return (m_settings->renderPolicy() == QRenderSettings::Always || m_dirtyBits.marked != 0
2227 || m_dirtyBits.remaining != 0 || !m_lastFrameCorrect.loadRelaxed());
2228}
2229
2230void Renderer::skipNextFrame()
2231{
2232 Q_ASSERT(m_settings->renderPolicy() != QRenderSettings::Always);
2233
2234 // make submitRenderViews() actually run
2235 m_renderQueue.setNoRender();
2236 m_submitRenderViewsSemaphore.release(n: 1);
2237}
2238
2239void Renderer::jobsDone(Qt3DCore::QAspectManager *manager)
2240{
2241 // called in main thread once all jobs are done running
2242
2243 // sync captured renders to frontend
2244 QMutexLocker lock(&m_pendingRenderCaptureSendRequestsMutex);
2245 const std::vector<Qt3DCore::QNodeId> pendingCaptureIds =
2246 Qt3DCore::moveAndClear(data&: m_pendingRenderCaptureSendRequests);
2247 lock.unlock();
2248 for (const Qt3DCore::QNodeId &id : std::as_const(t: pendingCaptureIds)) {
2249 auto *backend = static_cast<Qt3DRender::Render::RenderCapture *>(
2250 m_nodesManager->frameGraphManager()->lookupNode(id));
2251 backend->syncRenderCapturesToFrontend(manager);
2252 }
2253
2254 // Do we need to notify any texture about property changes?
2255 if (!m_updatedTextureProperties.empty())
2256 sendTextureChangesToFrontend(manager);
2257
2258 sendDisablesToFrontend(manager);
2259}
2260
2261bool Renderer::processMouseEvent(QObject *object, QMouseEvent *event)
2262{
2263 Q_UNUSED(object);
2264 Q_UNUSED(event);
2265 return false;
2266}
2267
2268bool Renderer::processKeyEvent(QObject *object, QKeyEvent *event)
2269{
2270 Q_UNUSED(object);
2271 Q_UNUSED(event);
2272 return false;
2273}
2274
2275// Jobs we may have to run even if no rendering will happen
2276std::vector<Qt3DCore::QAspectJobPtr> Renderer::preRenderingJobs()
2277{
2278 if (m_sendBufferCaptureJob->hasRequests())
2279 return { m_sendBufferCaptureJob };
2280 else
2281 return {};
2282}
2283
2284// Waits to be told to create jobs for the next frame
2285// Called by QRenderAspect jobsToExecute context of QAspectThread
2286// Returns all the jobs (and with proper dependency chain) required
2287// for the rendering of the scene
2288std::vector<Qt3DCore::QAspectJobPtr> Renderer::renderBinJobs()
2289{
2290 std::vector<Qt3DCore::QAspectJobPtr> renderBinJobs;
2291
2292 // Remove previous dependencies
2293 m_cleanupJob->removeDependency(dependency: QWeakPointer<Qt3DCore::QAspectJob>());
2294
2295 const bool dirtyParametersForCurrentFrame = m_dirtyBits.marked & AbstractRenderer::ParameterDirty;
2296 const BackendNodeDirtySet dirtyBitsForFrame = m_dirtyBits.marked | m_dirtyBits.remaining;
2297 m_dirtyBits.marked = {};
2298 m_dirtyBits.remaining = {};
2299 BackendNodeDirtySet notCleared = {};
2300
2301 // Add jobs
2302 if (dirtyBitsForFrame & AbstractRenderer::TransformDirty) {
2303 renderBinJobs.push_back(x: m_updateShaderDataTransformJob);
2304 }
2305
2306 // TO DO: Conditionally add if skeletons dirty
2307 renderBinJobs.push_back(x: m_cleanupJob);
2308
2309 // Jobs to prepare RHI Resource upload
2310 if (dirtyBitsForFrame & AbstractRenderer::BuffersDirty)
2311 renderBinJobs.push_back(x: m_bufferGathererJob);
2312
2313 if (dirtyBitsForFrame & AbstractRenderer::TexturesDirty)
2314 renderBinJobs.push_back(x: m_textureGathererJob);
2315
2316 // Layer cache is dependent on layers, layer filters (hence FG structure
2317 // changes) and the enabled flag on entities
2318 const bool entitiesEnabledDirty = dirtyBitsForFrame & AbstractRenderer::EntityEnabledDirty;
2319 const bool frameGraphDirty = dirtyBitsForFrame & AbstractRenderer::FrameGraphDirty;
2320 const bool layersDirty = dirtyBitsForFrame & AbstractRenderer::LayersDirty;
2321 const bool layersCacheNeedsToBeRebuilt = layersDirty || entitiesEnabledDirty || frameGraphDirty;
2322 const bool shadersDirty = dirtyBitsForFrame & AbstractRenderer::ShadersDirty;
2323 const bool materialDirty = dirtyBitsForFrame & AbstractRenderer::MaterialDirty;
2324 const bool lightsDirty = dirtyBitsForFrame & AbstractRenderer::LightsDirty;
2325 const bool computeableDirty = dirtyBitsForFrame & AbstractRenderer::ComputeDirty;
2326 const bool renderableDirty = dirtyBitsForFrame & AbstractRenderer::GeometryDirty;
2327 const bool materialCacheNeedsToBeRebuilt = shadersDirty || materialDirty || frameGraphDirty;
2328 const bool renderCommandsDirty =
2329 materialCacheNeedsToBeRebuilt || renderableDirty || computeableDirty;
2330
2331 // Rebuild Entity Layers list if layers are dirty
2332
2333 if (renderableDirty)
2334 renderBinJobs.push_back(x: m_renderableEntityFilterJob);
2335
2336 if (computeableDirty)
2337 renderBinJobs.push_back(x: m_computableEntityFilterJob);
2338
2339 if (lightsDirty)
2340 renderBinJobs.push_back(x: m_lightGathererJob);
2341
2342 QMutexLocker lock(m_renderQueue.mutex());
2343 if (m_renderQueue.wasReset()) { // Have we rendered yet? (Scene3D case)
2344 // Traverse the current framegraph. For each leaf node create a
2345 // RenderView and set its configuration then create a job to
2346 // populate the RenderView with a set of RenderCommands that get
2347 // their details from the RenderNodes that are visible to the
2348 // Camera selected by the framegraph configuration
2349 if (frameGraphDirty) {
2350 FrameGraphVisitor visitor(m_nodesManager->frameGraphManager());
2351 m_frameGraphLeaves = visitor.traverse(root: frameGraphRoot());
2352 // Remove leaf nodes that no longer exist from cache
2353 const QList<FrameGraphNode *> keys = m_cache.leafNodeCache.keys();
2354 for (FrameGraphNode *leafNode : keys) {
2355 if (std::find(first: m_frameGraphLeaves.begin(),
2356 last: m_frameGraphLeaves.end(),
2357 val: leafNode) == m_frameGraphLeaves.end())
2358 m_cache.leafNodeCache.remove(key: leafNode);
2359 }
2360
2361 // Handle single shot subtree enablers
2362 const auto subtreeEnablers = visitor.takeEnablersToDisable();
2363 for (auto *node : subtreeEnablers)
2364 m_updatedDisableSubtreeEnablers.push_back(x: node->peerId());
2365 }
2366
2367 int idealThreadCount = Qt3DCore::QAspectJobManager::idealThreadCount();
2368
2369 const size_t fgBranchCount = m_frameGraphLeaves.size();
2370 if (fgBranchCount > 1) {
2371 int workBranches = int(fgBranchCount);
2372 for (auto leaf: m_frameGraphLeaves)
2373 if (leaf->nodeType() == FrameGraphNode::NoDraw)
2374 --workBranches;
2375
2376 if (idealThreadCount > 4 && workBranches)
2377 idealThreadCount = qMax(a: 4, b: idealThreadCount / workBranches);
2378 }
2379
2380 for (size_t i = 0; i < fgBranchCount; ++i) {
2381 FrameGraphNode *leaf = m_frameGraphLeaves.at(n: i);
2382 RenderViewBuilder builder(leaf, int(i), this);
2383 builder.setOptimalJobCount(leaf->nodeType() == FrameGraphNode::NoDraw ? 1 : idealThreadCount);
2384
2385 // If we have a new RV (wasn't in the cache before, then it contains no cached data)
2386 const bool isNewRV = !m_cache.leafNodeCache.contains(key: leaf);
2387 builder.setLayerCacheNeedsToBeRebuilt(layersCacheNeedsToBeRebuilt || isNewRV);
2388 builder.setMaterialGathererCacheNeedsToBeRebuilt(materialCacheNeedsToBeRebuilt
2389 || isNewRV);
2390 builder.setRenderCommandCacheNeedsToBeRebuilt(renderCommandsDirty || isNewRV);
2391 builder.setLightCacheNeedsToBeRebuilt(lightsDirty);
2392
2393 // Insert leaf into cache
2394 if (isNewRV) {
2395 m_cache.leafNodeCache[leaf] = {};
2396 }
2397
2398 builder.prepareJobs();
2399 Qt3DCore::moveAtEnd(destination&: renderBinJobs, source: builder.buildJobHierachy());
2400 }
2401
2402 // Set target number of RenderViews
2403 m_renderQueue.setTargetRenderViewCount(int(fgBranchCount));
2404 } else {
2405 // FilterLayerEntityJob is part of the RenderViewBuilder jobs and must be run later
2406 // if none of those jobs are started this frame
2407 notCleared |= AbstractRenderer::EntityEnabledDirty;
2408 notCleared |= AbstractRenderer::FrameGraphDirty;
2409 notCleared |= AbstractRenderer::LayersDirty;
2410 }
2411
2412 if (isRunning() && m_submissionContext->isInitialized()) {
2413 if (dirtyBitsForFrame & AbstractRenderer::TechniquesDirty)
2414 renderBinJobs.push_back(x: m_filterCompatibleTechniqueJob);
2415 if (dirtyBitsForFrame & AbstractRenderer::ShadersDirty)
2416 renderBinJobs.push_back(x: m_introspectShaderJob);
2417 } else {
2418 notCleared |= AbstractRenderer::TechniquesDirty;
2419 notCleared |= AbstractRenderer::ShadersDirty;
2420 }
2421
2422 m_dirtyBits.remaining = dirtyBitsForFrame & notCleared;
2423
2424 // Dirty Parameters might need 2 frames to react if the parameter references a texture
2425 if (dirtyParametersForCurrentFrame)
2426 m_dirtyBits.remaining |= AbstractRenderer::ParameterDirty;
2427
2428 return renderBinJobs;
2429}
2430
2431Qt3DCore::QAbstractFrameAdvanceService *Renderer::frameAdvanceService() const
2432{
2433 return static_cast<Qt3DCore::QAbstractFrameAdvanceService *>(m_vsyncFrameAdvanceService.data());
2434}
2435
2436bool Renderer::performCompute(QRhiCommandBuffer *cb, RenderCommand &command)
2437{
2438 RHIComputePipeline *pipeline = command.pipeline.compute();
2439 if (!pipeline)
2440 return true;
2441 cb->setComputePipeline(pipeline->pipeline());
2442
2443 if (!setBindingAndShaderResourcesForCommand(cb, command, uboSet: pipeline->uboSet()))
2444 return false;
2445
2446 const std::vector<QRhiCommandBuffer::DynamicOffset> offsets = pipeline->uboSet()->offsets(command);
2447 cb->setShaderResources(srb: command.shaderResourceBindings,
2448 dynamicOffsetCount: int(offsets.size()),
2449 dynamicOffsets: offsets.data());
2450
2451 cb->dispatch(x: command.m_workGroups[0], y: command.m_workGroups[1], z: command.m_workGroups[2]);
2452 m_dirtyBits.marked |= AbstractRenderer::ComputeDirty;
2453 return true;
2454}
2455
2456static auto rhiIndexFormat(Qt3DCore::QAttribute::VertexBaseType type)
2457{
2458 switch (type) {
2459 case Qt3DCore::QAttribute::VertexBaseType ::UnsignedShort:
2460 return QRhiCommandBuffer::IndexUInt16;
2461 case Qt3DCore::QAttribute::VertexBaseType ::UnsignedInt:
2462 return QRhiCommandBuffer::IndexUInt32;
2463 default:
2464 std::abort();
2465 }
2466}
2467
2468bool Renderer::uploadBuffersForCommand(QRhiCommandBuffer *cb, const RenderView *rv,
2469 RenderCommand &command)
2470{
2471 Q_UNUSED(cb);
2472 Q_UNUSED(rv);
2473
2474 struct
2475 {
2476 Renderer &self;
2477 RenderCommand &command;
2478 bool operator()(RHIGraphicsPipeline* pipeline) const noexcept {
2479 if (!pipeline)
2480 return true;
2481
2482 return self.uploadBuffersForCommand(graphics: pipeline, command);
2483 }
2484 bool operator()(RHIComputePipeline* pipeline) const noexcept {
2485 if (!pipeline)
2486 return true;
2487
2488 return self.uploadBuffersForCommand(compute: pipeline, command);
2489 }
2490 bool operator()(std::monostate) {
2491 return false;
2492 }
2493 } vis{.self: *this, .command: command};
2494
2495 if (!command.pipeline.visit(f&: vis))
2496 return false;
2497
2498 for (const BlockToUBO &pack : command.m_parameterPack.uniformBuffers()) {
2499 Buffer *cpuBuffer = nodeManagers()->bufferManager()->lookupResource(id: pack.m_bufferID);
2500 RHIBuffer *ubo = m_submissionContext->rhiBufferForRenderBuffer(buf: cpuBuffer);
2501 if (!ubo->bind(ctx: &*m_submissionContext, t: RHIBuffer::UniformBuffer))
2502 return false;
2503 }
2504 for (const BlockToSSBO &pack : command.m_parameterPack.shaderStorageBuffers()) {
2505 Buffer *cpuBuffer = nodeManagers()->bufferManager()->lookupResource(id: pack.m_bufferID);
2506 RHIBuffer *ubo = m_submissionContext->rhiBufferForRenderBuffer(buf: cpuBuffer);
2507 if (!ubo->bind(ctx: &*m_submissionContext, t: RHIBuffer::ShaderStorageBuffer))
2508 return false;
2509 }
2510
2511 return true;
2512}
2513
2514bool Renderer::uploadBuffersForCommand(RHIGraphicsPipeline* graphicsPipeline, RenderCommand &command)
2515{
2516 // Create the vertex input description
2517
2518 // Note: we have to bind the buffers here -> which will trigger the actual
2519 // upload, as this is the only place where we know about the usage type of the buffers
2520
2521 const auto geom = command.m_geometry;
2522 const auto &attributes = geom->attributes();
2523 const QRhiVertexInputLayout layout = graphicsPipeline->pipeline()->vertexInputLayout();
2524 const int bindingAttributeCount = std::distance(first: layout.cbeginBindings(), last: layout.cendBindings());
2525 command.vertex_input.resize(sz: bindingAttributeCount);
2526
2527 for (Qt3DCore::QNodeId attribute_id : attributes) {
2528 // TODO isn't there a more efficient way than doing three hash lookups ?
2529 Attribute *attrib = m_nodesManager->attributeManager()->lookupResource(id: attribute_id);
2530 Buffer *buffer = m_nodesManager->bufferManager()->lookupResource(id: attrib->bufferId());
2531 RHIBuffer *hbuf = m_RHIResourceManagers->rhiBufferManager()->lookupResource(id: buffer->peerId());
2532 switch (attrib->attributeType()) {
2533 case Qt3DCore::QAttribute::VertexAttribute: {
2534 if (!hbuf->bind(ctx: &*m_submissionContext, t: RHIBuffer::Type((int)RHIBuffer::Type::ArrayBuffer | (int)RHIBuffer::Type::ShaderStorageBuffer)))
2535 return false;
2536 assert(hbuf->rhiBuffer());
2537 // Find Binding for Attribute
2538 const int bindingIndex = graphicsPipeline->bindingIndexForAttribute(attributeNameId: attrib->nameId());
2539 // We need to reference a binding, a buffer and an offset which is always = 0
2540 // as Qt3D only assumes interleaved or continuous data but not
2541 // buffer where first half of it is attribute1 and second half attribute2
2542 if (bindingIndex != -1)
2543 command.vertex_input[bindingIndex] = { hbuf->rhiBuffer(), 0 };
2544 break;
2545 }
2546 case Qt3DCore::QAttribute::IndexAttribute: {
2547 if (!hbuf->bind(ctx: &*m_submissionContext, t: RHIBuffer::Type::IndexBuffer))
2548 return false;
2549 assert(hbuf->rhiBuffer());
2550
2551 command.indexBuffer = hbuf->rhiBuffer();
2552 command.indexAttribute = attrib;
2553 break;
2554 }
2555 case Qt3DCore::QAttribute::DrawIndirectAttribute:
2556 RHI_UNIMPLEMENTED;
2557 break;
2558 }
2559 }
2560
2561 return true;
2562}
2563
2564bool Renderer::uploadBuffersForCommand(RHIComputePipeline* computePipeline, RenderCommand &command)
2565{
2566 Q_UNUSED(computePipeline);
2567 Q_UNUSED(command);
2568 return true;
2569}
2570
2571bool Renderer::performDraw(QRhiCommandBuffer *cb, const QRhiViewport &vp,
2572 const QRhiScissor *scissor, RenderCommand &command)
2573{
2574 RHIGraphicsPipeline *pipeline = command.pipeline.graphics();
2575 if (!pipeline || !pipeline->isComplete())
2576 return true;
2577
2578 // Setup the rendering pass
2579 cb->setGraphicsPipeline(pipeline->pipeline());
2580 cb->setViewport(vp);
2581 if (scissor)
2582 cb->setScissor(*scissor);
2583
2584 if (!setBindingAndShaderResourcesForCommand(cb, command, uboSet: pipeline->uboSet()))
2585 return false;
2586
2587 // Send the draw command
2588 if (Q_UNLIKELY(!command.indexBuffer)) {
2589 cb->setVertexInput(startBinding: 0, bindingCount: command.vertex_input.size(), bindings: command.vertex_input.data());
2590 cb->draw(vertexCount: command.m_primitiveCount, instanceCount: command.m_instanceCount, firstVertex: command.m_firstVertex,
2591 firstInstance: command.m_firstInstance);
2592 } else {
2593 auto indexFormat = rhiIndexFormat(type: command.indexAttribute->vertexBaseType());
2594 auto indexOffset = command.indexAttribute->byteOffset();
2595 cb->setVertexInput(startBinding: 0, bindingCount: command.vertex_input.size(), bindings: command.vertex_input.data(),
2596 indexBuf: command.indexBuffer, indexOffset, indexFormat);
2597 cb->drawIndexed(indexCount: command.m_primitiveCount, instanceCount: command.m_instanceCount, firstIndex: command.m_indexOffset,
2598 vertexOffset: command.m_indexAttributeByteOffset, firstInstance: command.m_firstInstance);
2599 }
2600 return true;
2601}
2602
2603bool Renderer::setBindingAndShaderResourcesForCommand(QRhiCommandBuffer *cb,
2604 RenderCommand &command,
2605 PipelineUBOSet *uboSet)
2606{
2607 // We need to create new resource bindings for each RC as each RC might potentially
2608 // have different textures or reference custom UBOs (if using Parameters with UBOs directly).
2609 // TO DO: We could propably check for texture and use the UBO set default ShaderResourceBindings
2610 // if we only have UBOs with offsets
2611 bool needsRecreate = false;
2612 if (command.shaderResourceBindings == nullptr) {
2613 command.shaderResourceBindings = m_submissionContext->rhi()->newShaderResourceBindings();
2614 needsRecreate = true;
2615 }
2616
2617 // TO DO: Improve this to only perform when required
2618 const std::vector<QRhiShaderResourceBinding> &resourcesBindings = uboSet->resourceBindings(command);
2619 if (command.resourcesBindings != resourcesBindings) {
2620 command.resourcesBindings = std::move(resourcesBindings);
2621 command.shaderResourceBindings->setBindings(first: command.resourcesBindings.cbegin(), last: command.resourcesBindings.cend());
2622 needsRecreate = true;
2623 }
2624
2625 if (needsRecreate && !command.shaderResourceBindings->create()) {
2626 qCWarning(Backend) << "Failed to create ShaderResourceBindings";
2627 return false;
2628 }
2629 const std::vector<QRhiCommandBuffer::DynamicOffset> offsets = uboSet->offsets(command);
2630
2631 cb->setShaderResources(srb: command.shaderResourceBindings,
2632 dynamicOffsetCount: int(offsets.size()),
2633 dynamicOffsets: offsets.data());
2634 return true;
2635}
2636
2637// Called by RenderView->submit() in RenderThread context
2638// Returns true, if all RenderCommands were sent to the GPU
2639bool Renderer::executeCommandsSubmission(const RHIPassInfo &passInfo)
2640{
2641 bool allCommandsIssued = true;
2642
2643 const std::vector<RenderView *> &renderViews = passInfo.rvs;
2644 QColor clearColor;
2645 QRhiDepthStencilClearValue clearDepthStencil;
2646
2647 // Submit the commands to the underlying graphics API (RHI)
2648 QRhiCommandBuffer *cb = m_submissionContext->currentFrameCommandBuffer();
2649
2650 // Upload data for all RenderCommands
2651 for (RenderView *rv : renderViews) {
2652 // Render drawing commands
2653
2654 // Upload UBOs for pipelines used in current RV
2655 const std::vector<RHIGraphicsPipeline *> &rvGraphicsPipelines = m_rvToGraphicsPipelines[rv];
2656 for (RHIGraphicsPipeline *pipeline : rvGraphicsPipelines)
2657 pipeline->uboSet()->uploadUBOs(ctx: m_submissionContext.data(), rv);
2658
2659 const std::vector<RHIComputePipeline *> &rvComputePipelines = m_rvToComputePipelines[rv];
2660 for (RHIComputePipeline *pipeline : rvComputePipelines)
2661 pipeline->uboSet()->uploadUBOs(ctx: m_submissionContext.data(), rv);
2662
2663 // Upload Buffers for Commands
2664 rv->forEachCommand(func: [&] (RenderCommand &command) {
2665 if (Q_UNLIKELY(!command.isValid()))
2666 return;
2667
2668 if (!uploadBuffersForCommand(cb, rv, command)) {
2669 // Something went wrong trying to upload buffers
2670 // -> likely that frontend buffer has no initial data
2671 qCWarning(Backend) << "Failed to upload buffers";
2672 // Prevents further processing which could be catastrophic
2673 command.m_isValid = false;
2674 }
2675 });
2676
2677 // Record clear information
2678 if (rv->clearTypes() != QClearBuffers::None) {
2679 clearColor = [=] {
2680 auto col = rv->globalClearColorBufferInfo().clearColor;
2681 return QColor::fromRgbF(r: col.x(), g: col.y(), b: col.z(), a: col.w());
2682 }();
2683 clearDepthStencil = { rv->clearDepthValue(), (quint32)rv->clearStencilValue() };
2684 }
2685 }
2686
2687 // Lookup the render target
2688 QRhiRenderTarget *rhiRenderTarget{};
2689 {
2690 const auto &managers = *m_RHIResourceManagers;
2691 auto &renderTargetManager = *managers.rhiRenderTargetManager();
2692 auto *renderTarget = renderTargetManager.lookupResource(id: passInfo.renderTargetId);
2693
2694 if (renderTarget) {
2695 // Is our RenderTarget targeting offscreen attachments?
2696 if (renderTarget->backBuffer == RHIRenderTarget::BackBuffer::None)
2697 rhiRenderTarget = renderTarget->renderTarget;
2698 else // Or one of the back buffers?
2699 rhiRenderTarget = m_submissionContext->currentSwapChain()->currentFrameRenderTarget(targetBuffer: renderTarget->backBuffer == RHIRenderTarget::BackBuffer::Left
2700 ? QRhiSwapChain::LeftBuffer
2701 : QRhiSwapChain::RightBuffer);
2702 } else if (m_submissionContext->defaultRenderTarget()) {
2703 rhiRenderTarget = m_submissionContext->defaultRenderTarget();
2704 } else {
2705 rhiRenderTarget = m_submissionContext->currentSwapChain()->currentFrameRenderTarget();
2706 }
2707 }
2708
2709 auto executeDrawRenderView = [&] (RenderView* rv) {
2710 // Viewport
2711 QRhiViewport vp;
2712 QRhiScissor scissor;
2713 bool hasScissor = false;
2714 {
2715 const QSize surfaceSize = rhiRenderTarget->pixelSize();
2716
2717 const float x = rv->viewport().x() * surfaceSize.width();
2718 const float y = (1. - rv->viewport().y() - rv->viewport().height()) * surfaceSize.height();
2719 const float w = rv->viewport().width() * surfaceSize.width();
2720 const float h = rv->viewport().height() * surfaceSize.height();
2721 // qDebug() << surfaceSize << x << y << w << h;
2722 vp = { x, y, w, h };
2723 }
2724 // Scissoring
2725 {
2726 RenderStateSet *ss = rv->stateSet();
2727 if (ss == nullptr)
2728 ss = m_defaultRenderStateSet;
2729 StateVariant *scissorTestSVariant =
2730 m_submissionContext->getState(ss, type: StateMask::ScissorStateMask);
2731 if (scissorTestSVariant) {
2732 const ScissorTest *scissorTest =
2733 static_cast<const ScissorTest *>(scissorTestSVariant->constState());
2734 const auto &scissorValues = scissorTest->values();
2735 scissor = { std::get<0>(t: scissorValues), std::get<1>(t: scissorValues),
2736 std::get<2>(t: scissorValues), std::get<3>(t: scissorValues) };
2737 hasScissor = true;
2738 }
2739 }
2740
2741 // Render drawing commands
2742 rv->forEachCommand(func: [&] (RenderCommand &command) {
2743 if (Q_UNLIKELY(!command.isValid()))
2744 return;
2745
2746 Q_ASSERT (command.m_type == RenderCommand::Draw);
2747 performDraw(cb, vp, scissor: hasScissor ? &scissor : nullptr, command);
2748 });
2749 };
2750
2751 auto executeComputeRenderView = [&] (RenderView* rv) {
2752 rv->forEachCommand(func: [&] (RenderCommand &command) {
2753 if (Q_UNLIKELY(!command.isValid()))
2754 return;
2755
2756 Q_ASSERT (command.m_type == RenderCommand::Compute);
2757 performCompute(cb, command);
2758 });
2759 };
2760
2761 bool inCompute = false;
2762 bool inDraw = false;
2763
2764 // All the RVs in the current passinfo target the same RenderTarget
2765 // A single beginPass should take place, unless Computes RVs are intermingled
2766 static const bool supportsCompute = m_submissionContext->rhi()->isFeatureSupported(feature: QRhi::Compute);
2767
2768 // Per Pass Global States
2769 for (RenderView *rv : renderViews) {
2770 if (rv->isCompute()) {
2771 // If we were running draw calls we stop the draw pass
2772 if (inDraw) {
2773 cb->endPass(resourceUpdates: m_submissionContext->m_currentUpdates);
2774 m_submissionContext->m_currentUpdates = m_submissionContext->rhi()->nextResourceUpdateBatch();
2775 inDraw = false;
2776 }
2777
2778 // There is also the possibility where we weren't either in a compute or draw pass (for the first RV)
2779 // hence why these conditions are like this
2780 if (supportsCompute) {
2781 if (!inCompute) {
2782 cb->beginComputePass(resourceUpdates: m_submissionContext->m_currentUpdates);
2783 m_submissionContext->m_currentUpdates = m_submissionContext->rhi()->nextResourceUpdateBatch();
2784 inCompute = true;
2785 }
2786 executeComputeRenderView(rv);
2787 } else {
2788 qWarning() << "RHI backend doesn't support Compute";
2789 }
2790 } else {
2791 // Same logic than above but reversed
2792 if (inCompute) {
2793 cb->endComputePass(resourceUpdates: m_submissionContext->m_currentUpdates);
2794 m_submissionContext->m_currentUpdates = m_submissionContext->rhi()->nextResourceUpdateBatch();
2795 inCompute = false;
2796 }
2797
2798 if (!inDraw) {
2799 if (rhiRenderTarget == nullptr) {
2800 qWarning(catFunc: Backend) << "Invalid Render Target for pass, skipping";
2801 inDraw = false;
2802 continue;
2803 }
2804 cb->beginPass(rt: rhiRenderTarget, colorClearValue: clearColor, depthStencilClearValue: clearDepthStencil, resourceUpdates: m_submissionContext->m_currentUpdates);
2805 m_submissionContext->m_currentUpdates = m_submissionContext->rhi()->nextResourceUpdateBatch();
2806 inDraw = true;
2807 }
2808
2809 executeDrawRenderView(rv);
2810
2811 const Qt3DCore::QNodeId renderCaptureId = rv->renderCaptureNodeId();
2812 if (!renderCaptureId.isNull()) {
2813 const QRenderCaptureRequest request = rv->renderCaptureRequest();
2814 QRhiRenderTarget *rhiTarget = m_submissionContext->defaultRenderTarget();
2815 RHIRenderTarget *target = nullptr;
2816
2817 if (rv->renderTargetId()) {
2818 RHIRenderTargetManager *targetManager = m_RHIResourceManagers->rhiRenderTargetManager();
2819 target = targetManager->lookupResource(id: rv->renderTargetId());
2820 if (target != nullptr)
2821 rhiTarget = target->renderTarget;
2822 }
2823 // Use FBO size if RV has one
2824 const QSize size = rhiTarget ? rhiTarget->pixelSize() : rv->surfaceSize();
2825
2826 QRect rect(QPoint(0, 0), size);
2827 if (!request.rect.isEmpty())
2828 rect = rect.intersected(other: request.rect);
2829 if (!rect.isEmpty()) {
2830 // Bind fbo as read framebuffer
2831 QRhiReadbackResult *readBackResult = new QRhiReadbackResult;
2832 readBackResult->completed = [this, readBackResult, renderCaptureId, request] () {
2833 const QImage::Format fmt = QImage::Format_RGBA8888_Premultiplied; // fits QRhiTexture::RGBA8
2834 const uchar *p = reinterpret_cast<const uchar *>(readBackResult->data.constData());
2835 const QImage image(p, readBackResult->pixelSize.width(), readBackResult->pixelSize.height(), fmt, [] (void *ptr) {
2836 delete static_cast<QRhiReadbackResult *>(ptr);
2837 }, readBackResult);
2838
2839 Render::RenderCapture *renderCapture = static_cast<Render::RenderCapture*>(m_nodesManager->frameGraphManager()->lookupNode(id: renderCaptureId));
2840 renderCapture->addRenderCapture(captureId: request.captureId, image);
2841 QMutexLocker lock(&m_pendingRenderCaptureSendRequestsMutex);
2842 if (!Qt3DCore::contains(destination: m_pendingRenderCaptureSendRequests, element: renderCaptureId))
2843 m_pendingRenderCaptureSendRequests.push_back(x: renderCaptureId);
2844 };
2845
2846 QRhiReadbackDescription readbackDesc;
2847 if (rhiTarget) {
2848 // First texture should be Attachment 0
2849 QRhiTextureRenderTarget *textureRenderTarget = static_cast<QRhiTextureRenderTarget *>(rhiTarget);
2850 const QRhiTextureRenderTargetDescription &desc = textureRenderTarget->description();
2851 const QRhiColorAttachment *color0Att = desc.colorAttachmentAt(index: 0);
2852 readbackDesc.setTexture(color0Att->texture());
2853 }
2854 m_submissionContext->m_currentUpdates->readBackTexture(rb: readbackDesc, result: readBackResult);
2855 } else {
2856 qCWarning(Backend) << "Requested capture rectangle is outside framebuffer";
2857 }
2858 }
2859 }
2860
2861 if (rv->isDownloadBuffersEnable())
2862 downloadRHIBuffers();
2863 }
2864
2865 if (Q_LIKELY(inDraw))
2866 cb->endPass(resourceUpdates: m_submissionContext->m_currentUpdates);
2867 else if (inCompute)
2868 cb->endComputePass(resourceUpdates: m_submissionContext->m_currentUpdates);
2869
2870 m_submissionContext->m_currentUpdates = m_submissionContext->rhi()->nextResourceUpdateBatch();
2871
2872 return allCommandsIssued;
2873}
2874
2875namespace {
2876
2877template<typename Manager>
2878void removeUnusedPipelines(Manager *manager)
2879{
2880 const auto &pipelinesHandles = manager->activeHandles();
2881 std::vector<typename Manager::Handle> pipelinesToCleanup;
2882 // Store pipelines to cleanup in a temporary vector so that we can use a
2883 // ref on the activeHandles vector. Calling releaseResources modifies the
2884 // activeHandles vector which we want to avoid while iterating over it.
2885 for (const auto &pipelineHandle : pipelinesHandles) {
2886 auto *pipeline = pipelineHandle.data();
2887 Q_ASSERT(pipeline);
2888 pipeline->decreaseScore();
2889 // Pipeline wasn't used recently, let's destroy it
2890 if (pipeline->score() < 0) {
2891 pipelinesToCleanup.push_back(pipelineHandle);
2892 }
2893 }
2894
2895 // Release Pipelines marked for cleanup
2896 for (const auto &pipelineHandle : pipelinesToCleanup) {
2897 manager->releaseResource(pipelineHandle->key());
2898 }
2899}
2900
2901} // anonymous
2902
2903// Erase graphics related resources that may become unused after a frame
2904void Renderer::cleanGraphicsResources()
2905{
2906 // Remove unused Pipeline
2907 RHIGraphicsPipelineManager *graphicsPipelineManager = m_RHIResourceManagers->rhiGraphicsPipelineManager();
2908 RHIComputePipelineManager *computePipelineManager = m_RHIResourceManagers->rhiComputePipelineManager();
2909
2910 removeUnusedPipelines(manager: graphicsPipelineManager);
2911 removeUnusedPipelines(manager: computePipelineManager);
2912
2913 // Clean buffers
2914 const QList<Qt3DCore::QNodeId> buffersToRelease =
2915 m_nodesManager->bufferManager()->takeBuffersToRelease();
2916 for (Qt3DCore::QNodeId bufferId : buffersToRelease)
2917 m_submissionContext->releaseBuffer(bufferId);
2918
2919 const RHIBufferManager *bufferManager = m_RHIResourceManagers->rhiBufferManager();
2920 const std::vector<HRHIBuffer> &activeBufferHandles = bufferManager->activeHandles();
2921 // Release internal QRhiBuffer that might have been orphaned
2922 for (const HRHIBuffer &bufferH : activeBufferHandles)
2923 bufferH->destroyOrphaned();
2924
2925 // When Textures are cleaned up, their id is saved so that they can be
2926 // cleaned up in the render thread
2927 const QList<Qt3DCore::QNodeId> cleanedUpTextureIds = Qt3DCore::moveAndClear(data&: m_textureIdsToCleanup);
2928 for (const Qt3DCore::QNodeId &textureCleanedUpId : cleanedUpTextureIds)
2929 cleanupTexture(cleanedUpTextureId: textureCleanedUpId);
2930
2931 // Abandon GL shaders when a Shader node is destroyed Note: We are sure
2932 // that when this gets executed, all scene changes have been received and
2933 // shader nodes updated
2934 const QList<Qt3DCore::QNodeId> cleanedUpShaderIds =
2935 m_nodesManager->shaderManager()->takeShaderIdsToCleanup();
2936 for (const Qt3DCore::QNodeId &shaderCleanedUpId : cleanedUpShaderIds) {
2937 cleanupShader(shader: m_nodesManager->shaderManager()->lookupResource(id: shaderCleanedUpId));
2938 // We can really release the texture at this point
2939 m_nodesManager->shaderManager()->releaseResource(id: shaderCleanedUpId);
2940
2941 // Release pipelines that were referencing shader
2942 graphicsPipelineManager->releasePipelinesReferencingShader(shaderId: shaderCleanedUpId);
2943 computePipelineManager->releasePipelinesReferencingShader(shaderId: shaderCleanedUpId);
2944 }
2945
2946 const QList<Qt3DCore::QNodeId> cleanedUpRenderTargetIds =
2947 m_nodesManager->renderTargetManager()->takeRenderTargetIdsToCleanup();
2948 for (const Qt3DCore::QNodeId &renderTargetCleanedUpId : cleanedUpRenderTargetIds) {
2949 cleanupRenderTarget(renderTargetId: renderTargetCleanedUpId);
2950 m_nodesManager->renderTargetManager()->releaseResource(id: renderTargetCleanedUpId);
2951 // Release pipelines that were referencing renderTarget
2952 graphicsPipelineManager->releasePipelinesReferencingRenderTarget(renderTargetId: renderTargetCleanedUpId);
2953 }
2954}
2955
2956const GraphicsApiFilterData *Renderer::contextInfo() const
2957{
2958 return m_submissionContext->contextInfo();
2959}
2960
2961SubmissionContext *Renderer::submissionContext() const
2962{
2963 return m_submissionContext.data();
2964}
2965
2966} // namespace Rhi
2967} // namespace Render
2968} // namespace Qt3DRender
2969
2970QT_END_NAMESPACE
2971

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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