summaryrefslogtreecommitdiffstats
path: root/src/render/renderers/opengl/renderer/renderer.cpp
diff options
context:
space:
mode:
authorPaul Lemire <paul.lemire@kdab.com>2017-06-02 08:45:55 +0200
committerPaul Lemire <paul.lemire@kdab.com>2018-03-19 07:49:27 +0000
commit34f6d8a88677cffa44be05da7e1e2da0cfc2f3b4 (patch)
treed95b8632aa5a895b1eaa3cbb14891758923d93c9 /src/render/renderers/opengl/renderer/renderer.cpp
parente28192812168b676b57dc505b31eed3bfcba0e67 (diff)
Move Renderer specific classes into new folder
This is another step toward isolating the renderer from the render aspect Change-Id: I4031675b961d6645b65bbe05cf62d150993038b0 Task-number: QTBUG-61151 Reviewed-by: Paul Lemire <paul.lemire@kdab.com>
Diffstat (limited to 'src/render/renderers/opengl/renderer/renderer.cpp')
-rw-r--r--src/render/renderers/opengl/renderer/renderer.cpp2067
1 files changed, 2067 insertions, 0 deletions
diff --git a/src/render/renderers/opengl/renderer/renderer.cpp b/src/render/renderers/opengl/renderer/renderer.cpp
new file mode 100644
index 000000000..2611fb6cc
--- /dev/null
+++ b/src/render/renderers/opengl/renderer/renderer.cpp
@@ -0,0 +1,2067 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB).
+** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt3D module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "renderer_p.h"
+
+#include <Qt3DCore/qentity.h>
+
+#include <Qt3DRender/qmaterial.h>
+#include <Qt3DRender/qmesh.h>
+#include <Qt3DRender/qrenderpass.h>
+#include <Qt3DRender/qshaderprogram.h>
+#include <Qt3DRender/qtechnique.h>
+#include <Qt3DRender/qrenderaspect.h>
+#include <Qt3DRender/qeffect.h>
+
+#include <Qt3DRender/private/qsceneimporter_p.h>
+#include <Qt3DRender/private/renderstates_p.h>
+#include <Qt3DRender/private/cameraselectornode_p.h>
+#include <Qt3DRender/private/framegraphvisitor_p.h>
+#include <Qt3DRender/private/graphicscontext_p.h>
+#include <Qt3DRender/private/cameralens_p.h>
+#include <Qt3DRender/private/rendercommand_p.h>
+#include <Qt3DRender/private/entity_p.h>
+#include <Qt3DRender/private/renderlogging_p.h>
+#include <Qt3DRender/private/material_p.h>
+#include <Qt3DRender/private/renderpassfilternode_p.h>
+#include <Qt3DRender/private/renderqueue_p.h>
+#include <Qt3DRender/private/shader_p.h>
+#include <Qt3DRender/private/buffer_p.h>
+#include <Qt3DRender/private/glbuffer_p.h>
+#include <Qt3DRender/private/renderstateset_p.h>
+#include <Qt3DRender/private/technique_p.h>
+#include <Qt3DRender/private/renderthread_p.h>
+#include <Qt3DRender/private/renderview_p.h>
+#include <Qt3DRender/private/scenemanager_p.h>
+#include <Qt3DRender/private/techniquefilternode_p.h>
+#include <Qt3DRender/private/viewportnode_p.h>
+#include <Qt3DRender/private/vsyncframeadvanceservice_p.h>
+#include <Qt3DRender/private/pickeventfilter_p.h>
+#include <Qt3DRender/private/managers_p.h>
+#include <Qt3DRender/private/buffermanager_p.h>
+#include <Qt3DRender/private/nodemanagers_p.h>
+#include <Qt3DRender/private/gltexturemanager_p.h>
+#include <Qt3DRender/private/gltexture_p.h>
+#include <Qt3DRender/private/geometryrenderermanager_p.h>
+#include <Qt3DRender/private/techniquemanager_p.h>
+#include <Qt3DRender/private/openglvertexarrayobject_p.h>
+#include <Qt3DRender/private/platformsurfacefilter_p.h>
+#include <Qt3DRender/private/loadbufferjob_p.h>
+#include <Qt3DRender/private/rendercapture_p.h>
+#include <Qt3DRender/private/updatelevelofdetailjob_p.h>
+#include <Qt3DRender/private/buffercapture_p.h>
+#include <Qt3DRender/private/offscreensurfacehelper_p.h>
+#include <Qt3DRender/private/renderviewbuilder_p.h>
+#include <Qt3DRender/private/commandthread_p.h>
+#include <Qt3DRender/private/glcommands_p.h>
+
+#include <Qt3DRender/qcameralens.h>
+#include <Qt3DCore/private/qeventfilterservice_p.h>
+#include <Qt3DCore/private/qabstractaspectjobmanager_p.h>
+#include <Qt3DCore/private/qnodecreatedchangegenerator_p.h>
+
+#if QT_CONFIG(qt3d_profile_jobs)
+#include <Qt3DCore/private/aspectcommanddebugger_p.h>
+#endif
+
+#include <QStack>
+#include <QOffscreenSurface>
+#include <QSurface>
+#include <QElapsedTimer>
+#include <QLibraryInfo>
+#include <QPluginLoader>
+#include <QDir>
+#include <QUrl>
+#include <QOffscreenSurface>
+#include <QWindow>
+
+#include <QtGui/private/qopenglcontext_p.h>
+
+// For Debug purposes only
+#include <QThread>
+
+
+#if QT_CONFIG(qt3d_profile_jobs)
+#include <Qt3DCore/private/qthreadpooler_p.h>
+#include <Qt3DRender/private/job_common_p.h>
+#include <Qt3DRender/private/commandexecuter_p.h>
+#endif
+
+#include <Qt3DRender/private/frameprofiler_p.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt3DCore;
+
+namespace Qt3DRender {
+namespace Render {
+/*!
+ \internal
+
+ Renderer shutdown procedure:
+
+ Since the renderer relies on the surface and OpenGLContext to perform its cleanup,
+ it is shutdown when the surface is set to nullptr
+
+ When the surface is set to nullptr this will request the RenderThread to terminate
+ and will prevent createRenderBinJobs from returning a set of jobs as there is nothing
+ more to be rendered.
+
+ In turn, this will call shutdown which will make the OpenGL context current one last time
+ to allow cleanups requiring a call to QOpenGLContext::currentContext to execute properly.
+ At the end of that function, the GraphicsContext is set to null.
+
+ At this point though, the QAspectThread is still running its event loop and will only stop
+ a short while after.
+ */
+
+Renderer::Renderer(QRenderAspect::RenderType type)
+ : m_services(nullptr)
+ , m_nodesManager(nullptr)
+ , m_renderSceneRoot(nullptr)
+ , m_defaultRenderStateSet(nullptr)
+ , m_submissionContext(nullptr)
+ , m_renderQueue(new RenderQueue())
+ , m_renderThread(type == QRenderAspect::Threaded ? new RenderThread(this) : nullptr)
+ , m_commandThread(new CommandThread(this))
+ , m_vsyncFrameAdvanceService(new VSyncFrameAdvanceService(m_renderThread != nullptr))
+ , m_waitForInitializationToBeCompleted(0)
+ , m_pickEventFilter(new PickEventFilter())
+ , m_exposed(0)
+ , m_lastFrameCorrect(0)
+ , m_glContext(nullptr)
+ , m_shareContext(nullptr)
+ , m_shaderCache(new ShaderCache())
+ , m_pickBoundingVolumeJob(PickBoundingVolumeJobPtr::create())
+ , m_rayCastingJob(RayCastingJobPtr::create())
+ , m_time(0)
+ , m_settings(nullptr)
+ , m_updateShaderDataTransformJob(Render::UpdateShaderDataTransformJobPtr::create())
+ , m_cleanupJob(Render::FrameCleanupJobPtr::create())
+ , m_worldTransformJob(Render::UpdateWorldTransformJobPtr::create())
+ , m_expandBoundingVolumeJob(Render::ExpandBoundingVolumeJobPtr::create())
+ , m_calculateBoundingVolumeJob(Render::CalculateBoundingVolumeJobPtr::create())
+ , m_updateWorldBoundingVolumeJob(Render::UpdateWorldBoundingVolumeJobPtr::create())
+ , m_updateTreeEnabledJob(Render::UpdateTreeEnabledJobPtr::create())
+ , m_sendRenderCaptureJob(Render::SendRenderCaptureJobPtr::create(this))
+ , m_sendBufferCaptureJob(Render::SendBufferCaptureJobPtr::create())
+ , m_updateSkinningPaletteJob(Render::UpdateSkinningPaletteJobPtr::create())
+ , m_updateLevelOfDetailJob(Render::UpdateLevelOfDetailJobPtr::create())
+ , m_updateMeshTriangleListJob(Render::UpdateMeshTriangleListJobPtr::create())
+ , m_filterCompatibleTechniqueJob(Render::FilterCompatibleTechniqueJobPtr::create())
+ , m_bufferGathererJob(Render::GenericLambdaJobPtr<std::function<void ()>>::create([this] { lookForDirtyBuffers(); }, JobTypes::DirtyBufferGathering))
+ , m_vaoGathererJob(Render::GenericLambdaJobPtr<std::function<void ()>>::create([this] { lookForAbandonedVaos(); }, JobTypes::DirtyVaoGathering))
+ , m_textureGathererJob(Render::GenericLambdaJobPtr<std::function<void ()>>::create([this] { lookForDirtyTextures(); }, JobTypes::DirtyTextureGathering))
+ , m_introspectShaderJob(Render::GenericLambdaJobPtr<std::function<void ()>>::create([this] { reloadDirtyShaders(); }, JobTypes::DirtyShaderGathering))
+ , m_syncTextureLoadingJob(Render::GenericLambdaJobPtr<std::function<void ()>>::create([] {}, JobTypes::SyncTextureLoading))
+ , m_ownedContext(false)
+ , m_offscreenHelper(nullptr)
+ #if QT_CONFIG(qt3d_profile_jobs)
+ , m_commandExecuter(new Qt3DRender::Debug::CommandExecuter(this))
+ #endif
+{
+ // Set renderer as running - it will wait in the context of the
+ // RenderThread for RenderViews to be submitted
+ m_running.fetchAndStoreOrdered(1);
+ if (m_renderThread)
+ m_renderThread->waitForStart();
+
+ // Create jobs to update transforms and bounding volumes
+ // We can only update bounding volumes once all world transforms are known
+ m_updateWorldBoundingVolumeJob->addDependency(m_worldTransformJob);
+ m_updateWorldBoundingVolumeJob->addDependency(m_calculateBoundingVolumeJob);
+ m_expandBoundingVolumeJob->addDependency(m_updateWorldBoundingVolumeJob);
+ m_updateShaderDataTransformJob->addDependency(m_worldTransformJob);
+ m_pickBoundingVolumeJob->addDependency(m_expandBoundingVolumeJob);
+ // m_calculateBoundingVolumeJob's dependency on m_updateTreeEnabledJob is set in renderBinJobs
+
+ // Dirty texture gathering depends on m_syncTextureLoadingJob
+ // m_syncTextureLoadingJob will depend on the texture loading jobs
+ m_textureGathererJob->addDependency(m_syncTextureLoadingJob);
+
+ // Ensures all skeletons are loaded before we try to update them
+ m_updateSkinningPaletteJob->addDependency(m_syncTextureLoadingJob);
+
+ // All world stuff depends on the RenderEntity's localBoundingVolume
+ m_updateLevelOfDetailJob->addDependency(m_updateMeshTriangleListJob);
+ m_pickBoundingVolumeJob->addDependency(m_updateMeshTriangleListJob);
+ m_rayCastingJob->addDependency(m_updateMeshTriangleListJob);
+
+ m_introspectShaderJob->addDependency(m_filterCompatibleTechniqueJob);
+
+ m_filterCompatibleTechniqueJob->setRenderer(this);
+
+ m_defaultRenderStateSet = new RenderStateSet;
+ m_defaultRenderStateSet->addState(RenderStateSet::createState<DepthTest>(GL_LESS));
+ m_defaultRenderStateSet->addState(RenderStateSet::createState<CullFace>(GL_BACK));
+ m_defaultRenderStateSet->addState(RenderStateSet::createState<ColorMask>(true, true, true, true));
+}
+
+Renderer::~Renderer()
+{
+ // If using a threaded rendering approach, tell the thread to exit
+ // and wait for it to be done
+ m_running.fetchAndStoreOrdered(0);
+ if (m_renderThread)
+ m_renderThread->wait();
+
+ delete m_renderQueue;
+ delete m_defaultRenderStateSet;
+ delete m_shaderCache;
+
+ if (!m_ownedContext)
+ QObject::disconnect(m_contextConnection);
+}
+
+void Renderer::dumpInfo() const
+{
+ qDebug() << Q_FUNC_INFO << "t =" << m_time;
+
+ const ShaderManager *shaderManager = m_nodesManager->shaderManager();
+ qDebug() << "=== Shader Manager ===";
+ qDebug() << *shaderManager;
+
+ const TextureManager *textureManager = m_nodesManager->textureManager();
+ qDebug() << "=== Texture Manager ===";
+ qDebug() << *textureManager;
+
+ const TextureImageManager *textureImageManager = m_nodesManager->textureImageManager();
+ qDebug() << "=== Texture Image Manager ===";
+ qDebug() << *textureImageManager;
+}
+
+qint64 Renderer::time() const
+{
+ return m_time;
+}
+
+void Renderer::setTime(qint64 time)
+{
+ m_time = time;
+}
+
+void Renderer::setNodeManagers(NodeManagers *managers)
+{
+ m_nodesManager = managers;
+
+ m_updateShaderDataTransformJob->setManagers(m_nodesManager);
+ m_cleanupJob->setManagers(m_nodesManager);
+ m_calculateBoundingVolumeJob->setManagers(m_nodesManager);
+ m_pickBoundingVolumeJob->setManagers(m_nodesManager);
+ m_rayCastingJob->setManagers(m_nodesManager);
+ m_updateWorldBoundingVolumeJob->setManager(m_nodesManager->renderNodesManager());
+ m_sendRenderCaptureJob->setManagers(m_nodesManager);
+ m_sendBufferCaptureJob->setManagers(m_nodesManager);
+ m_updateLevelOfDetailJob->setManagers(m_nodesManager);
+ m_updateSkinningPaletteJob->setManagers(m_nodesManager);
+ m_updateMeshTriangleListJob->setManagers(m_nodesManager);
+ m_filterCompatibleTechniqueJob->setManager(m_nodesManager->techniqueManager());
+}
+
+void Renderer::setServices(QServiceLocator *services)
+{
+ m_services = services;
+
+ m_nodesManager->sceneManager()->setDownloadService(m_services->downloadHelperService());
+}
+
+NodeManagers *Renderer::nodeManagers() const
+{
+ return m_nodesManager;
+}
+
+/*!
+ \internal
+
+ Return context which can be used to share resources safely
+ with qt3d main render context.
+*/
+QOpenGLContext *Renderer::shareContext() const
+{
+ QMutexLocker lock(&m_shareContextMutex);
+ return m_shareContext ? m_shareContext
+ : (m_submissionContext->openGLContext()
+ ? m_submissionContext->openGLContext()->shareContext()
+ : nullptr);
+}
+
+// Executed in the command thread
+void Renderer::loadShader(Shader *shader) const
+{
+ Profiling::GLTimeRecorder recorder(Profiling::ShaderUpload);
+ LoadShaderCommand cmd(shader);
+ m_commandThread->executeCommand(&cmd);
+}
+
+void Renderer::setOpenGLContext(QOpenGLContext *context)
+{
+ m_glContext = context;
+}
+
+// Called in RenderThread context by the run method of RenderThread
+// RenderThread has locked the mutex already and unlocks it when this
+// method termintates
+void Renderer::initialize()
+{
+ m_submissionContext.reset(new SubmissionContext);
+ m_submissionContext->setRenderer(this);
+
+ QOpenGLContext* ctx = m_glContext;
+
+ {
+ QMutexLocker lock(&m_shareContextMutex);
+ // If we are using our own context (not provided by QtQuick),
+ // we need to create it
+ if (!m_glContext) {
+ ctx = new QOpenGLContext;
+ ctx->setShareContext(qt_gl_global_share_context());
+
+ // TO DO: Shouldn't we use the highest context available and trust
+ // QOpenGLContext to fall back on the best lowest supported ?
+ const QByteArray debugLoggingMode = qgetenv("QT3DRENDER_DEBUG_LOGGING");
+
+ if (!debugLoggingMode.isEmpty()) {
+ QSurfaceFormat sf = ctx->format();
+ sf.setOption(QSurfaceFormat::DebugContext);
+ ctx->setFormat(sf);
+ }
+
+ // Create OpenGL context
+ if (ctx->create())
+ qCDebug(Backend) << "OpenGL context created with actual format" << ctx->format();
+ else
+ qCWarning(Backend) << Q_FUNC_INFO << "OpenGL context creation failed";
+ m_ownedContext = true;
+ } else {
+ // Context is not owned by us, so we need to know if it gets destroyed
+ m_contextConnection = QObject::connect(m_glContext, &QOpenGLContext::aboutToBeDestroyed,
+ [this] { releaseGraphicsResources(); });
+ }
+
+ if (!ctx->shareContext()) {
+ m_shareContext = new QOpenGLContext;
+ m_shareContext->setFormat(ctx->format());
+ m_shareContext->setShareContext(ctx);
+ m_shareContext->create();
+ }
+
+ // Set shader cache on submission context and command thread
+ m_submissionContext->setShaderCache(m_shaderCache);
+ m_commandThread->setShaderCache(m_shaderCache);
+
+ // Note: we don't have a surface at this point
+ // The context will be made current later on (at render time)
+ m_submissionContext->setOpenGLContext(ctx);
+
+ // Store the format used by the context and queue up creating an
+ // offscreen surface in the main thread so that it is available
+ // for use when we want to shutdown the renderer. We need to create
+ // the offscreen surface on the main thread because on some platforms
+ // (MS Windows), an offscreen surface is just a hidden QWindow.
+ m_format = ctx->format();
+ QMetaObject::invokeMethod(m_offscreenHelper, "createOffscreenSurface");
+
+ // Initialize command thread (uses the offscreen surface to make its own ctx current)
+ m_commandThread->initialize(ctx, m_offscreenHelper);
+ // Note: the offscreen surface is also used at shutdown time to release resources
+ // of the submission gl context (when the window is already gone).
+ // By that time (in releaseGraphicResources), the commandThread has been destroyed
+ // and the offscreenSurface can be reused
+ }
+
+ // Awake setScenegraphRoot in case it was waiting
+ m_waitForInitializationToBeCompleted.release(1);
+ // Allow the aspect manager to proceed
+ m_vsyncFrameAdvanceService->proceedToNextFrame();
+}
+
+/*!
+ * \internal
+ *
+ * Signals for the renderer to stop rendering. If a threaded renderer is in use,
+ * the render thread will call releaseGraphicsResources() just before the thread exits.
+ * If rendering synchronously, this function will call releaseGraphicsResources().
+ */
+void Renderer::shutdown()
+{
+ qCDebug(Backend) << Q_FUNC_INFO << "Requesting renderer shutdown";
+ m_running.store(0);
+
+ // We delete any renderqueue that we may not have had time to render
+ // before the surface was destroyed
+ qDeleteAll(m_renderQueue->nextFrameQueue());
+ m_renderQueue->reset();
+
+ m_commandThread->shutdown();
+
+ if (!m_renderThread) {
+ releaseGraphicsResources();
+ } else {
+ // Wake up the render thread in case it is waiting for some renderviews
+ // to be ready. The isReadyToSubmit() function checks for a shutdown
+ // having been requested.
+ m_submitRenderViewsSemaphore.release(1);
+ m_renderThread->wait();
+ }
+}
+
+/*!
+ \internal
+
+ When using a threaded renderer this function is called in the context of the
+ RenderThread to do any shutdown and cleanup that needs to be performed in the
+ thread where the OpenGL context lives.
+
+ When using Scene3D or anything that provides a custom QOpenGLContext (not
+ owned by Qt3D) this function is called whenever the signal
+ QOpenGLContext::aboutToBeDestroyed is emitted. In that case this function
+ is called in the context of the emitter's thread.
+*/
+void Renderer::releaseGraphicsResources()
+{
+ // We may get called twice when running inside of a Scene3D. Once when Qt Quick
+ // wants to shutdown, and again when the render aspect gets unregistered. So
+ // check that we haven't already cleaned up before going any further.
+ if (!m_submissionContext)
+ return;
+
+ // Try to temporarily make the context current so we can free up any resources
+ QMutexLocker locker(&m_offscreenSurfaceMutex);
+ QOffscreenSurface *offscreenSurface = m_offscreenHelper->offscreenSurface();
+ if (!offscreenSurface) {
+ qWarning() << "Failed to make context current: OpenGL resources will not be destroyed";
+ return;
+ }
+
+ QOpenGLContext *context = m_submissionContext->openGLContext();
+ Q_ASSERT(context);
+ if (context->makeCurrent(offscreenSurface)) {
+
+ // Clean up the graphics context and any resources
+ const QVector<GLTexture*> activeTextures = m_nodesManager->glTextureManager()->activeResources();
+ for (GLTexture *tex : activeTextures)
+ tex->destroyGLTexture();
+
+ // Do the same thing with buffers
+ const QVector<HGLBuffer> activeBuffers = m_nodesManager->glBufferManager()->activeHandles();
+ for (const HGLBuffer &bufferHandle : activeBuffers) {
+ GLBuffer *buffer = m_nodesManager->glBufferManager()->data(bufferHandle);
+ buffer->destroy(m_submissionContext.data());
+ }
+
+ // Do the same thing with VAOs
+ const QVector<HVao> activeVaos = m_nodesManager->vaoManager()->activeHandles();
+ for (const HVao &vaoHandle : activeVaos) {
+ OpenGLVertexArrayObject *vao = m_nodesManager->vaoManager()->data(vaoHandle);
+ vao->destroy();
+ }
+
+ context->doneCurrent();
+ } else {
+ qWarning() << "Failed to make context current: OpenGL resources will not be destroyed";
+ }
+
+ if (m_ownedContext)
+ delete context;
+ if (m_shareContext)
+ delete m_shareContext;
+
+ m_submissionContext.reset(nullptr);
+ qCDebug(Backend) << Q_FUNC_INFO << "Renderer properly shutdown";
+}
+
+void Renderer::setSurfaceExposed(bool exposed)
+{
+ qCDebug(Backend) << "Window exposed: " << exposed;
+ m_exposed.fetchAndStoreOrdered(exposed);
+}
+
+Render::FrameGraphNode *Renderer::frameGraphRoot() const
+{
+ Q_ASSERT(m_settings);
+ if (m_nodesManager && m_nodesManager->frameGraphManager() && m_settings)
+ return m_nodesManager->frameGraphManager()->lookupNode(m_settings->activeFrameGraphID());
+ return nullptr;
+}
+
+// QAspectThread context
+// Order of execution :
+// 1) RenderThread is created -> release 1 of m_waitForInitializationToBeCompleted when started
+// 2) setSceneRoot waits to acquire initialization
+// 3) submitRenderView -> check for surface
+// -> make surface current + create proper glHelper if needed
+void Renderer::setSceneRoot(QBackendNodeFactory *factory, Entity *sgRoot)
+{
+ Q_ASSERT(sgRoot);
+ Q_UNUSED(factory);
+
+ // If initialization hasn't been completed we must wait
+ m_waitForInitializationToBeCompleted.acquire();
+
+ m_renderSceneRoot = sgRoot;
+ if (!m_renderSceneRoot)
+ qCWarning(Backend) << "Failed to build render scene";
+ m_renderSceneRoot->dump();
+ qCDebug(Backend) << Q_FUNC_INFO << "DUMPING SCENE";
+
+ // Set the scene root on the jobs
+ m_worldTransformJob->setRoot(m_renderSceneRoot);
+ m_expandBoundingVolumeJob->setRoot(m_renderSceneRoot);
+ m_calculateBoundingVolumeJob->setRoot(m_renderSceneRoot);
+ m_cleanupJob->setRoot(m_renderSceneRoot);
+ m_pickBoundingVolumeJob->setRoot(m_renderSceneRoot);
+ m_rayCastingJob->setRoot(m_renderSceneRoot);
+ m_updateLevelOfDetailJob->setRoot(m_renderSceneRoot);
+ m_updateSkinningPaletteJob->setRoot(m_renderSceneRoot);
+ m_updateTreeEnabledJob->setRoot(m_renderSceneRoot);
+
+ // Set all flags to dirty
+ m_dirtyBits.marked |= AbstractRenderer::AllDirty;
+}
+
+void Renderer::registerEventFilter(QEventFilterService *service)
+{
+ qCDebug(Backend) << Q_FUNC_INFO << QThread::currentThread();
+ service->registerEventFilter(m_pickEventFilter.data(), 1024);
+}
+
+void Renderer::setSettings(RenderSettings *settings)
+{
+ m_settings = settings;
+}
+
+RenderSettings *Renderer::settings() const
+{
+ return m_settings;
+}
+
+void Renderer::render()
+{
+ // Traversing the framegraph tree from root to lead node
+ // Allows us to define the rendering set up
+ // Camera, RenderTarget ...
+
+ // Utimately the renderer should be a framework
+ // For the processing of the list of renderviews
+
+ // Matrice update, bounding volumes computation ...
+ // Should be jobs
+
+ // namespace Qt3DCore has 2 distincts node trees
+ // One scene description
+ // One framegraph description
+
+ while (m_running.load() > 0) {
+ doRender();
+ // TO DO: Restore windows exposed detection
+ // Probably needs to happens some place else though
+ }
+}
+
+void Renderer::doRender(bool scene3dBlocking)
+{
+ Renderer::ViewSubmissionResultData submissionData;
+ bool hasCleanedQueueAndProceeded = false;
+ bool preprocessingComplete = false;
+ bool beganDrawing = false;
+ const bool canSubmit = isReadyToSubmit();
+
+ // Lock the mutex to protect access to the renderQueue while we look for its state
+ QMutexLocker locker(m_renderQueue->mutex());
+ bool queueIsComplete = m_renderQueue->isFrameQueueComplete();
+ const bool queueIsEmpty = m_renderQueue->targetRenderViewCount() == 0;
+
+ // Scene3D Blocking Mode
+ if (scene3dBlocking && !queueIsComplete && !queueIsEmpty) {
+ int i = 0;
+ // We wait at most 10ms to avoid a case we could never recover from
+ while (!queueIsComplete && i++ < 10) {
+ QThread::msleep(1);
+ qCDebug(Backend) << Q_FUNC_INFO << "Waiting for ready queue (try:" << i << "/ 10)";
+ locker.unlock();
+ queueIsComplete = m_renderQueue->isFrameQueueComplete();
+ locker.relock();
+ }
+ }
+
+ // When using synchronous rendering (QtQuick)
+ // We are not sure that the frame queue is actually complete
+ // Since a call to render may not be synched with the completions
+ // of the RenderViewJobs
+ // In such a case we return early, waiting for a next call with
+ // the frame queue complete at this point
+
+ // RenderQueue is complete (but that means it may be of size 0)
+ if (canSubmit && (queueIsComplete && !queueIsEmpty)) {
+ const QVector<Render::RenderView *> renderViews = m_renderQueue->nextFrameQueue();
+
+#if QT_CONFIG(qt3d_profile_jobs)
+ // Save start of frame
+ JobRunStats submissionStatsPart1;
+ JobRunStats submissionStatsPart2;
+ submissionStatsPart1.jobId.typeAndInstance[0] = JobTypes::FrameSubmissionPart1;
+ submissionStatsPart1.jobId.typeAndInstance[1] = 0;
+ submissionStatsPart1.threadId = reinterpret_cast<quint64>(QThread::currentThreadId());
+ submissionStatsPart1.startTime = QThreadPooler::m_jobsStatTimer.nsecsElapsed();
+ submissionStatsPart2.jobId.typeAndInstance[0] = JobTypes::FrameSubmissionPart2;
+ submissionStatsPart2.jobId.typeAndInstance[1] = 0;
+ submissionStatsPart2.threadId = reinterpret_cast<quint64>(QThread::currentThreadId());
+#endif
+
+ if (canRender()) {
+ { // Scoped to destroy surfaceLock
+ QSurface *surface = nullptr;
+ for (const Render::RenderView *rv: renderViews) {
+ surface = rv->surface();
+ if (surface)
+ break;
+ }
+
+ SurfaceLocker surfaceLock(surface);
+ const bool surfaceIsValid = (surface && surfaceLock.isSurfaceValid());
+ if (surfaceIsValid) {
+ // Reset state for each draw if we don't have complete control of the context
+ if (!m_ownedContext)
+ m_submissionContext->setCurrentStateSet(nullptr);
+ beganDrawing = m_submissionContext->beginDrawing(surface);
+ if (beganDrawing) {
+ // 1) Execute commands for buffer uploads, texture updates, shader loading first
+ updateGLResources();
+ // 2) Update VAO and copy data into commands to allow concurrent submission
+ prepareCommandsSubmission(renderViews);
+ preprocessingComplete = true;
+ }
+ }
+ }
+ // 2) Proceed to next frame and start preparing frame n + 1
+ m_renderQueue->reset();
+ locker.unlock(); // Done protecting RenderQueue
+ m_vsyncFrameAdvanceService->proceedToNextFrame();
+ hasCleanedQueueAndProceeded = true;
+
+#if QT_CONFIG(qt3d_profile_jobs)
+ if (preprocessingComplete) {
+ submissionStatsPart2.startTime = QThreadPooler::m_jobsStatTimer.nsecsElapsed();
+ submissionStatsPart1.endTime = submissionStatsPart2.startTime;
+ }
+#endif
+ // Only try to submit the RenderViews if the preprocessing was successful
+ // This part of the submission is happening in parallel to the RV building for the next frame
+ if (preprocessingComplete) {
+ // 3) Submit the render commands for frame n (making sure we never reference something that could be changing)
+ // Render using current device state and renderer configuration
+ submissionData = submitRenderViews(renderViews);
+
+ // Perform any required cleanup of the Graphics resources (Buffers deleted, Shader deleted...)
+ cleanGraphicsResources();
+ }
+ }
+
+#if QT_CONFIG(qt3d_profile_jobs)
+ // Execute the pending shell commands
+ m_commandExecuter->performAsynchronousCommandExecution(renderViews);
+#endif
+
+ // Delete all the RenderViews which will clear the allocators
+ // that were used for their allocation
+ qDeleteAll(renderViews);
+
+#if QT_CONFIG(qt3d_profile_jobs)
+ if (preprocessingComplete) {
+ // Save submission elapsed time
+ submissionStatsPart2.endTime = QThreadPooler::m_jobsStatTimer.nsecsElapsed();
+ // Note this is safe since proceedToNextFrame is the one going to trigger
+ // the write to the file, and this is performed after this step
+ Qt3DCore::QThreadPooler::addSubmissionLogStatsEntry(submissionStatsPart1);
+ Qt3DCore::QThreadPooler::addSubmissionLogStatsEntry(submissionStatsPart2);
+ Profiling::GLTimeRecorder::writeResults();
+ }
+#endif
+ }
+
+ // Only reset renderQueue and proceed to next frame if the submission
+ // succeeded or if we are using a render thread and that is wasn't performed
+ // already
+
+ // If hasCleanedQueueAndProceeded isn't true this implies that something went wrong
+ // with the rendering and/or the renderqueue is incomplete from some reason
+ // (in the case of scene3d the render jobs may be taking too long ....)
+ // or alternatively it could be complete but empty (RenderQueue of size 0)
+ if (!hasCleanedQueueAndProceeded &&
+ (m_renderThread || queueIsComplete || queueIsEmpty)) {
+ // RenderQueue was full but something bad happened when
+ // trying to render it and therefore proceedToNextFrame was not called
+ // Note: in this case the renderQueue mutex is still locked
+
+ // Reset the m_renderQueue so that we won't try to render
+ // with a queue used by a previous frame with corrupted content
+ // if the current queue was correctly submitted
+ m_renderQueue->reset();
+
+ // We allow the RenderTickClock service to proceed to the next frame
+ // In turn this will allow the aspect manager to request a new set of jobs
+ // to be performed for each aspect
+ m_vsyncFrameAdvanceService->proceedToNextFrame();
+ }
+
+ // Perform the last swapBuffers calls after the proceedToNextFrame
+ // as this allows us to gain a bit of time for the preparation of the
+ // next frame
+ // Finish up with last surface used in the list of RenderViews
+ if (beganDrawing) {
+ SurfaceLocker surfaceLock(submissionData.surface);
+ // Finish up with last surface used in the list of RenderViews
+ m_submissionContext->endDrawing(submissionData.lastBoundFBOId == m_submissionContext->defaultFBO() && surfaceLock.isSurfaceValid());
+ }
+}
+
+// Called by RenderViewJobs
+// When the frameQueue is complete and we are using a renderThread
+// we allow the render thread to proceed
+void Renderer::enqueueRenderView(Render::RenderView *renderView, int submitOrder)
+{
+ QMutexLocker locker(m_renderQueue->mutex()); // Prevent out of order execution
+ // We cannot use a lock free primitive here because:
+ // - QVector is not thread safe
+ // - Even if the insert is made correctly, the isFrameComplete call
+ // could be invalid since depending on the order of execution
+ // the counter could be complete but the renderview not yet added to the
+ // buffer depending on whichever order the cpu decides to process this
+ const bool isQueueComplete = m_renderQueue->queueRenderView(renderView, submitOrder);
+ locker.unlock(); // We're done protecting the queue at this point
+ if (isQueueComplete) {
+ if (m_renderThread && m_running.load())
+ Q_ASSERT(m_submitRenderViewsSemaphore.available() == 0);
+ m_submitRenderViewsSemaphore.release(1);
+ }
+}
+
+bool Renderer::canRender() const
+{
+ // Make sure that we've not been told to terminate
+ if (m_renderThread && !m_running.load()) {
+ qCDebug(Rendering) << "RenderThread termination requested whilst waiting";
+ return false;
+ }
+
+ // TO DO: Check if all surfaces have been destroyed...
+ // It may be better if the last window to be closed trigger a call to shutdown
+ // Rather than having checks for the surface everywhere
+
+ return true;
+}
+
+bool Renderer::isReadyToSubmit()
+{
+ // If we are using a render thread, make sure that
+ // we've been told to render before rendering
+ if (m_renderThread) { // Prevent ouf of order execution
+ m_submitRenderViewsSemaphore.acquire(1);
+
+ // Check if shutdown has been requested
+ if (m_running.load() == 0)
+ return false;
+
+ // When using Thread rendering, the semaphore should only
+ // be released when the frame queue is complete and there's
+ // something to render
+ // The case of shutdown should have been handled just before
+ Q_ASSERT(m_renderQueue->isFrameQueueComplete());
+ }
+ return true;
+}
+
+// Main thread
+QVariant Renderer::executeCommand(const QStringList &args)
+{
+#if QT_CONFIG(qt3d_profile_jobs)
+ return m_commandExecuter->executeCommand(args);
+#else
+ Q_UNUSED(args);
+#endif
+ return QVariant();
+}
+
+/*!
+ \internal
+ Called in the context of the aspect thread from QRenderAspect::onRegistered
+*/
+void Renderer::setOffscreenSurfaceHelper(OffscreenSurfaceHelper *helper)
+{
+ QMutexLocker locker(&m_offscreenSurfaceMutex);
+ m_offscreenHelper = helper;
+}
+
+QSurfaceFormat Renderer::format()
+{
+ return m_format;
+}
+
+// When this function is called, we must not be processing the commands for frame n+1
+void Renderer::prepareCommandsSubmission(const QVector<RenderView *> &renderViews)
+{
+ OpenGLVertexArrayObject *vao = nullptr;
+ QHash<HVao, bool> updatedTable;
+
+ for (RenderView *rv: renderViews) {
+ const QVector<RenderCommand *> commands = rv->commands();
+ for (RenderCommand *command : commands) {
+ // Update/Create VAO
+ if (command->m_type == RenderCommand::Draw) {
+ Geometry *rGeometry = m_nodesManager->data<Geometry, GeometryManager>(command->m_geometry);
+ GeometryRenderer *rGeometryRenderer = m_nodesManager->data<GeometryRenderer, GeometryRendererManager>(command->m_geometryRenderer);
+ Shader *shader = m_nodesManager->data<Shader, ShaderManager>(command->m_shader);
+
+ // We should never have inserted a command for which these are null
+ // in the first place
+ Q_ASSERT(rGeometry && rGeometryRenderer && shader);
+
+ // The VAO should be created only once for a QGeometry and a ShaderProgram
+ // Manager should have a VAO Manager that are indexed by QMeshData and Shader
+ // RenderCommand should have a handle to the corresponding VAO for the Mesh and Shader
+ HVao vaoHandle;
+
+ // Create VAO or return already created instance associated with command shader/geometry
+ // (VAO is emulated if not supported)
+ createOrUpdateVAO(command, &vaoHandle, &vao);
+ command->m_vao = vaoHandle;
+
+ // Avoids redoing the same thing for the same VAO
+ if (!updatedTable.contains(vaoHandle)) {
+ updatedTable.insert(vaoHandle, true);
+
+ // Do we have any attributes that are dirty ?
+ const bool requiresPartialVAOUpdate = requiresVAOAttributeUpdate(rGeometry, command);
+
+ // If true, we need to reupload all attributes to set the VAO
+ // Otherwise only dirty attributes will be updates
+ const bool requiresFullVAOUpdate = (!vao->isSpecified()) || (rGeometry->isDirty() || rGeometryRenderer->isDirty());
+
+ // Append dirty Geometry to temporary vector
+ // so that its dirtiness can be unset later
+ if (rGeometry->isDirty())
+ m_dirtyGeometry.push_back(rGeometry);
+
+ if (!command->m_attributes.isEmpty() && (requiresFullVAOUpdate || requiresPartialVAOUpdate)) {
+ Profiling::GLTimeRecorder recorder(Profiling::VAOUpload);
+ // Activate shader
+ m_submissionContext->activateShader(shader->dna());
+ // Bind VAO
+ vao->bind();
+ // Update or set Attributes and Buffers for the given rGeometry and Command
+ // Note: this fills m_dirtyAttributes as well
+ if (updateVAOWithAttributes(rGeometry, command, shader, requiresFullVAOUpdate))
+ vao->setSpecified(true);
+ }
+ }
+
+ // Unset dirtiness on rGeometryRenderer only
+ // The rGeometry may be shared by several rGeometryRenderer
+ // so we cannot unset its dirtiness at this point
+ if (rGeometryRenderer->isDirty())
+ rGeometryRenderer->unsetDirty();
+
+ // Prepare the ShaderParameterPack based on the active uniforms of the shader
+ shader->prepareUniforms(command->m_parameterPack);
+
+ // TO DO: The step below could be performed by the RenderCommand builder job
+ { // Scoped to show extent
+ command->m_isValid = !command->m_attributes.empty();
+ if (!command->m_isValid)
+ continue;
+
+ // Update the draw command with what's going to be needed for the drawing
+ uint primitiveCount = rGeometryRenderer->vertexCount();
+ uint estimatedCount = 0;
+ Attribute *indexAttribute = nullptr;
+ Attribute *indirectAttribute = nullptr;
+
+ const QVector<Qt3DCore::QNodeId> attributeIds = rGeometry->attributes();
+ for (Qt3DCore::QNodeId attributeId : attributeIds) {
+ Attribute *attribute = m_nodesManager->attributeManager()->lookupResource(attributeId);
+ switch (attribute->attributeType()) {
+ case QAttribute::IndexAttribute:
+ indexAttribute = attribute;
+ break;
+ case QAttribute::DrawIndirectAttribute:
+ indirectAttribute = attribute;
+ break;
+ case QAttribute::VertexAttribute: {
+ if (command->m_attributes.contains(attribute->nameId()))
+ estimatedCount = qMax(attribute->count(), estimatedCount);
+ break;
+ }
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ }
+
+ command->m_drawIndexed = (indexAttribute != nullptr);
+ command->m_drawIndirect = (indirectAttribute != nullptr);
+
+ // Update the draw command with all the information required for the drawing
+ if (command->m_drawIndexed) {
+ command->m_indexAttributeDataType = GraphicsContext::glDataTypeFromAttributeDataType(indexAttribute->vertexBaseType());
+ command->m_indexAttributeByteOffset = indexAttribute->byteOffset() + rGeometryRenderer->indexBufferByteOffset();
+ }
+
+ // Note: we only care about the primitiveCount when using direct draw calls
+ // For indirect draw calls it is assumed the buffer was properly set already
+ if (command->m_drawIndirect) {
+ command->m_indirectAttributeByteOffset = indirectAttribute->byteOffset();
+ command->m_indirectDrawBuffer = m_nodesManager->bufferManager()->lookupHandle(indirectAttribute->bufferId());
+ } else {
+ // Use the count specified by the GeometryRender
+ // If not specify use the indexAttribute count if present
+ // Otherwise tries to use the count from the attribute with the highest count
+ if (primitiveCount == 0) {
+ if (indexAttribute)
+ primitiveCount = indexAttribute->count();
+ else
+ primitiveCount = estimatedCount;
+ }
+ }
+
+ command->m_primitiveCount = primitiveCount;
+ command->m_primitiveType = rGeometryRenderer->primitiveType();
+ command->m_primitiveRestartEnabled = rGeometryRenderer->primitiveRestartEnabled();
+ command->m_restartIndexValue = rGeometryRenderer->restartIndexValue();
+ command->m_firstInstance = rGeometryRenderer->firstInstance();
+ command->m_instanceCount = rGeometryRenderer->instanceCount();
+ command->m_firstVertex = rGeometryRenderer->firstVertex();
+ command->m_indexOffset = rGeometryRenderer->indexOffset();
+ command->m_verticesPerPatch = rGeometryRenderer->verticesPerPatch();
+ } // scope
+ } else if (command->m_type == RenderCommand::Compute) {
+ Shader *shader = m_nodesManager->data<Shader, ShaderManager>(command->m_shader);
+ Q_ASSERT(shader);
+
+ // Prepare the ShaderParameterPack based on the active uniforms of the shader
+ shader->prepareUniforms(command->m_parameterPack);
+ }
+ }
+ }
+
+ // Make sure we leave nothing bound
+ if (vao)
+ vao->release();
+
+ // Unset dirtiness on Geometry and Attributes
+ // Note: we cannot do it in the loop above as we want to be sure that all
+ // the VAO which reference the geometry/attributes are properly updated
+ for (Attribute *attribute : qAsConst(m_dirtyAttributes))
+ attribute->unsetDirty();
+ m_dirtyAttributes.clear();
+
+ for (Geometry *geometry : qAsConst(m_dirtyGeometry))
+ geometry->unsetDirty();
+ m_dirtyGeometry.clear();
+}
+
+// Executed in a job
+void Renderer::lookForAbandonedVaos()
+{
+ const QVector<HVao> activeVaos = m_nodesManager->vaoManager()->activeHandles();
+ for (const HVao &handle : activeVaos) {
+ OpenGLVertexArrayObject *vao = m_nodesManager->vaoManager()->data(handle);
+
+ // Make sure to only mark VAOs for deletion that were already created
+ // (ignore those that might be currently under construction in the render thread)
+ if (vao && vao->isAbandoned(m_nodesManager->geometryManager(), m_nodesManager->shaderManager())) {
+ m_abandonedVaosMutex.lock();
+ m_abandonedVaos.push_back(handle);
+ m_abandonedVaosMutex.unlock();
+ }
+ }
+}
+
+// Executed in a job
+void Renderer::lookForDirtyBuffers()
+{
+ const QVector<HBuffer> activeBufferHandles = m_nodesManager->bufferManager()->activeHandles();
+ for (const HBuffer &handle: activeBufferHandles) {
+ Buffer *buffer = m_nodesManager->bufferManager()->data(handle);
+ if (buffer->isDirty())
+ m_dirtyBuffers.push_back(handle);
+ }
+}
+
+void Renderer::lookForDownloadableBuffers()
+{
+ m_downloadableBuffers.clear();
+ const QVector<HBuffer> activeBufferHandles = m_nodesManager->bufferManager()->activeHandles();
+ for (const HBuffer &handle : activeBufferHandles) {
+ Buffer *buffer = m_nodesManager->bufferManager()->data(handle);
+ if (buffer->access() & QBuffer::Read)
+ m_downloadableBuffers.push_back(handle);
+ }
+}
+
+// Executed in a job
+void Renderer::lookForDirtyTextures()
+{
+ const QVector<HTexture> activeTextureHandles = m_nodesManager->textureManager()->activeHandles();
+ for (const HTexture &handle: activeTextureHandles) {
+ Texture *texture = m_nodesManager->textureManager()->data(handle);
+ // Dirty meaning that something has changed on the texture
+ // either properties, parameters, generator or a texture image
+ if (texture->dirtyFlags() != Texture::NotDirty)
+ m_dirtyTextures.push_back(handle);
+ }
+}
+
+// Executed in a job
+void Renderer::reloadDirtyShaders()
+{
+ Q_ASSERT(isRunning());
+ const QVector<HTechnique> activeTechniques = m_nodesManager->techniqueManager()->activeHandles();
+ const QVector<HShaderBuilder> activeBuilders = m_nodesManager->shaderBuilderManager()->activeHandles();
+ for (const HTechnique &techniqueHandle : activeTechniques) {
+ Technique *technique = m_nodesManager->techniqueManager()->data(techniqueHandle);
+ // If api of the renderer matches the one from the technique
+ if (technique->isCompatibleWithRenderer()) {
+ const auto passIds = technique->renderPasses();
+ for (const QNodeId passId : passIds) {
+ RenderPass *renderPass = m_nodesManager->renderPassManager()->lookupResource(passId);
+ HShader shaderHandle = m_nodesManager->shaderManager()->lookupHandle(renderPass->shaderProgram());
+ Shader *shader = m_nodesManager->shaderManager()->data(shaderHandle);
+
+ ShaderBuilder *shaderBuilder = nullptr;
+ for (const HShaderBuilder &builderHandle : activeBuilders) {
+ ShaderBuilder *builder = m_nodesManager->shaderBuilderManager()->data(builderHandle);
+ if (builder->shaderProgramId() == shader->peerId()) {
+ shaderBuilder = builder;
+ break;
+ }
+ }
+
+ if (shaderBuilder) {
+ shaderBuilder->setGraphicsApi(*technique->graphicsApiFilter());
+
+ for (int i = 0; i <= ShaderBuilder::Compute; i++) {
+ const auto builderType = static_cast<ShaderBuilder::ShaderType>(i);
+ if (!shaderBuilder->shaderGraph(builderType).isValid())
+ continue;
+
+ if (shaderBuilder->isShaderCodeDirty(builderType)) {
+ shaderBuilder->generateCode(builderType);
+ }
+
+ QShaderProgram::ShaderType shaderType = QShaderProgram::Vertex;
+ switch (builderType) {
+ case ShaderBuilder::Vertex:
+ shaderType = QShaderProgram::Vertex;
+ break;
+ case ShaderBuilder::TessellationControl:
+ shaderType = QShaderProgram::TessellationControl;
+ break;
+ case ShaderBuilder::TessellationEvaluation:
+ shaderType = QShaderProgram::TessellationEvaluation;
+ break;
+ case ShaderBuilder::Geometry:
+ shaderType = QShaderProgram::Geometry;
+ break;
+ case ShaderBuilder::Fragment:
+ shaderType = QShaderProgram::Fragment;
+ break;
+ case ShaderBuilder::Compute:
+ shaderType = QShaderProgram::Compute;
+ break;
+ }
+
+ const auto code = shaderBuilder->shaderCode(builderType);
+ shader->setShaderCode(shaderType, code);
+ }
+ }
+
+ if (Q_UNLIKELY(shader->hasPendingNotifications()))
+ shader->submitPendingNotifications();
+ // If the shader hasn't be loaded, load it
+ if (shader != nullptr && !shader->isLoaded())
+ loadShader(shader);
+ }
+ }
+ }
+}
+
+// Render Thread
+void Renderer::updateGLResources()
+{
+ {
+ Profiling::GLTimeRecorder recorder(Profiling::BufferUpload);
+ const QVector<HBuffer> dirtyBufferHandles = std::move(m_dirtyBuffers);
+ for (const HBuffer &handle: dirtyBufferHandles) {
+ Buffer *buffer = m_nodesManager->bufferManager()->data(handle);
+ // Forces creation if it doesn't exit
+ // Also note the binding point doesn't really matter here, we just upload data
+ if (!m_submissionContext->hasGLBufferForBuffer(buffer))
+ m_submissionContext->glBufferForRenderBuffer(buffer, GLBuffer::ArrayBuffer);
+ // Update the glBuffer data
+ m_submissionContext->updateBuffer(buffer);
+ buffer->unsetDirty();
+ }
+ }
+
+ {
+ Profiling::GLTimeRecorder recorder(Profiling::TextureUpload);
+ const QVector<HTexture> activeTextureHandles = std::move(m_dirtyTextures);
+ for (const HTexture &handle: activeTextureHandles) {
+ Texture *texture = m_nodesManager->textureManager()->data(handle);
+ // Upload/Update texture
+ updateTexture(texture);
+ }
+ }
+ // When Textures are cleaned up, their id is saved
+ // so that they can be cleaned up in the render thread
+ // Note: we perform this step in second so that the previous updateTexture call
+ // has a chance to find a shared texture
+ const QVector<Qt3DCore::QNodeId> cleanedUpTextureIds = m_nodesManager->textureManager()->takeTexturesIdsToCleanup();
+ for (const Qt3DCore::QNodeId textureCleanedUpId: cleanedUpTextureIds) {
+ cleanupTexture(m_nodesManager->textureManager()->lookupResource(textureCleanedUpId));
+ // We can really release the texture at this point
+ m_nodesManager->textureManager()->releaseResource(textureCleanedUpId);
+ }
+}
+
+// Render Thread
+void Renderer::updateTexture(Texture *texture)
+{
+ // Check that the current texture images are still in place, if not, do not update
+ const bool isValid = texture->isValid();
+ if (!isValid)
+ return;
+
+ // For implementing unique, non-shared, non-cached textures.
+ // for now, every texture is shared by default
+
+ bool isUnique = false;
+
+ // TO DO: Update the vector once per frame (or in a job)
+ const QVector<HAttachment> activeRenderTargetOutputs = m_nodesManager->attachmentManager()->activeHandles();
+ // A texture is unique if it's being reference by a render target output
+ for (const HAttachment &attachmentHandle : activeRenderTargetOutputs) {
+ RenderTargetOutput *attachment = m_nodesManager->attachmentManager()->data(attachmentHandle);
+ if (attachment->textureUuid() == texture->peerId()) {
+ isUnique = true;
+ break;
+ }
+ }
+
+ // Try to find the associated GLTexture for the backend Texture
+ GLTextureManager *glTextureManager = m_nodesManager->glTextureManager();
+ GLTexture *glTexture = glTextureManager->lookupResource(texture->peerId());
+
+ // No GLTexture associated yet -> create it
+ if (glTexture == nullptr) {
+ if (isUnique)
+ glTextureManager->createUnique(texture);
+ else
+ glTextureManager->getOrCreateShared(texture);
+ texture->unsetDirty();
+ return;
+ }
+
+ // if this texture is a shared texture, we might need to look for a new TextureImpl
+ // and abandon the old one
+ if (glTextureManager->isShared(glTexture)) {
+ glTextureManager->abandon(glTexture, texture);
+ // Check if a shared texture should become unique
+ if (isUnique)
+ glTextureManager->createUnique(texture);
+ else
+ glTextureManager->getOrCreateShared(texture);
+ texture->unsetDirty();
+ return;
+ }
+
+ // this texture node is the only one referring to the GLTexture.
+ // we could thus directly modify the texture. Instead, for non-unique textures,
+ // we first see if there is already a matching texture present.
+ if (!isUnique) {
+ GLTexture *newSharedTex = glTextureManager->findMatchingShared(texture);
+ if (newSharedTex && newSharedTex != glTexture) {
+ glTextureManager->abandon(glTexture, texture);
+ glTextureManager->adoptShared(newSharedTex, texture);
+ texture->unsetDirty();
+ return;
+ }
+ }
+
+ // we hold a reference to a unique or exclusive access to a shared texture
+ // we can thus modify the texture directly.
+ const Texture::DirtyFlags dirtyFlags = texture->dirtyFlags();
+
+ if (dirtyFlags.testFlag(Texture::DirtyProperties) &&
+ !glTextureManager->setProperties(glTexture, texture->properties()))
+ qWarning() << "[Qt3DRender::TextureNode] updateTexture: TextureImpl.setProperties failed, should be non-shared";
+
+ if (dirtyFlags.testFlag(Texture::DirtyParameters) &&
+ !glTextureManager->setParameters(glTexture, texture->parameters()))
+ qWarning() << "[Qt3DRender::TextureNode] updateTexture: TextureImpl.setParameters failed, should be non-shared";
+
+ // Will make the texture requestUpload
+ if (dirtyFlags.testFlag(Texture::DirtyImageGenerators) &&
+ !glTextureManager->setImages(glTexture, texture->textureImages()))
+ qWarning() << "[Qt3DRender::TextureNode] updateTexture: TextureImpl.setGenerators failed, should be non-shared";
+
+ // Will make the texture requestUpload
+ if (dirtyFlags.testFlag(Texture::DirtyDataGenerator) &&
+ !glTextureManager->setGenerator(glTexture, texture->dataGenerator()))
+ qWarning() << "[Qt3DRender::TextureNode] updateTexture: TextureImpl.setGenerator failed, should be non-shared";
+
+ // Unset the dirty flag on the texture
+ texture->unsetDirty();
+}
+
+// Render Thread
+void Renderer::cleanupTexture(const Texture *texture)
+{
+ GLTextureManager *glTextureManager = m_nodesManager->glTextureManager();
+ GLTexture *glTexture = glTextureManager->lookupResource(texture->peerId());
+
+ if (glTexture != nullptr)
+ glTextureManager->abandon(glTexture, texture);
+}
+
+void Renderer::downloadGLBuffers()
+{
+ lookForDownloadableBuffers();
+ const QVector<HBuffer> downloadableHandles = std::move(m_downloadableBuffers);
+ for (const HBuffer &handle : downloadableHandles) {
+ Buffer *buffer = m_nodesManager->bufferManager()->data(handle);
+ QByteArray content = m_submissionContext->downloadBufferContent(buffer);
+ m_sendBufferCaptureJob->addRequest(QPair<Buffer*, QByteArray>(buffer, content));
+ }
+}
+
+// Happens in RenderThread context when all RenderViewJobs are done
+// Returns the id of the last bound FBO
+Renderer::ViewSubmissionResultData Renderer::submitRenderViews(const QVector<Render::RenderView *> &renderViews)
+{
+ QElapsedTimer timer;
+ quint64 queueElapsed = 0;
+ timer.start();
+
+ const int renderViewsCount = renderViews.size();
+ quint64 frameElapsed = queueElapsed;
+ m_lastFrameCorrect.store(1); // everything fine until now.....
+
+ qCDebug(Memory) << Q_FUNC_INFO << "rendering frame ";
+
+ // We might not want to render on the default FBO
+ uint lastBoundFBOId = m_submissionContext->boundFrameBufferObject();
+ QSurface *surface = nullptr;
+ QSurface *previousSurface = nullptr;
+ for (const Render::RenderView *rv: renderViews) {
+ previousSurface = rv->surface();
+ if (previousSurface)
+ break;
+ }
+ QSurface *lastUsedSurface = nullptr;
+
+ for (int i = 0; i < renderViewsCount; ++i) {
+ // Initialize GraphicsContext for drawing
+ // If the RenderView has a RenderStateSet defined
+ const RenderView *renderView = renderViews.at(i);
+
+ // Check if using the same surface as the previous RenderView.
+ // If not, we have to free up the context from the previous surface
+ // and make the context current on the new surface
+ surface = renderView->surface();
+ SurfaceLocker surfaceLock(surface);
+
+ // TO DO: Make sure that the surface we are rendering too has not been unset
+
+ // For now, if we do not have a surface, skip this renderview
+ // TODO: Investigate if it's worth providing a fallback offscreen surface
+ // to use when surface is null. Or if we should instead expose an
+ // offscreensurface to Qt3D.
+ if (!surface || !surfaceLock.isSurfaceValid()) {
+ m_lastFrameCorrect.store(0);
+ continue;
+ }
+
+ lastUsedSurface = surface;
+ const bool surfaceHasChanged = surface != previousSurface;
+
+ if (surfaceHasChanged && previousSurface) {
+ const bool swapBuffers = (lastBoundFBOId == m_submissionContext->defaultFBO()) && PlatformSurfaceFilter::isSurfaceValid(previousSurface);
+ // We only call swap buffer if we are sure the previous surface is still valid
+ m_submissionContext->endDrawing(swapBuffers);
+ }
+
+ if (surfaceHasChanged) {
+ // If we can't make the context current on the surface, skip to the
+ // next RenderView. We won't get the full frame but we may get something
+ if (!m_submissionContext->beginDrawing(surface)) {
+ qWarning() << "Failed to make OpenGL context current on surface";
+ m_lastFrameCorrect.store(0);
+ continue;
+ }
+
+ previousSurface = surface;
+ lastBoundFBOId = m_submissionContext->boundFrameBufferObject();
+ }
+
+ // Apply Memory Barrier if needed
+ if (renderView->memoryBarrier() != QMemoryBarrier::None)
+ m_submissionContext->memoryBarrier(renderView->memoryBarrier());
+
+ // Note: the RenderStateSet is allocated once per RV if needed
+ // and it contains a list of StateVariant value types
+ RenderStateSet *renderViewStateSet = renderView->stateSet();
+
+ {
+ Profiling::GLTimeRecorder recorder(Profiling::StateUpdate);
+ // Set the RV state if not null,
+ if (renderViewStateSet != nullptr)
+ m_submissionContext->setCurrentStateSet(renderViewStateSet);
+ else
+ m_submissionContext->setCurrentStateSet(m_defaultRenderStateSet);
+ }
+
+ // Set RenderTarget ...
+ // Activate RenderTarget
+ {
+ Profiling::GLTimeRecorder recorder(Profiling::RenderTargetUpdate);
+ m_submissionContext->activateRenderTarget(renderView->renderTargetId(),
+ renderView->attachmentPack(),
+ lastBoundFBOId);
+ }
+
+ {
+ Profiling::GLTimeRecorder recorder(Profiling::ClearBuffer);
+ // set color, depth, stencil clear values (only if needed)
+ auto clearBufferTypes = renderView->clearTypes();
+ if (clearBufferTypes & QClearBuffers::ColorBuffer) {
+ const QVector4D vCol = renderView->globalClearColorBufferInfo().clearColor;
+ m_submissionContext->clearColor(QColor::fromRgbF(vCol.x(), vCol.y(), vCol.z(), vCol.w()));
+ }
+ if (clearBufferTypes & QClearBuffers::DepthBuffer)
+ m_submissionContext->clearDepthValue(renderView->clearDepthValue());
+ if (clearBufferTypes & QClearBuffers::StencilBuffer)
+ m_submissionContext->clearStencilValue(renderView->clearStencilValue());
+
+ // Clear BackBuffer
+ m_submissionContext->clearBackBuffer(clearBufferTypes);
+
+ // if there are ClearColors set for different draw buffers,
+ // clear each of these draw buffers individually now
+ const QVector<ClearBufferInfo> clearDrawBuffers = renderView->specificClearColorBufferInfo();
+ for (const ClearBufferInfo &clearBuffer : clearDrawBuffers)
+ m_submissionContext->clearBufferf(clearBuffer.drawBufferIndex, clearBuffer.clearColor);
+ }
+
+ // Set the Viewport
+ m_submissionContext->setViewport(renderView->viewport(), renderView->surfaceSize() * renderView->devicePixelRatio());
+
+ // Execute the render commands
+ if (!executeCommandsSubmission(renderView))
+ m_lastFrameCorrect.store(0); // something went wrong; make sure to render the next frame!
+
+ // executeCommandsSubmission takes care of restoring the stateset to the value
+ // of gc->currentContext() at the moment it was called (either
+ // renderViewStateSet or m_defaultRenderStateSet)
+ if (!renderView->renderCaptureNodeId().isNull()) {
+ const QRenderCaptureRequest request = renderView->renderCaptureRequest();
+ const QSize size = m_submissionContext->renderTargetSize(renderView->surfaceSize() * renderView->devicePixelRatio());
+ QRect rect(QPoint(0, 0), size);
+ if (!request.rect.isEmpty())
+ rect = rect.intersected(request.rect);
+ QImage image;
+ if (!rect.isEmpty()) {
+ // Bind fbo as read framebuffer
+ m_submissionContext->bindFramebuffer(m_submissionContext->activeFBO(), GraphicsHelperInterface::FBORead);
+ image = m_submissionContext->readFramebuffer(rect);
+ } else {
+ qWarning() << "Requested capture rectangle is outside framebuffer";
+ }
+ Render::RenderCapture *renderCapture =
+ static_cast<Render::RenderCapture*>(m_nodesManager->frameGraphManager()->lookupNode(renderView->renderCaptureNodeId()));
+ renderCapture->addRenderCapture(request.captureId, image);
+ addRenderCaptureSendRequest(renderView->renderCaptureNodeId());
+ }
+
+ if (renderView->isDownloadBuffersEnable())
+ downloadGLBuffers();
+
+ // Perform BlitFramebuffer operations
+ if (renderView->hasBlitFramebufferInfo()) {
+ const auto &blitFramebufferInfo = renderView->blitFrameBufferInfo();
+ const QNodeId inputTargetId = blitFramebufferInfo.sourceRenderTargetId;
+ const QNodeId outputTargetId = blitFramebufferInfo.destinationRenderTargetId;
+ const QRect inputRect = blitFramebufferInfo.sourceRect;
+ const QRect outputRect = blitFramebufferInfo.destinationRect;
+ const QRenderTargetOutput::AttachmentPoint inputAttachmentPoint = blitFramebufferInfo.sourceAttachmentPoint;
+ const QRenderTargetOutput::AttachmentPoint outputAttachmentPoint = blitFramebufferInfo.destinationAttachmentPoint;
+ const QBlitFramebuffer::InterpolationMethod interpolationMethod = blitFramebufferInfo.interpolationMethod;
+ m_submissionContext->blitFramebuffer(inputTargetId, outputTargetId, inputRect, outputRect, lastBoundFBOId,
+ inputAttachmentPoint, outputAttachmentPoint,
+ interpolationMethod);
+ }
+
+
+ frameElapsed = timer.elapsed() - frameElapsed;
+ qCDebug(Rendering) << Q_FUNC_INFO << "Submitted Renderview " << i + 1 << "/" << renderViewsCount << "in " << frameElapsed << "ms";
+ frameElapsed = timer.elapsed();
+ }
+
+ // Bind lastBoundFBOId back. Needed also in threaded mode.
+ // lastBoundFBOId != m_graphicsContext->activeFBO() when the last FrameGraph leaf node/renderView
+ // contains RenderTargetSelector/RenderTarget
+ if (lastBoundFBOId != m_submissionContext->activeFBO())
+ m_submissionContext->bindFramebuffer(lastBoundFBOId, GraphicsHelperInterface::FBOReadAndDraw);
+
+ // Reset state and call doneCurrent if the surface
+ // is valid and was actually activated
+ if (surface && m_submissionContext->hasValidGLHelper()) {
+ // Reset state to the default state if the last stateset is not the
+ // defaultRenderStateSet
+ if (m_submissionContext->currentStateSet() != m_defaultRenderStateSet)
+ m_submissionContext->setCurrentStateSet(m_defaultRenderStateSet);
+ }
+
+ queueElapsed = timer.elapsed() - queueElapsed;
+ 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";
+ qCDebug(Rendering) << Q_FUNC_INFO << "Submission Completed in " << timer.elapsed() << "ms";
+
+ // Stores the necessary information to safely perform
+ // the last swap buffer call
+ ViewSubmissionResultData resultData;
+ resultData.lastBoundFBOId = lastBoundFBOId;
+ resultData.surface = lastUsedSurface;
+
+ return resultData;
+}
+
+void Renderer::markDirty(BackendNodeDirtySet changes, BackendNode *node)
+{
+ Q_UNUSED(node);
+ m_dirtyBits.marked |= changes;
+}
+
+Renderer::BackendNodeDirtySet Renderer::dirtyBits()
+{
+ return m_dirtyBits.marked;
+}
+
+#if defined(QT_BUILD_INTERNAL)
+void Renderer::clearDirtyBits(BackendNodeDirtySet changes)
+{
+ m_dirtyBits.remaining &= ~changes;
+ m_dirtyBits.marked &= ~changes;
+}
+#endif
+
+bool Renderer::shouldRender()
+{
+ // Only render if something changed during the last frame, or the last frame
+ // was not rendered successfully (or render-on-demand is disabled)
+ return (m_settings->renderPolicy() == QRenderSettings::Always
+ || m_dirtyBits.marked != 0
+ || m_dirtyBits.remaining != 0
+ || !m_lastFrameCorrect.load());
+}
+
+void Renderer::skipNextFrame()
+{
+ Q_ASSERT(m_settings->renderPolicy() != QRenderSettings::Always);
+
+ // make submitRenderViews() actually run
+ m_renderQueue->setNoRender();
+ m_submitRenderViewsSemaphore.release(1);
+}
+
+// Waits to be told to create jobs for the next frame
+// Called by QRenderAspect jobsToExecute context of QAspectThread
+// Returns all the jobs (and with proper dependency chain) required
+// for the rendering of the scene
+QVector<Qt3DCore::QAspectJobPtr> Renderer::renderBinJobs()
+{
+ QVector<QAspectJobPtr> renderBinJobs;
+
+ // Create the jobs to build the frame
+ const QVector<QAspectJobPtr> bufferJobs = createRenderBufferJobs();
+
+ // Remove previous dependencies
+ m_calculateBoundingVolumeJob->removeDependency(QWeakPointer<QAspectJob>());
+ m_cleanupJob->removeDependency(QWeakPointer<QAspectJob>());
+
+ // Set dependencies
+ for (const QAspectJobPtr &bufferJob : bufferJobs)
+ m_calculateBoundingVolumeJob->addDependency(bufferJob);
+
+ m_updateLevelOfDetailJob->setFrameGraphRoot(frameGraphRoot());
+
+ const BackendNodeDirtySet dirtyBitsForFrame = m_dirtyBits.marked | m_dirtyBits.remaining;
+ m_dirtyBits.marked = 0;
+ m_dirtyBits.remaining = 0;
+ BackendNodeDirtySet notCleared = 0;
+
+ // Add jobs
+ const bool entitiesEnabledDirty = dirtyBitsForFrame & AbstractRenderer::EntityEnabledDirty;
+ if (entitiesEnabledDirty) {
+ renderBinJobs.push_back(m_updateTreeEnabledJob);
+ // This dependency is added here because we clear all dependencies
+ // at the start of this function.
+ m_calculateBoundingVolumeJob->addDependency(m_updateTreeEnabledJob);
+ }
+
+ if (dirtyBitsForFrame & AbstractRenderer::TransformDirty) {
+ renderBinJobs.push_back(m_worldTransformJob);
+ renderBinJobs.push_back(m_updateWorldBoundingVolumeJob);
+ renderBinJobs.push_back(m_updateShaderDataTransformJob);
+ }
+
+ if (dirtyBitsForFrame & AbstractRenderer::GeometryDirty) {
+ renderBinJobs.push_back(m_calculateBoundingVolumeJob);
+ renderBinJobs.push_back(m_updateMeshTriangleListJob);
+ }
+
+ if (dirtyBitsForFrame & AbstractRenderer::GeometryDirty ||
+ dirtyBitsForFrame & AbstractRenderer::TransformDirty) {
+ renderBinJobs.push_back(m_expandBoundingVolumeJob);
+ }
+
+ m_updateSkinningPaletteJob->setDirtyJoints(m_nodesManager->jointManager()->dirtyJoints());
+ renderBinJobs.push_back(m_updateSkinningPaletteJob);
+ renderBinJobs.push_back(m_updateLevelOfDetailJob);
+ renderBinJobs.push_back(m_cleanupJob);
+ renderBinJobs.push_back(m_sendRenderCaptureJob);
+ renderBinJobs.push_back(m_sendBufferCaptureJob);
+ renderBinJobs.append(bufferJobs);
+
+ // Jobs to prepare GL Resource upload
+ renderBinJobs.push_back(m_vaoGathererJob);
+
+ if (dirtyBitsForFrame & AbstractRenderer::BuffersDirty)
+ renderBinJobs.push_back(m_bufferGathererJob);
+
+ if (dirtyBitsForFrame & AbstractRenderer::TexturesDirty) {
+ renderBinJobs.push_back(m_syncTextureLoadingJob);
+ renderBinJobs.push_back(m_textureGathererJob);
+ }
+
+
+ // Layer cache is dependent on layers, layer filters and the enabled flag
+ // on entities
+ const bool layersDirty = dirtyBitsForFrame & AbstractRenderer::LayersDirty;
+ const bool layersCacheNeedsToBeRebuilt = layersDirty || entitiesEnabledDirty;
+ const bool materialDirty = dirtyBitsForFrame & AbstractRenderer::MaterialDirty;
+
+ QMutexLocker lock(m_renderQueue->mutex());
+ if (m_renderQueue->wasReset()) { // Have we rendered yet? (Scene3D case)
+ // Traverse the current framegraph. For each leaf node create a
+ // RenderView and set its configuration then create a job to
+ // populate the RenderView with a set of RenderCommands that get
+ // their details from the RenderNodes that are visible to the
+ // Camera selected by the framegraph configuration
+ FrameGraphVisitor visitor(m_nodesManager->frameGraphManager());
+ const QVector<FrameGraphNode *> fgLeaves = visitor.traverse(frameGraphRoot());
+
+ // Remove leaf nodes that no longer exist from cache
+ const QList<FrameGraphNode *> keys = m_cache.leafNodeCache.keys();
+ for (FrameGraphNode *leafNode : keys) {
+ if (!fgLeaves.contains(leafNode))
+ m_cache.leafNodeCache.remove(leafNode);
+ }
+
+ const int fgBranchCount = fgLeaves.size();
+ for (int i = 0; i < fgBranchCount; ++i) {
+ RenderViewBuilder builder(fgLeaves.at(i), i, this);
+ builder.setLayerCacheNeedsToBeRebuilt(layersCacheNeedsToBeRebuilt);
+ builder.setMaterialGathererCacheNeedsToBeRebuilt(materialDirty);
+ builder.prepareJobs();
+ renderBinJobs.append(builder.buildJobHierachy());
+ }
+
+ // Set target number of RenderViews
+ m_renderQueue->setTargetRenderViewCount(fgBranchCount);
+ } else {
+ // FilterLayerEntityJob is part of the RenderViewBuilder jobs and must be run later
+ // if none of those jobs are started this frame
+ notCleared |= AbstractRenderer::EntityEnabledDirty;
+ notCleared |= AbstractRenderer::LayersDirty;
+ }
+
+ if (isRunning() && m_submissionContext->isInitialized()) {
+ if (dirtyBitsForFrame & AbstractRenderer::TechniquesDirty )
+ renderBinJobs.push_back(m_filterCompatibleTechniqueJob);
+ if (dirtyBitsForFrame & AbstractRenderer::ShadersDirty)
+ renderBinJobs.push_back(m_introspectShaderJob);
+ } else {
+ notCleared |= AbstractRenderer::TechniquesDirty;
+ notCleared |= AbstractRenderer::ShadersDirty;
+ }
+
+ m_dirtyBits.remaining = dirtyBitsForFrame & notCleared;
+
+ return renderBinJobs;
+}
+
+QAspectJobPtr Renderer::pickBoundingVolumeJob()
+{
+ // Set values on pickBoundingVolumeJob
+ RenderSettings *renderSetting = settings();
+ if (renderSetting != nullptr) {
+ m_pickBoundingVolumeJob->setRenderSettings(renderSetting);
+ m_pickBoundingVolumeJob->setFrameGraphRoot(frameGraphRoot());
+ m_pickBoundingVolumeJob->setMouseEvents(pendingPickingEvents());
+ m_pickBoundingVolumeJob->setKeyEvents(pendingKeyEvents());
+ }
+
+ return m_pickBoundingVolumeJob;
+}
+
+QAspectJobPtr Renderer::rayCastingJob()
+{
+ // Set values on rayCastingJob
+ RenderSettings *renderSetting = settings();
+ if (renderSetting != nullptr) {
+ m_rayCastingJob->setRenderSettings(renderSetting);
+ m_rayCastingJob->setFrameGraphRoot(frameGraphRoot());
+ }
+
+ return m_rayCastingJob;
+}
+
+QAspectJobPtr Renderer::syncTextureLoadingJob()
+{
+ return m_syncTextureLoadingJob;
+}
+
+QAspectJobPtr Renderer::expandBoundingVolumeJob()
+{
+ return m_expandBoundingVolumeJob;
+}
+
+QAbstractFrameAdvanceService *Renderer::frameAdvanceService() const
+{
+ return static_cast<Qt3DCore::QAbstractFrameAdvanceService *>(m_vsyncFrameAdvanceService.data());
+}
+
+// Called by executeCommands
+void Renderer::performDraw(RenderCommand *command)
+{
+ // Indirect Draw Calls
+ if (command->m_drawIndirect) {
+
+ // Bind the indirect draw buffer
+ Buffer *indirectDrawBuffer = m_nodesManager->bufferManager()->data(command->m_indirectDrawBuffer);
+ if (Q_UNLIKELY(indirectDrawBuffer == nullptr)) {
+ qWarning() << "Invalid Indirect Draw Buffer - failed to retrieve Buffer";
+ return;
+ }
+
+ // Get GLBuffer from Buffer;
+ GLBuffer *indirectDrawGLBuffer = m_submissionContext->glBufferForRenderBuffer(indirectDrawBuffer, GLBuffer::DrawIndirectBuffer);
+ if (Q_UNLIKELY(indirectDrawGLBuffer == nullptr)) {
+ qWarning() << "Invalid Indirect Draw Buffer - failed to retrieve GLBuffer";
+ return;
+ }
+
+ // Bind GLBuffer
+ const bool successfullyBound = indirectDrawGLBuffer->bind(m_submissionContext.data(), GLBuffer::DrawIndirectBuffer);
+
+ if (Q_LIKELY(successfullyBound)) {
+ // TO DO: Handle multi draw variants if attribute count > 1
+ if (command->m_drawIndexed) {
+ m_submissionContext->drawElementsIndirect(command->m_primitiveType,
+ command->m_indexAttributeDataType,
+ reinterpret_cast<void*>(quintptr(command->m_indirectAttributeByteOffset)));
+ } else {
+ m_submissionContext->drawArraysIndirect(command->m_primitiveType,
+ reinterpret_cast<void*>(quintptr(command->m_indirectAttributeByteOffset)));
+ }
+ } else {
+ qWarning() << "Failed to bind IndirectDrawBuffer";
+ }
+
+ } else { // Direct Draw Calls
+
+ // TO DO: Add glMulti Draw variants
+ if (command->m_primitiveType == QGeometryRenderer::Patches)
+ m_submissionContext->setVerticesPerPatch(command->m_verticesPerPatch);
+
+ if (command->m_primitiveRestartEnabled)
+ m_submissionContext->enablePrimitiveRestart(command->m_restartIndexValue);
+
+ // TO DO: Add glMulti Draw variants
+ if (command->m_drawIndexed) {
+ Profiling::GLTimeRecorder recorder(Profiling::DrawElement);
+ m_submissionContext->drawElementsInstancedBaseVertexBaseInstance(command->m_primitiveType,
+ command->m_primitiveCount,
+ command->m_indexAttributeDataType,
+ reinterpret_cast<void*>(quintptr(command->m_indexAttributeByteOffset)),
+ command->m_instanceCount,
+ command->m_indexOffset,
+ command->m_firstVertex);
+ } else {
+ Profiling::GLTimeRecorder recorder(Profiling::DrawArray);
+ m_submissionContext->drawArraysInstancedBaseInstance(command->m_primitiveType,
+ command->m_firstInstance,
+ command->m_primitiveCount,
+ command->m_instanceCount,
+ command->m_firstVertex);
+ }
+ }
+
+#if defined(QT3D_RENDER_ASPECT_OPENGL_DEBUG)
+ int err = m_graphicsContext->openGLContext()->functions()->glGetError();
+ if (err)
+ qCWarning(Rendering) << "GL error after drawing mesh:" << QString::number(err, 16);
+#endif
+
+ if (command->m_primitiveRestartEnabled)
+ m_submissionContext->disablePrimitiveRestart();
+}
+
+void Renderer::performCompute(const RenderView *, RenderCommand *command)
+{
+ {
+ Profiling::GLTimeRecorder recorder(Profiling::ShaderUpdate);
+ m_submissionContext->activateShader(command->m_shaderDna);
+ }
+ {
+ Profiling::GLTimeRecorder recorder(Profiling::UniformUpdate);
+ m_submissionContext->setParameters(command->m_parameterPack);
+ }
+ {
+ Profiling::GLTimeRecorder recorder(Profiling::DispatchCompute);
+ m_submissionContext->dispatchCompute(command->m_workGroups[0],
+ command->m_workGroups[1],
+ command->m_workGroups[2]);
+ }
+ // HACK: Reset the compute flag to dirty
+ m_dirtyBits.marked |= AbstractRenderer::ComputeDirty;
+
+#if defined(QT3D_RENDER_ASPECT_OPENGL_DEBUG)
+ int err = m_graphicsContext->openGLContext()->functions()->glGetError();
+ if (err)
+ qCWarning(Rendering) << "GL error after drawing mesh:" << QString::number(err, 16);
+#endif
+}
+
+void Renderer::createOrUpdateVAO(RenderCommand *command,
+ HVao *previousVaoHandle,
+ OpenGLVertexArrayObject **vao)
+{
+ const VAOIdentifier vaoKey(command->m_geometry, command->m_shader);
+
+ VAOManager *vaoManager = m_nodesManager->vaoManager();
+ command->m_vao = vaoManager->lookupHandle(vaoKey);
+
+ if (command->m_vao.isNull()) {
+ qCDebug(Rendering) << Q_FUNC_INFO << "Allocating new VAO";
+ command->m_vao = vaoManager->getOrAcquireHandle(vaoKey);
+ vaoManager->data(command->m_vao)->create(m_submissionContext.data(), vaoKey);
+ }
+
+ if (*previousVaoHandle != command->m_vao) {
+ *previousVaoHandle = command->m_vao;
+ *vao = vaoManager->data(command->m_vao);
+ }
+ Q_ASSERT(*vao);
+}
+
+// Called by RenderView->submit() in RenderThread context
+// Returns true, if all RenderCommands were sent to the GPU
+bool Renderer::executeCommandsSubmission(const RenderView *rv)
+{
+ bool allCommandsIssued = true;
+
+ // Render drawing commands
+ const QVector<RenderCommand *> commands = rv->commands();
+
+ // Use the graphicscontext to submit the commands to the underlying
+ // graphics API (OpenGL)
+
+ // Save the RenderView base stateset
+ RenderStateSet *globalState = m_submissionContext->currentStateSet();
+ OpenGLVertexArrayObject *vao = nullptr;
+
+ for (RenderCommand *command : qAsConst(commands)) {
+
+ if (command->m_type == RenderCommand::Compute) { // Compute Call
+ performCompute(rv, command);
+ } else { // Draw Command
+ // Check if we have a valid command that can be drawn
+ if (!command->m_isValid) {
+ allCommandsIssued = false;
+ continue;
+ }
+
+ vao = m_nodesManager->vaoManager()->data(command->m_vao);
+
+ // something may have went wrong when initializing the VAO
+ if (!vao->isSpecified()) {
+ allCommandsIssued = false;
+ continue;
+ }
+
+ {
+ Profiling::GLTimeRecorder recorder(Profiling::ShaderUpdate);
+ //// We activate the shader here
+ if (!m_submissionContext->activateShader(command->m_shaderDna)) {
+ allCommandsIssued = false;
+ continue;
+ }
+ }
+
+ {
+ Profiling::GLTimeRecorder recorder(Profiling::VAOUpdate);
+ // Bind VAO
+ vao->bind();
+ }
+
+ {
+ Profiling::GLTimeRecorder recorder(Profiling::UniformUpdate);
+ //// Update program uniforms
+ if (!m_submissionContext->setParameters(command->m_parameterPack)) {
+ allCommandsIssued = false;
+ // If we have failed to set uniform (e.g unable to bind a texture)
+ // we won't perform the draw call which could show invalid content
+ continue;
+ }
+ }
+
+ //// OpenGL State
+ // TO DO: Make states not dependendent on their backend node for this step
+ // Set state
+ RenderStateSet *localState = command->m_stateSet;
+
+
+ {
+ Profiling::GLTimeRecorder recorder(Profiling::StateUpdate);
+ // Merge the RenderCommand state with the globalState of the RenderView
+ // Or restore the globalState if no stateSet for the RenderCommand
+ if (localState != nullptr) {
+ command->m_stateSet->merge(globalState);
+ m_submissionContext->setCurrentStateSet(command->m_stateSet);
+ } else {
+ m_submissionContext->setCurrentStateSet(globalState);
+ }
+ }
+ // All Uniforms for a pass are stored in the QUniformPack of the command
+ // Uniforms for Effect, Material and Technique should already have been correctly resolved
+ // at that point
+
+ //// Draw Calls
+ performDraw(command);
+ }
+ } // end of RenderCommands loop
+
+ // We cache the VAO and release it only at the end of the exectute frame
+ // We try to minimize VAO binding between RenderCommands
+ if (vao)
+ vao->release();
+
+ // Reset to the state we were in before executing the render commands
+ m_submissionContext->setCurrentStateSet(globalState);
+
+ return allCommandsIssued;
+}
+
+bool Renderer::updateVAOWithAttributes(Geometry *geometry,
+ RenderCommand *command,
+ Shader *shader,
+ bool forceUpdate)
+{
+ m_dirtyAttributes.reserve(m_dirtyAttributes.size() + geometry->attributes().size());
+ const auto attributeIds = geometry->attributes();
+
+ for (QNodeId attributeId : attributeIds) {
+ // TO DO: Improvement we could store handles and use the non locking policy on the attributeManager
+ Attribute *attribute = m_nodesManager->attributeManager()->lookupResource(attributeId);
+
+ if (attribute == nullptr)
+ return false;
+
+ Buffer *buffer = m_nodesManager->bufferManager()->lookupResource(attribute->bufferId());
+
+ // Buffer update was already performed at this point
+ // Just make sure the attribute reference a valid buffer
+ if (buffer == nullptr)
+ return false;
+
+ // Index Attribute
+ bool attributeWasDirty = false;
+ if (attribute->attributeType() == QAttribute::IndexAttribute) {
+ if ((attributeWasDirty = attribute->isDirty()) == true || forceUpdate)
+ m_submissionContext->specifyIndices(buffer);
+ // Vertex Attribute
+ } else if (command->m_attributes.contains(attribute->nameId())) {
+ if ((attributeWasDirty = attribute->isDirty()) == true || forceUpdate) {
+ // Find the location for the attribute
+ const QVector<ShaderAttribute> shaderAttributes = shader->attributes();
+ const ShaderAttribute *attributeDescription = nullptr;
+ for (const ShaderAttribute &shaderAttribute : shaderAttributes) {
+ if (shaderAttribute.m_nameId == attribute->nameId()) {
+ attributeDescription = &shaderAttribute;
+ break;
+ }
+ }
+ if (!attributeDescription || attributeDescription->m_location < 0)
+ return false;
+ m_submissionContext->specifyAttribute(attribute, buffer, attributeDescription);
+ }
+ }
+
+ // Append attribute to temporary vector so that its dirtiness
+ // can be cleared at the end of the frame
+ if (attributeWasDirty)
+ m_dirtyAttributes.push_back(attribute);
+
+ // Note: We cannot call unsertDirty on the Attribute at this
+ // point as we don't know if the attributes are being shared
+ // with other geometry / geometryRenderer in which case they still
+ // should remain dirty so that VAO for these commands are properly
+ // updated
+ }
+
+ return true;
+}
+
+bool Renderer::requiresVAOAttributeUpdate(Geometry *geometry,
+ RenderCommand *command) const
+{
+ const auto attributeIds = geometry->attributes();
+
+ for (QNodeId attributeId : attributeIds) {
+ // TO DO: Improvement we could store handles and use the non locking policy on the attributeManager
+ Attribute *attribute = m_nodesManager->attributeManager()->lookupResource(attributeId);
+
+ if (attribute == nullptr)
+ continue;
+
+ if ((attribute->attributeType() == QAttribute::IndexAttribute && attribute->isDirty()) ||
+ (command->m_attributes.contains(attribute->nameId()) && attribute->isDirty()))
+ return true;
+ }
+ return false;
+}
+
+// Erase graphics related resources that may become unused after a frame
+void Renderer::cleanGraphicsResources()
+{
+ // Clean buffers
+ const QVector<Qt3DCore::QNodeId> buffersToRelease = m_nodesManager->bufferManager()->takeBuffersToRelease();
+ for (Qt3DCore::QNodeId bufferId : buffersToRelease)
+ m_submissionContext->releaseBuffer(bufferId);
+
+ // Delete abandoned textures
+ const QVector<GLTexture*> abandonedTextures = m_nodesManager->glTextureManager()->takeAbandonedTextures();
+ for (GLTexture *tex : abandonedTextures) {
+ tex->destroyGLTexture();
+ delete tex;
+ }
+
+ // Delete abandoned VAOs
+ m_abandonedVaosMutex.lock();
+ const QVector<HVao> abandonedVaos = std::move(m_abandonedVaos);
+ m_abandonedVaosMutex.unlock();
+ for (const HVao &vaoHandle : abandonedVaos) {
+ // might have already been destroyed last frame, but added by the cleanup job before, so
+ // check if the VAO is really still existent
+ OpenGLVertexArrayObject *vao = m_nodesManager->vaoManager()->data(vaoHandle);
+ if (vao) {
+ vao->destroy();
+ m_nodesManager->vaoManager()->release(vaoHandle);
+ }
+ }
+}
+
+QList<QPair<QObject *, QMouseEvent>> Renderer::pendingPickingEvents() const
+{
+ return m_pickEventFilter->pendingMouseEvents();
+}
+
+QList<QKeyEvent> Renderer::pendingKeyEvents() const
+{
+ return m_pickEventFilter->pendingKeyEvents();
+}
+
+const GraphicsApiFilterData *Renderer::contextInfo() const
+{
+ return m_submissionContext->contextInfo();
+}
+
+SubmissionContext *Renderer::submissionContext() const
+{
+ return m_submissionContext.data();
+}
+
+void Renderer::addRenderCaptureSendRequest(Qt3DCore::QNodeId nodeId)
+{
+ if (!m_pendingRenderCaptureSendRequests.contains(nodeId))
+ m_pendingRenderCaptureSendRequests.push_back(nodeId);
+}
+
+const QVector<Qt3DCore::QNodeId> Renderer::takePendingRenderCaptureSendRequests()
+{
+ return std::move(m_pendingRenderCaptureSendRequests);
+}
+
+// Returns a vector of jobs to be performed for dirty buffers
+// 1 dirty buffer == 1 job, all job can be performed in parallel
+QVector<Qt3DCore::QAspectJobPtr> Renderer::createRenderBufferJobs() const
+{
+ const QVector<QNodeId> dirtyBuffers = m_nodesManager->bufferManager()->takeDirtyBuffers();
+ QVector<QAspectJobPtr> dirtyBuffersJobs;
+ dirtyBuffersJobs.reserve(dirtyBuffers.size());
+
+ for (const QNodeId bufId : dirtyBuffers) {
+ Render::HBuffer bufferHandle = m_nodesManager->lookupHandle<Render::Buffer, Render::BufferManager, Render::HBuffer>(bufId);
+ if (!bufferHandle.isNull()) {
+ // Create new buffer job
+ auto job = Render::LoadBufferJobPtr::create(bufferHandle);
+ job->setNodeManager(m_nodesManager);
+ dirtyBuffersJobs.push_back(job);
+ }
+ }
+
+ return dirtyBuffersJobs;
+}
+
+} // namespace Render
+} // namespace Qt3DRender
+
+QT_END_NAMESPACE