Revision control

++ // Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB).
++ // Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include "renderer_p.h"
++
++ #include <Qt3DCore/qentity.h>
++ #include <Qt3DCore/private/vector_helper_p.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/cameralens_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/shader_p.h>
++ #include <Qt3DRender/private/buffer_p.h>
++ #include <Qt3DRender/private/technique_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/managers_p.h>
++ #include <Qt3DRender/private/buffermanager_p.h>
++ #include <Qt3DRender/private/nodemanagers_p.h>
++ #include <Qt3DRender/private/geometryrenderermanager_p.h>
++ #include <Qt3DRender/private/techniquemanager_p.h>
++ #include <Qt3DRender/private/platformsurfacefilter_p.h> // for SurfaceLocker
++ #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/subtreeenabler_p.h>
++ #include <Qt3DRender/private/qshaderprogrambuilder_p.h>
++ #include <Qt3DRender/private/qshaderprogram_p.h>
++ #include <Qt3DRender/private/qrenderaspect_p.h>
++ #ifndef Q_OS_INTEGRITY
++ #include <imguirenderer_p.h>
++ #endif
++
++ #include <Qt3DRender/qcameralens.h>
++ #include <Qt3DCore/private/qabstractaspectjobmanager_p.h>
++ #include <Qt3DCore/private/qaspectmanager_p.h>
++ #include <Qt3DCore/private/qsysteminformationservice_p.h>
++ #include <Qt3DCore/private/qsysteminformationservice_p_p.h>
++ #include <Qt3DRender/private/resourceaccessor_p.h>
++ #include <Qt3DRender/private/renderlogging_p.h>
++ #include <Qt3DRender/private/renderstateset_p.h>
++ #include <Qt3DRender/private/setfence_p.h>
++ #include <Qt3DRender/private/qsetfence_p.h>
++ #include <Qt3DRender/private/waitfence_p.h>
++ #include <Qt3DRender/private/renderqueue_p.h>
++
++ #include <glbuffer_p.h>
++ #include <graphicscontext_p.h>
++ #include <rendercommand_p.h>
++ #include <renderview_p.h>
++ #include <gltexture_p.h>
++ #include <openglvertexarrayobject_p.h>
++ #include <renderviewbuilder_p.h>
++ #include <glresourcemanagers_p.h>
++ #include <commandexecuter_p.h>
++
++ #include <QStack>
++ #include <QOffscreenSurface>
++ #include <QSurface>
++ #include <QElapsedTimer>
++ #include <QLibraryInfo>
++ #include <QMutexLocker>
++ #include <QPluginLoader>
++ #include <QDir>
++ #include <QUrl>
++ #include <QOffscreenSurface>
++ #include <QWindow>
++ #include <QThread>
++
++ #include <QtGui/private/qopenglcontext_p.h>
++ #include "frameprofiler_p.h"
++
++ QT_BEGIN_NAMESPACE
++
++ namespace Qt3DRender {
++ namespace Render {
++ namespace OpenGL {
++
++ using RendererCache = Render::RendererCache<RenderCommand>;
++
++ namespace {
++
++ class CachingLightGatherer : public LightGatherer {
++ public:
++ CachingLightGatherer(RendererCache *cache)
++ : LightGatherer()
++ , m_cache(cache)
++ {
++ }
++
++ void run() override
++ {
++ LightGatherer::run();
++
++ m_cache->gatheredLights = std::move(lights());
++ std::sort(m_cache->gatheredLights.begin(), m_cache->gatheredLights.end(),
++ [] (const LightSource &a, const LightSource &b) {
++ return a.entity < b.entity;
++ });
++
++ m_cache->environmentLight = environmentLight();
++ }
++
++ private:
++ RendererCache *m_cache;
++ };
++
++ class CachingRenderableEntityFilter : public RenderableEntityFilter {
++ public:
++ CachingRenderableEntityFilter(RendererCache *cache)
++ : RenderableEntityFilter()
++ , m_cache(cache)
++ {
++
++ }
++
++ void run() override
++ {
++ RenderableEntityFilter::run();
++
++ std::vector<Entity *> selectedEntities = std::move(filteredEntities());
++ std::sort(selectedEntities.begin(), selectedEntities.end());
++
++ m_cache->renderableEntities = std::move(selectedEntities);
++ }
++
++ private:
++ RendererCache *m_cache;
++ };
++
++ class CachingComputableEntityFilter : public ComputableEntityFilter {
++ public:
++ CachingComputableEntityFilter(RendererCache *cache)
++ : ComputableEntityFilter()
++ , m_cache(cache)
++ {
++
++ }
++
++ void run() override
++ {
++ ComputableEntityFilter::run();
++
++ std::vector<Entity *> selectedEntities = std::move(filteredEntities());
++ std::sort(selectedEntities.begin(), selectedEntities.end());
++
++ m_cache->computeEntities = std::move(selectedEntities);
++ }
++
++ private:
++ RendererCache *m_cache;
++ };
++
++ } // anonymous
++
++ /*!
++ \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()
++ : m_services(nullptr)
++ , m_aspect(nullptr)
++ , m_nodesManager(nullptr)
++ , m_renderSceneRoot(nullptr)
++ , m_defaultRenderStateSet(nullptr)
++ , m_submissionContext(nullptr)
++ , m_vsyncFrameAdvanceService(new VSyncFrameAdvanceService(false))
++ , m_hasBeenInitializedMutex()
++ , m_exposed(0)
++ , m_lastFrameCorrect(0)
++ , m_glContext(nullptr)
++ , m_shareContext(nullptr)
++ , m_time(0)
++ , m_settings(nullptr)
++ , m_updateShaderDataTransformJob(Render::UpdateShaderDataTransformJobPtr::create())
++ , m_cleanupJob(Render::FrameCleanupJobPtr::create())
++ , m_sendBufferCaptureJob(Render::SendBufferCaptureJobPtr::create())
++ , m_filterCompatibleTechniqueJob(FilterCompatibleTechniqueJobPtr::create())
++ , m_lightGathererJob(new CachingLightGatherer(&m_cache))
++ , m_renderableEntityFilterJob(new CachingRenderableEntityFilter(&m_cache))
++ , m_computableEntityFilterJob(new CachingComputableEntityFilter(&m_cache))
++ , m_bufferGathererJob(CreateSynchronizerJobPtr([this] { lookForDirtyBuffers(); }, JobTypes::DirtyBufferGathering, 0))
++ , m_vaoGathererJob(CreateSynchronizerJobPtr([this] { lookForAbandonedVaos(); }, JobTypes::DirtyVaoGathering, 0))
++ , m_textureGathererJob(CreateSynchronizerJobPtr([this] { lookForDirtyTextures(); }, JobTypes::DirtyTextureGathering, 0))
++ , m_introspectShaderJob(CreateSynchronizerPostFramePtr([this] { reloadDirtyShaders(); },
++ [this] (Qt3DCore::QAspectManager *m) { sendShaderChangesToFrontend(m); },
++ JobTypes::DirtyShaderGathering))
++ , m_ownedContext(false)
++ , m_offscreenHelper(nullptr)
++ , m_glResourceManagers(nullptr)
++ , m_commandExecuter(new Qt3DRender::Debug::CommandExecuter(this))
++ , m_shouldSwapBuffers(true)
++ , m_imGuiRenderer(nullptr)
++ , m_jobsInLastFrame(0)
++ {
++ // Set renderer as running - it will wait in the context of the
++ // RenderThread for RenderViews to be submitted
++ m_running.fetchAndStoreOrdered(1);
++
++ m_introspectShaderJob->addDependency(m_filterCompatibleTechniqueJob);
++
++ m_filterCompatibleTechniqueJob->setRenderer(this);
++
++ m_defaultRenderStateSet = new RenderStateSet;
++ m_defaultRenderStateSet->addState(StateVariant::createState<DepthTest>(GL_LESS));
++ m_defaultRenderStateSet->addState(StateVariant::createState<CullFace>(GL_BACK));
++ m_defaultRenderStateSet->addState(StateVariant::createState<ColorMask>(true, true, true, true));
++ }
++
++ Renderer::~Renderer()
++ {
++ Q_ASSERT(m_running.fetchAndStoreOrdered(0) == 0);
++
++ delete m_defaultRenderStateSet;
++ delete m_glResourceManagers;
++
++ if (!m_ownedContext)
++ QObject::disconnect(m_contextConnection);
++
++ #ifndef Q_OS_INTEGRITY
++ delete m_imGuiRenderer;
++ #endif
++ }
++
++ 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;
++ }
++
++ void Renderer::setRenderDriver(AbstractRenderer::RenderDriver driver)
++ {
++ m_driver = driver;
++ }
++
++ AbstractRenderer::RenderDriver Renderer::renderDriver() const
++ {
++ return m_driver;
++ }
++
++ qint64 Renderer::time() const
++ {
++ return m_time;
++ }
++
++ void Renderer::setTime(qint64 time)
++ {
++ m_time = time;
++ }
++
++ void Renderer::setJobsInLastFrame(int jobsInLastFrame)
++ {
++ m_jobsInLastFrame = jobsInLastFrame;
++ }
++
++ void Renderer::setAspect(QRenderAspect *aspect)
++ {
++ m_aspect = aspect;
++ m_updateShaderDataTransformJob->addDependency(QRenderAspectPrivate::get(aspect)->m_worldTransformJob);
++ }
++
++ void Renderer::setNodeManagers(NodeManagers *managers)
++ {
++ m_nodesManager = managers;
++ m_glResourceManagers = new GLResourceManagers();
++ m_scene2DResourceAccessor.reset(new ResourceAccessor(this, m_nodesManager));
++
++ m_updateShaderDataTransformJob->setManagers(m_nodesManager);
++ m_cleanupJob->setManagers(m_nodesManager);
++ m_filterCompatibleTechniqueJob->setManager(m_nodesManager->techniqueManager());
++ m_sendBufferCaptureJob->setManagers(m_nodesManager);
++ m_lightGathererJob->setManager(m_nodesManager->renderNodesManager());
++ m_renderableEntityFilterJob->setManager(m_nodesManager->renderNodesManager());
++ m_computableEntityFilterJob->setManager(m_nodesManager->renderNodesManager());
++ }
++
++ void Renderer::setServices(Qt3DCore::QServiceLocator *services)
++ {
++ m_services = services;
++
++ m_nodesManager->sceneManager()->setDownloadService(m_services->downloadHelperService());
++ }
++
++ QRenderAspect *Renderer::aspect() const
++ {
++ return m_aspect;
++ }
++
++ 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_hasBeenInitializedMutex);
++ return m_shareContext ? m_shareContext
++ : (m_submissionContext && m_submissionContext->openGLContext()
++ ? m_submissionContext->openGLContext()->shareContext()
++ : nullptr);
++ }
++
++ void Renderer::setOpenGLContext(QOpenGLContext *context)
++ {
++ m_glContext = context;
++ }
++
++ void Renderer::setScreen(QScreen *scr)
++ {
++ m_screen = scr;
++ }
++
++ QScreen *Renderer::screen() const
++ {
++ return m_screen;
++ }
++
++ bool Renderer::accessOpenGLTexture(Qt3DCore::QNodeId nodeId,
++ QOpenGLTexture **texture,
++ QMutex **lock,
++ bool readonly)
++ {
++ Texture *tex = m_nodesManager->textureManager()->lookupResource(nodeId);
++ if (!tex)
++ return false;
++
++ GLTexture *glTex = m_glResourceManagers->glTextureManager()->lookupResource(tex->peerId());
++ if (!glTex)
++ return false;
++
++ if (glTex->isDirty())
++ return false;
++
++ if (!readonly)
++ glTex->setExternalRenderingEnabled(true);
++
++ *texture = glTex->getGLTexture();
++
++ if (!readonly)
++ *lock = glTex->externalRenderingLock();
++
++ return true;
++ }
++
++ QSharedPointer<RenderBackendResourceAccessor> Renderer::resourceAccessor() const
++ {
++ return m_scene2DResourceAccessor;
++ }
++
++ // 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()
++ {
++ QMutexLocker lock(&m_hasBeenInitializedMutex);
++ m_submissionContext.reset(new SubmissionContext);
++ m_submissionContext->setRenderer(this);
++
++ // If we are using our own context (not provided by QtQuick),
++ // we need to create it
++ if (!m_glContext) {
++ m_glContext = new QOpenGLContext;
++ if (m_screen)
++ m_glContext->setScreen(m_screen);
++ m_glContext->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 = m_glContext->format();
++ sf.setOption(QSurfaceFormat::DebugContext);
++ m_glContext->setFormat(sf);
++ }
++
++ // Create OpenGL context
++
++ if (m_glContext->create())
++ qCDebug(Backend) << "OpenGL context created with actual format" << m_glContext->format();
++ else
++ qCWarning(Backend) << Q_FUNC_INFO << "OpenGL context creation failed";
++ m_ownedContext = true;
++
++ QObject::connect(m_glContext, &QOpenGLContext::aboutToBeDestroyed, m_glContext,
++ [this] { m_frameProfiler.reset(); });
++ } else {
++ // Context is not owned by us, so we need to know if it gets destroyed
++ m_contextConnection = QObject::connect(m_glContext, &QOpenGLContext::aboutToBeDestroyed,
++ m_glContext, [this] { releaseGraphicsResources(); });
++ }
++
++ qCDebug(Backend) << "Qt3D shared context:" << m_glContext->shareContext();
++ qCDebug(Backend) << "Qt global shared context:" << qt_gl_global_share_context();
++
++ if (!m_glContext->shareContext()) {
++ m_shareContext = new QOpenGLContext;
++ if (m_glContext->screen())
++ m_shareContext->setScreen(m_glContext->screen());
++ m_shareContext->setFormat(m_glContext->format());
++ m_shareContext->setShareContext(m_glContext);
++ m_shareContext->create();
++ }
++
++ // Note: we don't have a surface at this point
++ // The context will be made current later on (at render time)
++ m_submissionContext->setOpenGLContext(m_glContext);
++
++ // 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 = m_glContext->format();
++ QMetaObject::invokeMethod(m_offscreenHelper, "createOffscreenSurface");
++
++ // Awake setScenegraphRoot in case it was waiting
++ m_waitForInitializationToBeCompleted.countDown();
++ // Allow the aspect manager to proceed
++ m_vsyncFrameAdvanceService->proceedToNextFrame();
++
++ // Force initial refresh
++ markDirty(AllDirty, nullptr);
++ }
++
++ /*!
++ * \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()
++ {
++ // Ensure we have waited to be fully initialized before trying to shut down
++ // (in case initialization is taking place at the same time)
++ QMutexLocker lock(&m_hasBeenInitializedMutex);
++
++ qCDebug(Backend) << Q_FUNC_INFO << "Requesting renderer shutdown";
++ const bool wasRunning = m_running.testAndSetRelaxed(1, 0);
++
++ // We might have already been shutdown
++ if (!wasRunning)
++ return;
++
++ // We delete any renderqueue that we may not have had time to render
++ // before the surface was destroyed
++ QMutexLocker lockRenderQueue(m_renderQueue.mutex());
++ m_renderQueue.reset();
++ lockRenderQueue.unlock();
++
++ releaseGraphicsResources();
++
++ // Destroy internal managers
++ // This needs to be done before the nodeManager is destroy
++ // as the internal resources might somehow rely on nodeManager resources
++ delete m_glResourceManagers;
++ m_glResourceManagers = nullptr;
++
++ delete m_settings;
++ m_settings = nullptr;
++ }
++
++ /*!
++ \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";
++ // We still need to delete the submission context
++ m_submissionContext.reset(nullptr);
++ return;
++ }
++
++ QOpenGLContext *context = m_submissionContext->openGLContext();
++ Q_ASSERT(context);
++
++ if (context->thread() == QThread::currentThread()) {
++ QSurface *lastContextSurface = context->surface();
++
++ if (context->makeCurrent(offscreenSurface)) {
++ // Clean up the graphics context and any resources
++ const std::vector<HGLTexture> &activeTexturesHandles = m_glResourceManagers->glTextureManager()->activeHandles();
++ for (const HGLTexture &textureHandle : activeTexturesHandles) {
++ GLTexture *tex = m_glResourceManagers->glTextureManager()->data(textureHandle);
++ tex->destroy();
++ }
++
++ // Do the same thing with buffers
++ const std::vector<HGLBuffer> &activeBuffers = m_glResourceManagers->glBufferManager()->activeHandles();
++ for (const HGLBuffer &bufferHandle : activeBuffers) {
++ GLBuffer *buffer = m_glResourceManagers->glBufferManager()->data(bufferHandle);
++ buffer->destroy(m_submissionContext.data());
++ }
++
++ // Do the same thing with shaders
++ const std::vector<GLShader *> shaders = m_glResourceManagers->glShaderManager()->takeActiveResources();
++ qDeleteAll(shaders);
++
++ // Do the same thing with VAOs
++ const std::vector<HVao> &activeVaos = m_glResourceManagers->vaoManager()->activeHandles();
++ for (const HVao &vaoHandle : activeVaos) {
++ OpenGLVertexArrayObject *vao = m_glResourceManagers->vaoManager()->data(vaoHandle);
++ vao->destroy();
++ }
++
++ m_submissionContext->releaseRenderTargets();
++
++ m_frameProfiler.reset();
++ if (m_ownedContext) {
++ context->doneCurrent();
++ } else {
++ // Leave the context in the state we found it in by restoring
++ // its last used surface. This satisfies expectations when used
++ // with QQuickWidgets that surface on current context after
++ // QQuickRenderControl cleanup is the same as prior to the
++ // cleanup. Arguably this could also be checked for in
++ // QQuickWidgetPrivate::invalidateRenderControl.
++ context->makeCurrent(lastContextSurface);
++ }
++ }
++ } 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 -> countDown 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(Entity *sgRoot)
++ {
++ Q_ASSERT(sgRoot);
++
++ // If initialization hasn't been completed we must wait
++ m_waitForInitializationToBeCompleted.wait();
++
++ 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_cleanupJob->setRoot(m_renderSceneRoot);
++
++ // Set all flags to dirty
++ m_dirtyBits.marked |= AbstractRenderer::AllDirty;
++ }
++
++ void Renderer::setSettings(RenderSettings *settings)
++ {
++ m_settings = settings;
++ }
++
++ RenderSettings *Renderer::settings() const
++ {
++ return m_settings;
++ }
++
++ // Either called by render if Qt3D is in charge of rendering (in the mainthread)
++ // or by QRenderAspectPrivate::render (for Scene3D, potentially from a RenderThread)
++ // This will wait until renderQueue is ready or shutdown was requested
++ void Renderer::render(bool swapBuffers)
++ {
++ Renderer::ViewSubmissionResultData submissionData;
++ bool preprocessingComplete = false;
++ bool beganDrawing = false;
++
++ // Blocking until RenderQueue is full
++ const bool canSubmit = waitUntilReadyToSubmit();
++
++ // If it returns false -> we are shutting down
++ if (!canSubmit)
++ return;
++
++ m_shouldSwapBuffers = swapBuffers;
++ const std::vector<Render::OpenGL::RenderView *> &renderViews = m_renderQueue.nextFrameQueue();
++ const bool queueIsEmpty = m_renderQueue.targetRenderViewCount() == 0;
++
++ // RenderQueue is complete (but that means it may be of size 0)
++ if (!queueIsEmpty) {
++ Qt3DCore::QTaskLogger submissionStatsPart1(m_services->systemInformation(),
++ { JobTypes::FrameSubmissionPart1, 0 },
++ Qt3DCore::QTaskLogger::Submission);
++ Qt3DCore::QTaskLogger submissionStatsPart2(m_services->systemInformation(),
++ { JobTypes::FrameSubmissionPart2, 0 },
++ Qt3DCore::QTaskLogger::Submission);
++ { // Scoped to destroy surfaceLock
++ QSurface *surface = nullptr;
++ for (const 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;
++
++ // Purge shader which aren't used any longer
++ static int callCount = 0;
++ ++callCount;
++ const int shaderPurgePeriod = 600;
++ if (callCount % shaderPurgePeriod == 0)
++ m_glResourceManagers->glShaderManager()->purge();
++ }
++ }
++ }
++
++ // Only try to submit the RenderViews if the preprocessing was successful
++ if (preprocessingComplete) {
++ submissionStatsPart1.end(submissionStatsPart2.restart());
++
++ // 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();
++ }
++
++ // Execute the pending shell commands
++ m_commandExecuter->performAsynchronousCommandExecution(renderViews);
++
++ if (preprocessingComplete && activeProfiler())
++ m_frameProfiler->writeResults();
++ }
++
++ // Perform the last swapBuffers calls
++ // 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
++ const bool swapBuffers = submissionData.lastBoundFBOId == m_submissionContext->defaultFBO()
++ && surfaceLock.isSurfaceValid()
++ && m_shouldSwapBuffers;
++ m_submissionContext->endDrawing(swapBuffers);
++ }
++
++ // Reset RenderQueue and destroy the renderViews
++ m_renderQueue.reset();
++
++ // Allow next frame to be built once we are done doing all rendering
++ m_vsyncFrameAdvanceService->proceedToNextFrame();
++ }
++
++ // Called by RenderViewJobs
++ // When the frameQueue is complete and we are using a renderThread
++ // we allow the render thread to proceed
++ void Renderer::enqueueRenderView(RenderView *renderView, int submitOrder)
++ {
++ QMutexLocker locker(m_renderQueue.mutex()); // Prevent out of order execution
++ // We cannot use a lock free primitive here because:
++ // - QList 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_running.loadRelaxed())
++ Q_ASSERT(m_submitRenderViewsSemaphore.available() == 0);
++ m_submitRenderViewsSemaphore.release(1);
++ }
++ }
++
++ Profiling::FrameProfiler *Renderer::activeProfiler() const
++ {
++ if (m_services && m_services->systemInformation()->isGraphicsTraceEnabled()) {
++ if (m_frameProfiler.isNull())
++ m_frameProfiler.reset(new Profiling::FrameProfiler(m_services->systemInformation()));
++
++ return m_frameProfiler.data();
++ }
++
++ return nullptr;
++ }
++
++ bool Renderer::waitUntilReadyToSubmit()
++ {
++ // Make sure that we've been told to render before rendering
++ // Prevent ouf of order execution
++ m_submitRenderViewsSemaphore.acquire(1);
++
++ // Check if shutdown has been requested
++ if (m_running.loadRelaxed() == 0)
++ return false;
++
++ // 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)
++ {
++ return m_commandExecuter->executeCommand(args);
++ }
++
++ /*!
++ \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 std::vector<RenderView *> &renderViews)
++ {
++ OpenGLVertexArrayObject *vao = nullptr;
++ QHash<HVao, bool> updatedTable;
++
++ for (RenderView *rv: renderViews) {
++ rv->forEachCommand([&] (RenderCommand &command) {
++ // 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);
++ GLShader *shader = command.m_glShader;
++
++ // 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;
++
++ // If shader was loaded this frame, skip creating VAO for the command
++ // as we have to wait for next frame to make sure command was build against valid shader
++ if (m_lastLoadedShaderIds.contains(command.m_shaderId)) {
++ command.m_isValid = false;
++ return;
++ }
++
++ // 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_activeAttributes.empty() && (requiresFullVAOUpdate || requiresPartialVAOUpdate)) {
++ Profiling::GLTimeRecorder recorder(Profiling::VAOUpload, activeProfiler());
++ // Activate shader
++ m_submissionContext->activateShader(shader);
++ // 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();
++ } else if (command.m_type == RenderCommand::Compute) {
++ GLShader *shader = command.m_glShader;
++ Q_ASSERT(shader);
++ }
++ });
++ }
++
++ // 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 : m_dirtyAttributes)
++ attribute->unsetDirty();
++ m_dirtyAttributes.clear();
++
++ for (Geometry *geometry : m_dirtyGeometry)
++ geometry->unsetDirty();
++ m_dirtyGeometry.clear();
++ }
++
++ // Executed in a job
++ void Renderer::lookForAbandonedVaos()
++ {
++ const std::vector<HVao> &activeVaos = m_glResourceManagers->vaoManager()->activeHandles();
++ for (HVao handle : activeVaos) {
++ OpenGLVertexArrayObject *vao = m_glResourceManagers->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_glResourceManagers->glShaderManager())) {
++ m_abandonedVaosMutex.lock();
++ m_abandonedVaos.push_back(handle);
++ m_abandonedVaosMutex.unlock();
++ }
++ }
++ }
++
++ // Executed in a job
++ void Renderer::lookForDirtyBuffers()
++ {
++ const std::vector<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);
++ }
++ }
++
++ // Called in prepareSubmission
++ void Renderer::lookForDownloadableBuffers()
++ {
++ m_downloadableBuffers.clear();
++ const std::vector<HBuffer> &activeBufferHandles = m_nodesManager->bufferManager()->activeHandles();
++ for (const HBuffer &handle : activeBufferHandles) {
++ Buffer *buffer = m_nodesManager->bufferManager()->data(handle);
++ if (buffer->access() & Qt3DCore::QBuffer::Read)
++ m_downloadableBuffers.push_back(buffer->peerId());
++ }
++ }
++
++ // Executed in a job
++ void Renderer::lookForDirtyTextures()
++ {
++ // To avoid having Texture or TextureImage maintain relationships between
++ // one another, we instead perform a lookup here to check if a texture
++ // image has been updated to then notify textures referencing the image
++ // that they need to be updated
++ TextureImageManager *imageManager = m_nodesManager->textureImageManager();
++ const std::vector<HTextureImage> &activeTextureImageHandles = imageManager->activeHandles();
++ Qt3DCore::QNodeIdVector dirtyImageIds;
++ for (const HTextureImage &handle: activeTextureImageHandles) {
++ TextureImage *image = imageManager->data(handle);
++ if (image->isDirty()) {
++ dirtyImageIds.push_back(image->peerId());
++ image->unsetDirty();
++ }
++ }
++
++ TextureManager *textureManager = m_nodesManager->textureManager();
++ const std::vector<HTexture> &activeTextureHandles = textureManager->activeHandles();
++ for (const HTexture &handle: activeTextureHandles) {
++ Texture *texture = textureManager->data(handle);
++ const Qt3DCore::QNodeIdVector imageIds = texture->textureImageIds();
++
++ // Does the texture reference any of the dirty texture images?
++ for (const Qt3DCore::QNodeId &imageId : imageIds) {
++ if (dirtyImageIds.contains(imageId)) {
++ texture->addDirtyFlag(Texture::DirtyImageGenerators);
++ break;
++ }
++ }
++
++ // Dirty meaning that something has changed on the texture
++ // either properties, parameters, shared texture id, generator or a texture image
++ if (texture->dirtyFlags() != Texture::NotDirty)
++ m_dirtyTextures.push_back(handle);
++ // Note: texture dirty flags are reset when actually updating the
++ // textures in updateGLResources() as resetting flags here would make
++ // us lose information about what was dirty exactly.
++ }
++ }
++
++ // Executed in a job
++ void Renderer::reloadDirtyShaders()
++ {
++ Q_ASSERT(isRunning());
++ const std::vector<HTechnique> &activeTechniques = m_nodesManager->techniqueManager()->activeHandles();
++ const std::vector<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 Qt3DCore::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);
++
++ // Shader could be null if the pass doesn't reference one yet
++ if (!shader)
++ continue;
++
++ 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 <= QShaderProgram::Compute; i++) {
++ const auto shaderType = static_cast<QShaderProgram::ShaderType>(i);
++ if (!shaderBuilder->shaderGraph(shaderType).isValid())
++ continue;
++
++ if (shaderBuilder->isShaderCodeDirty(shaderType)) {
++ shaderBuilder->generateCode(shaderType);
++ Qt3DCore::moveAtEnd(m_shaderBuilderUpdates, shaderBuilder->takePendingUpdates());
++ }
++
++ const auto code = shaderBuilder->shaderCode(shaderType);
++ shader->setShaderCode(shaderType, code);
++ }
++ }
++
++ if (shader->isDirty()) {
++ if (!Qt3DCore::contains(m_dirtyShaders, shaderHandle))
++ m_dirtyShaders.push_back(shaderHandle);
++ }
++ }
++ }
++ }
++ }
++
++ // Executed in job (in main thread when jobs are done)
++ void Renderer::sendShaderChangesToFrontend(Qt3DCore::QAspectManager *manager)
++ {
++ Q_ASSERT(isRunning());
++
++ // Sync Shader
++ const std::vector<HShader> &activeShaders = m_nodesManager->shaderManager()->activeHandles();
++ for (const HShader &handle :activeShaders) {
++ Shader *s = m_nodesManager->shaderManager()->data(handle);
++ if (!s)
++ continue;
++
++ if (s->requiresFrontendSync()) {
++ QShaderProgram *frontend = static_cast<decltype(frontend)>(manager->lookupNode(s->peerId()));
++ // Could happen as a backend shader might live beyong the frontend
++ // the time needed to destroy the GLShader assoicated with it.
++ if (!frontend)
++ continue;
++ QShaderProgramPrivate *dFrontend = static_cast<decltype(dFrontend)>(Qt3DCore::QNodePrivate::get(frontend));
++ s->unsetRequiresFrontendSync();
++ dFrontend->setStatus(s->status());
++ dFrontend->setLog(s->log());
++ }
++ }
++
++ // Sync ShaderBuilder
++ const std::vector<ShaderBuilderUpdate> shaderBuilderUpdates = Qt3DCore::moveAndClear(m_shaderBuilderUpdates);
++ for (const ShaderBuilderUpdate &update : shaderBuilderUpdates) {
++ QShaderProgramBuilder *builder = static_cast<decltype(builder)>(manager->lookupNode(update.builderId));
++ QShaderProgramBuilderPrivate *dBuilder = static_cast<decltype(dBuilder)>(Qt3DCore::QNodePrivate::get(builder));
++ dBuilder->setShaderCode(update.shaderCode, update.shaderType);
++ }
++ }
++
++ // Executed in a job (in main thread when jobs are done)
++ void Renderer::sendTextureChangesToFrontend(Qt3DCore::QAspectManager *manager)
++ {
++ const std::vector<QPair<Texture::TextureUpdateInfo, Qt3DCore::QNodeIdVector>> updateTextureProperties = Qt3DCore::moveAndClear(m_updatedTextureProperties);
++ for (const auto &pair : updateTextureProperties) {
++ const Qt3DCore::QNodeIdVector targetIds = pair.second;
++ for (const Qt3DCore::QNodeId &targetId: targetIds) {
++ // Lookup texture
++ Texture *t = m_nodesManager->textureManager()->lookupResource(targetId);
++ // If backend texture is Dirty, some property has changed and the properties we are
++ // about to send are already outdate
++ if (t == nullptr || t->dirtyFlags() != Texture::NotDirty)
++ continue;
++
++ QAbstractTexture *texture = static_cast<QAbstractTexture *>(manager->lookupNode(targetId));
++ if (!texture)
++ continue;
++ const TextureProperties &properties = pair.first.properties;
++
++ const bool blocked = texture->blockNotifications(true);
++ texture->setWidth(properties.width);
++ texture->setHeight(properties.height);
++ texture->setDepth(properties.depth);
++ texture->setLayers(properties.layers);
++ texture->setFormat(properties.format);
++ texture->blockNotifications(blocked);
++
++ QAbstractTexturePrivate *dTexture = static_cast<QAbstractTexturePrivate *>(Qt3DCore::QNodePrivate::get(texture));
++
++ dTexture->setStatus(properties.status);
++ dTexture->setHandleType(pair.first.handleType);
++ dTexture->setHandle(pair.first.handle);
++ }
++ }
++ }
++
++ // Executed in main thread when jobs done
++ void Renderer::sendSetFenceHandlesToFrontend(Qt3DCore::QAspectManager *manager)
++ {
++ const std::vector<QPair<Qt3DCore::QNodeId, GLFence>> updatedSetFence = Qt3DCore::moveAndClear(m_updatedSetFences);
++ FrameGraphManager *fgManager = m_nodesManager->frameGraphManager();
++ for (const auto &pair : updatedSetFence) {
++ FrameGraphNode *fgNode = fgManager->lookupNode(pair.first);
++ if (fgNode != nullptr) { // Node could have been deleted before we got a chance to notify it
++ Q_ASSERT(fgNode->nodeType() == FrameGraphNode::SetFence);
++ QSetFence *frontend = static_cast<decltype(frontend)>(manager->lookupNode(fgNode->peerId()));
++ QSetFencePrivate *dFrontend = static_cast<decltype(dFrontend)>(Qt3DCore::QNodePrivate::get(frontend));
++ dFrontend->setHandleType(QSetFence::OpenGLFenceId);
++ dFrontend->setHandle(QVariant::fromValue(pair.second));
++ }
++ }
++ }
++
++ // Executed in main thread when jobs done
++ void Renderer::sendDisablesToFrontend(Qt3DCore::QAspectManager *manager)
++ {
++ // SubtreeEnabled
++ const auto updatedDisables = Qt3DCore::moveAndClear(m_updatedDisableSubtreeEnablers);
++ for (const auto &nodeId : updatedDisables) {
++ QSubtreeEnabler *frontend = static_cast<decltype(frontend)>(manager->lookupNode(nodeId));
++ frontend->setEnabled(false);
++ }
++
++ // Compute Commands
++ const std::vector<HComputeCommand> &activeCommands = m_nodesManager->computeJobManager()->activeHandles();
++ for (const HComputeCommand &handle :activeCommands) {
++ ComputeCommand *c = m_nodesManager->computeJobManager()->data(handle);
++ if (c->hasReachedFrameCount()) {
++ QComputeCommand *frontend = static_cast<decltype(frontend)>(manager->lookupNode(c->peerId()));
++ frontend->setEnabled(false);
++ c->resetHasReachedFrameCount();
++ }
++ }
++ }
++
++ // Render Thread (or QtQuick RenderThread when using Scene3D)
++ // Scene3D: When using Scene3D rendering, we can't assume that when
++ // updateGLResources is called, the resource handles points to still existing
++ // objects. This is because Scene3D calls doRender independently of whether all
++ // jobs have completed or not which in turn calls proceedToNextFrame under some
++ // conditions. Such conditions are usually met on startup to avoid deadlocks.
++ // proceedToNextFrame triggers the syncChanges calls for the next frame, which
++ // may contain destruction changes targeting resources. When the above
++ // happens, this can result in the dirtyResource vectors containing handles of
++ // objects that may already have been destroyed
++ void Renderer::updateGLResources()
++ {
++ {
++ // Update active fence objects:
++ // - Destroy fences that have reached their signaled state
++ GLFenceManager *fenceManager = m_glResourceManagers->glFenceManager();
++ const auto end = fenceManager->end();
++ auto it = fenceManager->begin();
++ while (it != end) {
++ const GLFence fence = it.value();
++ if (m_submissionContext->wasSyncSignaled(fence)) {
++ // Fence was signaled, we delete it
++ // before removing the entry from the manager
++ m_submissionContext->deleteSync(fence);
++ it = fenceManager->erase(it);
++ } else {
++ ++it;
++ }
++ }
++ }
++
++ {
++ Profiling::GLTimeRecorder recorder(Profiling::BufferUpload, activeProfiler());
++ const std::vector<HBuffer> dirtyBufferHandles = Qt3DCore::moveAndClear(m_dirtyBuffers);
++ for (const HBuffer &handle: dirtyBufferHandles) {
++ Buffer *buffer = m_nodesManager->bufferManager()->data(handle);
++
++ // Can be null when using Scene3D rendering
++ if (buffer == nullptr)
++ continue;
++
++ // 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);
++ // Update the glBuffer data
++ m_submissionContext->updateBuffer(buffer);
++ buffer->unsetDirty();
++ }
++ }
++
++ {
++ Profiling::GLTimeRecorder recorder(Profiling::ShaderUpload, activeProfiler());
++ const std::vector<HShader> dirtyShaderHandles = Qt3DCore::moveAndClear(m_dirtyShaders);
++ ShaderManager *shaderManager = m_nodesManager->shaderManager();
++ for (const HShader &handle: dirtyShaderHandles) {
++ Shader *shader = shaderManager->data(handle);
++
++ // Can be null when using Scene3D rendering
++ if (shader == nullptr)
++ continue;
++
++ // Compile shader
++ m_submissionContext->loadShader(shader, shaderManager, m_glResourceManagers->glShaderManager());
++
++ // Release any VAO referencing this shader. When we build VAO, we
++ // rely on the shader introspection to know the active uniforms In
++ // case the shader is reloaded, we might end up having more/less
++ // active uniforms than prior therefore we need to ensure VAO is
++ // rebuilt.
++ VAOManager *vaoManager = m_glResourceManagers->vaoManager();
++ const std::vector<HVao> activeVaos = vaoManager->activeHandles(); // copy
++ for (const HVao &vao : activeVaos) {
++ if (vao.data() && vao->key().second == shader->peerId())
++ vaoManager->releaseResource(vao->key());
++ }
++
++ // Record shader id in vector of vectors loaded this frame
++ // Given commands need to be built against loaded shader (at next frame)
++ // we can make use of this vector to skip operations that target this
++ // shader for this frame
++ m_lastLoadedShaderIds.push_back(shader->peerId());
++ }
++ }
++
++ {
++ Profiling::GLTimeRecorder recorder(Profiling::TextureUpload, activeProfiler());
++ const std::vector<HTexture> activeTextureHandles = Qt3DCore::moveAndClear(m_dirtyTextures);
++ for (const HTexture &handle: activeTextureHandles) {
++ Texture *texture = m_nodesManager->textureManager()->data(handle);
++
++ // Can be null when using Scene3D rendering
++ if (texture == nullptr)
++ continue;
++
++ // Create or Update GLTexture (the GLTexture instance is created
++ // (not the underlying GL instance) if required and all things that
++ // can take place without a GL context are done here)
++ updateTexture(texture);
++ }
++ // We want to upload textures data at this point as the SubmissionThread and
++ // AspectThread are locked ensuring no races between Texture/TextureImage and
++ // GLTexture
++ Qt3DCore::QNodeIdVector updatedTexturesForFrame;
++ if (m_submissionContext != nullptr) {
++ GLTextureManager *glTextureManager = m_glResourceManagers->glTextureManager();
++ const std::vector<HGLTexture> &glTextureHandles = glTextureManager->activeHandles();
++ // Upload texture data
++ for (const HGLTexture &glTextureHandle : glTextureHandles) {
++ GLTexture *glTexture = glTextureManager->data(glTextureHandle);
++
++ // We create/update the actual GL texture using the GL context at this point
++ const GLTexture::TextureUpdateInfo info = glTexture->createOrUpdateGLTexture();
++
++ // GLTexture creation provides us width/height/format ... information
++ // for textures which had not initially specified these information (TargetAutomatic...)
++ // Gather these information and store them to be distributed by a change next frame
++ const Qt3DCore::QNodeIdVector referenceTextureIds = { glTextureManager->texNodeIdForGLTexture.value(glTexture) };
++ // Store properties and referenceTextureIds
++ if (info.wasUpdated) {
++ Texture::TextureUpdateInfo updateInfo;
++ updateInfo.properties = info.properties;
++ updateInfo.handleType = QAbstractTexture::OpenGLTextureId;
++ updateInfo.handle = info.texture ? QVariant(info.texture->textureId()) : QVariant();
++ m_updatedTextureProperties.push_back({updateInfo, referenceTextureIds});
++ updatedTexturesForFrame += referenceTextureIds;
++ }
++ }
++ }
++
++ // If the underlying GL Texture was for whatever reason recreated, we need to make sure
++ // that if it is used as a color attachment, we rebuild the FBO next time it is used
++ m_submissionContext->setUpdatedTexture(std::move(updatedTexturesForFrame));
++
++ // Record ids of texture to cleanup while we are still blocking the aspect thread
++ m_textureIdsToCleanup += m_nodesManager->textureManager()->takeTexturesIdsToCleanup();
++ }
++
++ // Record list of buffer that might need uploading
++ lookForDownloadableBuffers();
++
++ // Remove destroyed FBOs
++ {
++ const Qt3DCore::QNodeIdVector destroyedRenderTargetIds = m_nodesManager->renderTargetManager()->takeRenderTargetIdsToCleanup();
++ for (const Qt3DCore::QNodeId &renderTargetId : destroyedRenderTargetIds)
++ m_submissionContext->releaseRenderTarget(renderTargetId);
++ }
++ }
++
++ // 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(m_nodesManager->textureImageManager());
++ if (!isValid) {
++ qWarning() << Q_FUNC_INFO << "QTexture referencing invalid QTextureImages";
++ return;
++ }
++
++ // All textures are unique, if you instanciate twice the exact same texture
++ // this will create 2 identical GLTextures, no sharing will take place
++
++ // Try to find the associated GLTexture for the backend Texture
++ GLTextureManager *glTextureManager = m_glResourceManagers->glTextureManager();
++ GLTexture *glTexture = glTextureManager->lookupResource(texture->peerId());
++
++ // No GLTexture associated yet -> create it
++ if (glTexture == nullptr) {
++ glTexture = glTextureManager->getOrCreateResource(texture->peerId());
++ glTextureManager->texNodeIdForGLTexture.insert(glTexture, texture->peerId());
++ }
++
++ // Update GLTexture to match Texture instance
++ const Texture::DirtyFlags dirtyFlags = texture->dirtyFlags();
++ if (dirtyFlags.testFlag(Texture::DirtySharedTextureId))
++ glTexture->setSharedTextureId(texture->sharedTextureId());
++
++ if (dirtyFlags.testFlag(Texture::DirtyProperties))
++ glTexture->setProperties(texture->properties());
++
++ if (dirtyFlags.testFlag(Texture::DirtyParameters))
++ glTexture->setParameters(texture->parameters());
++
++ // Will make the texture requestUpload
++ if (dirtyFlags.testFlag(Texture::DirtyImageGenerators)) {
++ const Qt3DCore::QNodeIdVector textureImageIds = texture->textureImageIds();
++ std::vector<GLTexture::Image> images;
++ images.reserve(textureImageIds.size());
++ // TODO: Move this into GLTexture directly
++ for (const Qt3DCore::QNodeId &textureImageId : textureImageIds) {
++ const TextureImage *img = m_nodesManager->textureImageManager()->lookupResource(textureImageId);
++ if (img == nullptr) {
++ qWarning() << Q_FUNC_INFO << "invalid TextureImage handle";
++ } else {
++ GLTexture::Image glImg {img->dataGenerator(), img->layer(), img->mipLevel(), img->face()};
++ images.push_back(glImg);
++ }
++ }
++ glTexture->setImages(std::move(images));
++ }
++
++ // Will make the texture requestUpload
++ if (dirtyFlags.testFlag(Texture::DirtyDataGenerator))
++ glTexture->setGenerator(texture->dataGenerator());
++
++ // Will make the texture requestUpload
++ if (dirtyFlags.testFlag(Texture::DirtyPendingDataUpdates))
++ glTexture->addTextureDataUpdates(texture->takePendingTextureDataUpdates());
++
++ // Unset the dirty flag on the texture
++ texture->unsetDirty();
++ }
++
++ // Render Thread
++ void Renderer::cleanupTexture(Qt3DCore::QNodeId cleanedUpTextureId)
++ {
++ GLTextureManager *glTextureManager = m_glResourceManagers->glTextureManager();
++ GLTexture *glTexture = glTextureManager->lookupResource(cleanedUpTextureId);
++
++ // Destroying the GLTexture implicitely also destroy the GL resources
++ if (glTexture != nullptr) {
++ glTextureManager->releaseResource(cleanedUpTextureId);
++ glTextureManager->texNodeIdForGLTexture.remove(glTexture);
++ }
++ }
++
++ // Render Thread
++ void Renderer::cleanupShader(const Shader *shader)
++ {
++ if (!shader)
++ return;
++
++ GLShaderManager *glShaderManager = m_glResourceManagers->glShaderManager();
++ GLShader *glShader = glShaderManager->lookupResource(shader->peerId());
++
++ if (glShader != nullptr)
++ glShaderManager->abandon(glShader, shader);
++ }
++
++ // Called by SubmitRenderView
++ void Renderer::downloadGLBuffers()
++ {
++ const std::vector<Qt3DCore::QNodeId> downloadableHandles = Qt3DCore::moveAndClear(m_downloadableBuffers);
++ for (const Qt3DCore::QNodeId &bufferId : downloadableHandles) {
++ BufferManager *bufferManager = m_nodesManager->bufferManager();
++ BufferManager::ReadLocker locker(const_cast<const BufferManager *>(bufferManager));
++ Buffer *buffer = bufferManager->lookupResource(bufferId);
++ // Buffer could have been destroyed at this point
++ if (!buffer)
++ continue;
++ // locker is protecting us from the buffer being destroy while we're looking
++ // up its content
++ const QByteArray content = m_submissionContext->downloadBufferContent(buffer);
++ m_sendBufferCaptureJob->addRequest(QPair<Qt3DCore::QNodeId, QByteArray>(bufferId, content));
++ }
++ }
++
++ // Happens in RenderThread context when all RenderViewJobs are done
++ // Returns the id of the last bound FBO
++ Renderer::ViewSubmissionResultData Renderer::submitRenderViews(const std::vector<RenderView *> &renderViews)
++ {
++ QElapsedTimer timer;
++ quint64 queueElapsed = 0;
++ timer.start();
++
++ const size_t renderViewsCount = renderViews.size();
++ quint64 frameElapsed = queueElapsed;
++ m_lastFrameCorrect.storeRelaxed(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 RenderView *rv: renderViews) {
++ previousSurface = rv->surface();
++ if (previousSurface)
++ break;
++ }
++ QSurface *lastUsedSurface = nullptr;
++
++ bool imGuiOverlayShown = false;
++ for (size_t i = 0; i < renderViewsCount; ++i) {
++ // Initialize GraphicsContext for drawing
++ // If the RenderView has a RenderStateSet defined
++ RenderView *renderView = renderViews.at(i);
++
++ if (renderView->shouldSkipSubmission())
++ continue;
++
++ // 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.storeRelaxed(0);
++ continue;
++ }
++
++ lastUsedSurface = surface;
++ const bool surfaceHasChanged = surface != previousSurface;
++
++ if (surfaceHasChanged && previousSurface) {
++ const bool swapBuffers = lastBoundFBOId == m_submissionContext->defaultFBO()
++ && surfaceLock.isSurfaceValid()
++ && m_shouldSwapBuffers;
++ // 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.storeRelaxed(0);
++ continue;
++ }
++
++ previousSurface = surface;
++ lastBoundFBOId = m_submissionContext->boundFrameBufferObject();
++ }
++
++ // Apply Memory Barrier if needed
++ if (renderView->memoryBarrier() != QMemoryBarrier::None)
++ m_submissionContext->memoryBarrier(renderView->memoryBarrier());
++
++
++ // Insert Fence into command stream if needed
++ const Qt3DCore::QNodeIdVector insertFenceIds = renderView->insertFenceIds();
++ GLFenceManager *fenceManager = m_glResourceManagers->glFenceManager();
++ for (const Qt3DCore::QNodeId &insertFenceId : insertFenceIds) {
++ // If the fence is not in the manager, then it hasn't been inserted
++ // into the command stream yet.
++ if (fenceManager->find(insertFenceId) == fenceManager->end()) {
++ // Insert fence into command stream
++ GLFence glFence = m_submissionContext->fenceSync();
++ // Record glFence
++ fenceManager->insert(insertFenceId, glFence);
++ // Add entry for notification changes to be sent
++ m_updatedSetFences.push_back({insertFenceId, glFence});
++ }
++ // If it is in the manager, then it hasn't been signaled yet,
++ // nothing we can do but try at the next frame
++ }
++
++ // Wait for fences if needed
++ const QList<WaitFence::Data> waitFences = renderView->waitFences();
++ for (const auto &waitFence : waitFences) {
++ // TO DO
++ if (waitFence.handleType != QWaitFence::OpenGLFenceId) {
++ qWarning() << "WaitFence handleType should be OpenGLFenceId when using the Qt 3D OpenGL renderer";
++ continue;
++ }
++ GLFence fence = reinterpret_cast<GLFence>(waitFence.handle.value<qintptr>());
++ if (fence == nullptr)
++ continue;
++
++ if (waitFence.waitOnCPU)
++ m_submissionContext->clientWaitSync(fence, waitFence.timeout);
++ else
++ m_submissionContext->waitSync(fence);
++ }
++
++ // 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, activeProfiler());
++ // 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, activeProfiler());
++ m_submissionContext->activateRenderTarget(renderView->renderTargetId(),
++ renderView->attachmentPack(),
++ lastBoundFBOId);
++ }
++
++ {
++ Profiling::GLTimeRecorder recorder(Profiling::ClearBuffer, activeProfiler());
++ // 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 std::vector<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());
++
++ // Execute the render commands
++ if (!executeCommandsSubmission(renderView))
++ m_lastFrameCorrect.storeRelaxed(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());
++ 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);
++ const Qt3DCore::QNodeId renderCaptureId = renderView->renderCaptureNodeId();
++ QMutexLocker lock(&m_pendingRenderCaptureSendRequestsMutex);
++ if (!Qt3DCore::contains(m_pendingRenderCaptureSendRequests, renderCaptureId))
++ m_pendingRenderCaptureSendRequests.push_back(renderView->renderCaptureNodeId());
++ }
++
++ if (renderView->isDownloadBuffersEnable())
++ downloadGLBuffers();
++
++ // Perform BlitFramebuffer operations
++ if (renderView->hasBlitFramebufferInfo()) {
++ const auto &blitFramebufferInfo = renderView->blitFrameBufferInfo();
++ const Qt3DCore::QNodeId inputTargetId = blitFramebufferInfo.sourceRenderTargetId;
++ const Qt3DCore::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);
++ }
++
++ #ifndef Q_OS_INTEGRITY
++ if (!imGuiOverlayShown && renderView->showDebugOverlay()) {
++ imGuiOverlayShown = true;
++ if (!m_imGuiRenderer) {
++ m_imGuiRenderer = new Debug::ImGuiRenderer(this);
++ if (m_settings)
++ m_imGuiRenderer->setCapabilities(m_settings->capabilities());
++ }
++
++ m_imGuiRenderer->renderDebugOverlay(renderViews, renderView, m_jobsInLastFrame);
++ }
++ #endif
++
++ 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 (lastUsedSurface && 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() const
++ {
++ // 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 && m_settings->renderPolicy() == QRenderSettings::Always)
++ || m_dirtyBits.marked != 0
++ || m_dirtyBits.remaining != 0
++ || !m_lastFrameCorrect.loadRelaxed());
++ }
++
++ void Renderer::skipNextFrame()
++ {
++ Q_ASSERT(m_settings->renderPolicy() != QRenderSettings::Always);
++
++ // make submitRenderViews() actually run
++ m_renderQueue.setNoRender();
++ m_submitRenderViewsSemaphore.release(1);
++ }
++
++ void Renderer::jobsDone(Qt3DCore::QAspectManager *manager)
++ {
++ // called in main thread once all jobs are done running
++
++ // sync captured renders to frontend
++ QMutexLocker lock(&m_pendingRenderCaptureSendRequestsMutex);
++ const std::vector<Qt3DCore::QNodeId> pendingCaptureIds = Qt3DCore::moveAndClear(m_pendingRenderCaptureSendRequests);
++ lock.unlock();
++ for (const Qt3DCore::QNodeId &id : pendingCaptureIds) {
++ auto *backend = static_cast<Qt3DRender::Render::RenderCapture *>
++ (m_nodesManager->frameGraphManager()->lookupNode(id));
++ backend->syncRenderCapturesToFrontend(manager);
++ }
++
++ // Do we need to notify any texture about property changes?
++ if (m_updatedTextureProperties.size() > 0)
++ sendTextureChangesToFrontend(manager);
++
++ sendDisablesToFrontend(manager);
++ sendSetFenceHandlesToFrontend(manager);
++ }
++
++ bool Renderer::processMouseEvent(QObject *object, QMouseEvent *event)
++ {
++ Q_UNUSED(object);
++
++ #ifndef Q_OS_INTEGRITY
++ if (m_imGuiRenderer)
++ m_imGuiRenderer->processEvent(event);
++ #endif
++ return false;
++ }
++
++ bool Renderer::processKeyEvent(QObject *object, QKeyEvent *event)
++ {
++ Q_UNUSED(object);
++
++ #ifndef Q_OS_INTEGRITY
++ if (m_imGuiRenderer)
++ m_imGuiRenderer->processEvent(event);
++ #endif
++ return false;
++ }
++
++ // Jobs we may have to run even if no rendering will happen
++ std::vector<Qt3DCore::QAspectJobPtr> Renderer::preRenderingJobs()
++ {
++ if (m_sendBufferCaptureJob->hasRequests())
++ return {m_sendBufferCaptureJob};
++ return {};
++ }
++
++ // 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
++ std::vector<Qt3DCore::QAspectJobPtr> Renderer::renderBinJobs()
++ {
++ std::vector<Qt3DCore::QAspectJobPtr> renderBinJobs;
++
++ // Remove previous dependencies
++ m_cleanupJob->removeDependency(QWeakPointer<Qt3DCore::QAspectJob>());
++
++ const bool dirtyParametersForCurrentFrame = m_dirtyBits.marked & AbstractRenderer::ParameterDirty;
++ const BackendNodeDirtySet dirtyBitsForFrame = m_dirtyBits.marked | m_dirtyBits.remaining;
++ m_dirtyBits.marked = {};
++ m_dirtyBits.remaining = {};
++ BackendNodeDirtySet notCleared = {};
++
++ // Add jobs
++ if (dirtyBitsForFrame & AbstractRenderer::TransformDirty)
++ renderBinJobs.push_back(m_updateShaderDataTransformJob);
++
++ // TO DO: Conditionally add if skeletons dirty
++ renderBinJobs.push_back(m_cleanupJob);
++
++ // 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_textureGathererJob);
++
++ // Layer cache is dependent on layers, layer filters (hence FG structure
++ // changes) and the enabled flag on entities
++ const bool entitiesEnabledDirty = dirtyBitsForFrame & AbstractRenderer::EntityEnabledDirty;
++ const bool frameGraphDirty = dirtyBitsForFrame & AbstractRenderer::FrameGraphDirty;
++ const bool layersDirty = dirtyBitsForFrame & AbstractRenderer::LayersDirty;
++ const bool layersCacheNeedsToBeRebuilt = layersDirty || entitiesEnabledDirty || frameGraphDirty;
++ const bool shadersDirty = dirtyBitsForFrame & AbstractRenderer::ShadersDirty;
++ const bool materialDirty = dirtyBitsForFrame & AbstractRenderer::MaterialDirty;
++ const bool lightsDirty = dirtyBitsForFrame & AbstractRenderer::LightsDirty;
++ const bool computeableDirty = dirtyBitsForFrame & AbstractRenderer::ComputeDirty;
++ const bool renderableDirty = dirtyBitsForFrame & AbstractRenderer::GeometryDirty;
++ const bool materialCacheNeedsToBeRebuilt = shadersDirty || materialDirty || frameGraphDirty;
++ const bool renderCommandsDirty = materialCacheNeedsToBeRebuilt || renderableDirty || computeableDirty;
++
++ if (renderableDirty)
++ renderBinJobs.push_back(m_renderableEntityFilterJob);
++
++ if (computeableDirty)
++ renderBinJobs.push_back(m_computableEntityFilterJob);
++
++ if (lightsDirty)
++ renderBinJobs.push_back(m_lightGathererJob);
++
++ // Sync rendering is synchronous, queue should always be reset
++ // when this is called
++ Q_ASSERT(m_renderQueue.wasReset());
++ // 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
++ if (frameGraphDirty) {
++ FrameGraphVisitor visitor(m_nodesManager->frameGraphManager());
++ m_frameGraphLeaves = 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 (!Qt3DCore::contains(m_frameGraphLeaves, leafNode))
++ m_cache.leafNodeCache.remove(leafNode);
++ }
++
++ // Handle single shot subtree enablers
++ const auto subtreeEnablers = visitor.takeEnablersToDisable();
++ for (auto *node : subtreeEnablers)
++ m_updatedDisableSubtreeEnablers.push_back(node->peerId());
++ }
++
++ int idealThreadCount = Qt3DCore::QAspectJobManager::idealThreadCount();
++
++ const size_t fgBranchCount = m_frameGraphLeaves.size();
++ if (fgBranchCount > 1) {
++ int workBranches = int(fgBranchCount);
++ for (auto leaf: m_frameGraphLeaves)
++ if (leaf->nodeType() == FrameGraphNode::NoDraw)
++ --workBranches;
++
++ if (idealThreadCount > 4 && workBranches)
++ idealThreadCount = qMax(4, idealThreadCount / workBranches);
++ }
++
++ for (size_t i = 0; i < fgBranchCount; ++i) {
++ FrameGraphNode *leaf = m_frameGraphLeaves[i];
++ RenderViewBuilder builder(leaf, int(i), this);
++ builder.setOptimalJobCount(leaf->nodeType() == FrameGraphNode::NoDraw ? 1 : idealThreadCount);
++
++ // If we have a new RV (wasn't in the cache before, then it contains no cached data)
++ const bool isNewRV = !m_cache.leafNodeCache.contains(leaf);
++ builder.setLayerCacheNeedsToBeRebuilt(layersCacheNeedsToBeRebuilt || isNewRV);
++ builder.setMaterialGathererCacheNeedsToBeRebuilt(materialCacheNeedsToBeRebuilt || isNewRV);
++ builder.setRenderCommandCacheNeedsToBeRebuilt(renderCommandsDirty || isNewRV);
++ builder.setLightCacheNeedsToBeRebuilt(lightsDirty);
++
++ // Insert leaf into cache
++ if (isNewRV) {
++ m_cache.leafNodeCache[leaf] = {};
++ }
++ builder.prepareJobs();
++ Qt3DCore::moveAtEnd(renderBinJobs, builder.buildJobHierachy());
++ }
++
++ // Set target number of RenderViews
++ m_renderQueue.setTargetRenderViewCount(int(fgBranchCount));
++
++ 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;
++
++ // Dirty Parameters might need 2 frames to react if the parameter references a texture
++ if (dirtyParametersForCurrentFrame)
++ m_dirtyBits.remaining |= AbstractRenderer::ParameterDirty;
++
++ return renderBinJobs;
++ }
++
++ Qt3DCore::QAbstractFrameAdvanceService *Renderer::frameAdvanceService() const
++ {
++ return static_cast<Qt3DCore::QAbstractFrameAdvanceService *>(m_vsyncFrameAdvanceService.data());
++ }
++
++ // Called by executeCommands
++ void Renderer::performDraw(const 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);
++ 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, activeProfiler());
++ 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_firstInstance);
++ } else {
++ Profiling::GLTimeRecorder recorder(Profiling::DrawArray, activeProfiler());
++ m_submissionContext->drawArraysInstancedBaseInstance(command->m_primitiveType,
++ command->m_firstVertex,
++ command->m_primitiveCount,
++ command->m_instanceCount,
++ command->m_firstInstance);
++ }
++ }
++
++ #if defined(QT3D_RENDER_ASPECT_OPENGL_DEBUG)
++ int err = m_submissionContext->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, activeProfiler());
++ GLShader *shader = m_glResourceManagers->glShaderManager()->lookupResource(command->m_shaderId);
++ m_submissionContext->activateShader(shader);
++ }
++ {
++ Profiling::GLTimeRecorder recorder(Profiling::UniformUpdate, activeProfiler());
++ m_submissionContext->setParameters(command->m_parameterPack, command->m_glShader);
++ }
++ {
++ Profiling::GLTimeRecorder recorder(Profiling::DispatchCompute, activeProfiler());
++ 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_submissionContext->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_shaderId);
++
++ VAOManager *vaoManager = m_glResourceManagers->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(RenderView *rv)
++ {
++ bool allCommandsIssued = true;
++
++ // Render drawing 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;
++
++ rv->forEachCommand([&] (RenderCommand &command) {
++
++ 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;
++ return;
++ }
++
++ vao = m_glResourceManagers->vaoManager()->data(command.m_vao);
++
++ // something may have went wrong when initializing the VAO
++ if (!vao->isSpecified()) {
++ allCommandsIssued = false;
++ return;
++ }
++
++ {
++ Profiling::GLTimeRecorder recorder(Profiling::ShaderUpdate, activeProfiler());
++ //// We activate the shader here
++ GLShader *shader = command.m_glShader;
++ if (!m_submissionContext->activateShader(shader)) {
++ allCommandsIssued = false;
++ return;
++ }
++ }
++
++ {
++ Profiling::GLTimeRecorder recorder(Profiling::VAOUpdate, activeProfiler());
++ // Bind VAO
++ vao->bind();
++ }
++
++ {
++ Profiling::GLTimeRecorder recorder(Profiling::UniformUpdate, activeProfiler());
++ //// Update program uniforms
++ if (!m_submissionContext->setParameters(command.m_parameterPack, command.m_glShader)) {
++ 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
++ return;
++ }
++ }
++
++ //// OpenGL State
++ // TO DO: Make states not dependendent on their backend node for this step
++ // Set state
++ RenderStateSet *localState = command.m_stateSet.data();
++
++
++ {
++ Profiling::GLTimeRecorder recorder(Profiling::StateUpdate, activeProfiler());
++ // 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(localState);
++ } 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,
++ const RenderCommand *command,
++ GLShader *shader,
++ bool forceUpdate)
++ {
++ m_dirtyAttributes.reserve(m_dirtyAttributes.size() + geometry->attributes().size());
++ const auto attributeIds = geometry->attributes();
++
++ for (Qt3DCore::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() == Qt3DCore::QAttribute::IndexAttribute) {
++ if ((attributeWasDirty = attribute->isDirty()) == true || forceUpdate)
++ m_submissionContext->specifyIndices(buffer);
++ // Vertex Attribute
++ } else if (Qt3DCore::contains(command->m_activeAttributes, attribute->nameId())) {
++ if ((attributeWasDirty = attribute->isDirty()) == true || forceUpdate) {
++ // Find the location for the attribute
++ const std::vector<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,
++ const RenderCommand *command) const
++ {
++ const auto attributeIds = geometry->attributes();
++
++ for (Qt3DCore::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() == Qt3DCore::QAttribute::IndexAttribute && attribute->isDirty()) || (Qt3DCore::contains(command->m_activeAttributes, 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 QList<Qt3DCore::QNodeId> buffersToRelease = m_nodesManager->bufferManager()->takeBuffersToRelease();
++ for (Qt3DCore::QNodeId bufferId : buffersToRelease)
++ m_submissionContext->releaseBuffer(bufferId);
++
++ // When Textures are cleaned up, their id is saved so that they can be
++ // cleaned up in the render thread
++ const QList<Qt3DCore::QNodeId> cleanedUpTextureIds = Qt3DCore::moveAndClear(m_textureIdsToCleanup);
++ for (const Qt3DCore::QNodeId &textureCleanedUpId: cleanedUpTextureIds)
++ cleanupTexture(textureCleanedUpId);
++
++ // Delete abandoned VAOs
++ m_abandonedVaosMutex.lock();
++ const std::vector<HVao> abandonedVaos = Qt3DCore::moveAndClear(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_glResourceManagers->vaoManager()->data(vaoHandle);
++ if (vao) {
++ vao->destroy();
++ // We remove VAO from manager using its VAOIdentifier
++ m_glResourceManagers->vaoManager()->release(vaoHandle);
++ }
++ }
++
++ // Abandon GL shaders when a Shader node is destroyed Note: We are sure
++ // that when this gets executed, all scene changes have been received and
++ // shader nodes updated
++ const QList<Qt3DCore::QNodeId> cleanedUpShaderIds = m_nodesManager->shaderManager()->takeShaderIdsToCleanup();
++ for (const Qt3DCore::QNodeId &shaderCleanedUpId: cleanedUpShaderIds) {
++ cleanupShader(m_nodesManager->shaderManager()->lookupResource(shaderCleanedUpId));
++ // We can really release the texture at this point
++ m_nodesManager->shaderManager()->releaseResource(shaderCleanedUpId);
++ }
++ m_lastLoadedShaderIds.clear();
++ }
++
++ const GraphicsApiFilterData *Renderer::contextInfo() const
++ {
++ return m_submissionContext->contextInfo();
++ }
++
++ SubmissionContext *Renderer::submissionContext() const
++ {
++ return m_submissionContext.data();
++ }
++
++ } // namespace OpenGL
++ } // namespace Render
++ } // namespace Qt3DRender
++
++ QT_END_NAMESPACE