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