summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSvenn-Arne Dragly <svenn-arne.dragly@qt.io>2018-01-22 10:42:55 +0100
committerSvenn-Arne Dragly <svenn-arne.dragly@qt.io>2018-02-02 10:20:11 +0000
commit46319648436814afb5a77755dde6681e304befaf (patch)
tree9a608913dd9be2ac99bb8e5ebf446dddd69c4c4f
parent30abe028f9a95fa32fbb77cb60063d062e13f7ff (diff)
Keep rendering in sync with aspect jobs by adding barriers
This makes sure that jobs that depend on a specific state of the renderer (such as context being initialized) never run before this is the case. This reduces the number of possible race conditions and checks we need to do to ensure the jobs and the renderer are in the correct state. This way we no longer swap buffers when nothing has been rendered, which in turn makes sure that we only draw one frame when the render policy is OnDemand and there are no changes. This change also adds a number of assertions on pointers to resources to make it easier to detect missing job dependencies. Finally, this change overhauls the job dependencies in Renderer and RenderViewBuilder. Task-number: QTBUG-66024 Change-Id: I3e4e9dd0dd53b5c88f5c1b17d68df42f28eae794 Reviewed-by: Paul Lemire <paul.lemire@kdab.com>
-rw-r--r--src/quick3d/imports/scene3d/scene3drenderer.cpp8
-rw-r--r--src/quick3d/imports/scene3d/scene3drenderer_p.h1
-rw-r--r--src/render/backend/abstractrenderer_p.h10
-rw-r--r--src/render/backend/render-backend.pri6
-rw-r--r--src/render/backend/renderbarrierjob.cpp111
-rw-r--r--src/render/backend/renderbarrierjob_p.h91
-rw-r--r--src/render/backend/renderer.cpp579
-rw-r--r--src/render/backend/renderer_p.h50
-rw-r--r--src/render/backend/renderqueue.cpp1
-rw-r--r--src/render/backend/renderview.cpp1
-rw-r--r--src/render/backend/renderviewbuilder.cpp23
-rw-r--r--src/render/frontend/qrenderaspect.cpp69
-rw-r--r--src/render/frontend/qrenderaspect_p.h4
-rw-r--r--src/render/geometry/geometryrenderer.cpp2
-rw-r--r--src/render/graphicshelpers/graphicscontext.cpp15
-rw-r--r--src/render/graphicshelpers/graphicscontext_p.h2
-rw-r--r--src/render/jobs/job_common_p.h9
-rw-r--r--src/render/jobs/loadtexturedatajob.cpp30
-rw-r--r--src/render/jobs/loadtexturedatajob_p.h6
-rw-r--r--src/render/services/vsyncframeadvanceservice.cpp2
-rw-r--r--tests/auto/render/commons/testrenderer.h10
-rw-r--r--tests/auto/render/filtercompatibletechniquejob/tst_filtercompatibletechniquejob.cpp3
-rw-r--r--tests/auto/render/geometryrenderer/tst_geometryrenderer.cpp3
-rw-r--r--tests/auto/render/render.pro3
-rw-r--r--tests/auto/render/renderbarrierjob/renderbarrierjob.pro9
-rw-r--r--tests/auto/render/renderbarrierjob/tst_renderbarrierjob.cpp118
-rw-r--r--tests/auto/render/renderer/tst_renderer.cpp156
-rw-r--r--tests/auto/render/renderviewbuilder/tst_renderviewbuilder.cpp21
28 files changed, 904 insertions, 439 deletions
diff --git a/src/quick3d/imports/scene3d/scene3drenderer.cpp b/src/quick3d/imports/scene3d/scene3drenderer.cpp
index 1c9fec4d2..b2e541d35 100644
--- a/src/quick3d/imports/scene3d/scene3drenderer.cpp
+++ b/src/quick3d/imports/scene3d/scene3drenderer.cpp
@@ -135,7 +135,6 @@ Scene3DRenderer::Scene3DRenderer(Scene3DItem *item, Qt3DCore::QAspectEngine *asp
, m_multisample(false) // this value is not used, will be synced from the Scene3DItem instead
, m_lastMultisample(false)
, m_needsShutdown(true)
- , m_blocking(false)
{
Q_CHECK_PTR(m_item);
Q_CHECK_PTR(m_item->window());
@@ -155,9 +154,6 @@ Scene3DRenderer::Scene3DRenderer(Scene3DItem *item, Qt3DCore::QAspectEngine *asp
ContextSaver saver;
static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(m_renderAspect))->renderInitialize(saver.context());
scheduleRootEntityChange();
-
- const bool blockingRendermode = !qgetenv("SCENE3D_BLOCKING_RENDERMODE").isEmpty();
- m_blocking = blockingRendermode;
}
Scene3DRenderer::~Scene3DRenderer()
@@ -207,6 +203,8 @@ void Scene3DRenderer::shutdown()
// would return early
m_item = nullptr;
+ static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(m_renderAspect))->abortRenderJobs();
+
// Exit the simulation loop so no more jobs are asked for. Once this
// returns it is safe to shutdown the renderer.
if (m_aspectEngine) {
@@ -311,7 +309,7 @@ void Scene3DRenderer::render()
m_finalFBO->bind();
// Render Qt3D Scene
- static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(m_renderAspect))->renderSynchronous(m_blocking);
+ static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(m_renderAspect))->tryRenderSynchronous();
// We may have called doneCurrent() so restore the context if the rendering surface was changed
// Note: keep in mind that the ContextSave also restores the surface when destroyed
diff --git a/src/quick3d/imports/scene3d/scene3drenderer_p.h b/src/quick3d/imports/scene3d/scene3drenderer_p.h
index eb2b930ef..7a85bc774 100644
--- a/src/quick3d/imports/scene3d/scene3drenderer_p.h
+++ b/src/quick3d/imports/scene3d/scene3drenderer_p.h
@@ -109,7 +109,6 @@ private:
bool m_multisample;
bool m_lastMultisample;
bool m_needsShutdown;
- bool m_blocking;
friend class Scene3DCleaner;
};
diff --git a/src/render/backend/abstractrenderer_p.h b/src/render/backend/abstractrenderer_p.h
index 5603a9195..4d2b5b3af 100644
--- a/src/render/backend/abstractrenderer_p.h
+++ b/src/render/backend/abstractrenderer_p.h
@@ -134,7 +134,7 @@ public:
// Threaded renderer
virtual void render() = 0;
// Synchronous renderer
- virtual void doRender(bool scene3dBlocking = false) = 0;
+ virtual void doRender() = 0;
virtual void cleanGraphicsResources() = 0;
@@ -150,7 +150,7 @@ public:
virtual QVector<Qt3DCore::QAspectJobPtr> renderBinJobs() = 0;
virtual Qt3DCore::QAspectJobPtr pickBoundingVolumeJob() = 0;
- virtual Qt3DCore::QAspectJobPtr syncTextureLoadingJob() = 0;
+ virtual Qt3DCore::QAspectJobPtr syncSkeletonLoadingJob() = 0;
virtual Qt3DCore::QAspectJobPtr expandBoundingVolumeJob() = 0;
virtual void setSceneRoot(Qt3DCore::QBackendNodeFactory *factory, Entity *root) = 0;
@@ -169,6 +169,12 @@ public:
virtual void setOffscreenSurfaceHelper(OffscreenSurfaceHelper *helper) = 0;
virtual QSurfaceFormat format() = 0;
virtual QOpenGLContext *shareContext() const = 0;
+
+ virtual void lockSurfaceAndRender() = 0;
+ virtual bool releaseRendererAndRequestPromiseToRender() = 0;
+ virtual bool waitForRenderJobs() = 0;
+ virtual bool tryWaitForRenderJobs(int timeout) = 0;
+ virtual void abortRenderJobs() = 0;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(AbstractRenderer::BackendNodeDirtySet)
diff --git a/src/render/backend/render-backend.pri b/src/render/backend/render-backend.pri
index f7f30b5c9..a3df90f8c 100644
--- a/src/render/backend/render-backend.pri
+++ b/src/render/backend/render-backend.pri
@@ -45,7 +45,8 @@ HEADERS += \
$$PWD/visitorutils_p.h \
$$PWD/segmentsvisitor_p.h \
$$PWD/pointsvisitor_p.h \
- $$PWD/renderercache_p.h
+ $$PWD/renderercache_p.h \
+ $$PWD/renderbarrierjob_p.h
SOURCES += \
$$PWD/renderthread.cpp \
@@ -82,7 +83,8 @@ SOURCES += \
$$PWD/resourceaccessor.cpp \
$$PWD/segmentsvisitor.cpp \
$$PWD/commandthread.cpp \
- $$PWD/pointsvisitor.cpp
+ $$PWD/pointsvisitor.cpp \
+ $$PWD/renderbarrierjob.cpp
include($$QT3D_BUILD_ROOT/src/core/qt3dcore-config.pri)
QT_FOR_CONFIG += 3dcore-private
diff --git a/src/render/backend/renderbarrierjob.cpp b/src/render/backend/renderbarrierjob.cpp
new file mode 100644
index 000000000..c60c9f368
--- /dev/null
+++ b/src/render/backend/renderbarrierjob.cpp
@@ -0,0 +1,111 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtCore 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 "renderbarrierjob_p.h"
+
+#include <Qt3DRender/private/renderlogging_p.h>
+
+#include <QtCore/QThread>
+
+QT_BEGIN_NAMESPACE
+
+namespace Qt3DRender {
+namespace Render {
+
+QDebug operator<<(QDebug debug, JobTypes::JobType type)
+{
+ switch (type) {
+ case JobTypes::ReadRenderQueueSizeBarrier:
+ debug << "ReadRenderQueueSize";
+ break;
+ case JobTypes::BeginDrawingBarrier:
+ debug << "BeginDrawing";
+ break;
+ case JobTypes::UpdateGLResourcesBarrier:
+ debug << "UpdateGLResources";
+ break;
+ case JobTypes::PrepareCommandSubmissionBarrier:
+ debug << "PrepareCommandSubmission";
+ break;
+ case JobTypes::EndDrawingBarrier:
+ debug << "EndDrawing";
+ break;
+ default:
+ break;
+ }
+ return debug;
+}
+
+RenderBarrierJob::RenderBarrierJob(JobTypes::JobType type)
+ : QAspectJob()
+ , m_type(type)
+ , m_begin(0)
+ , m_end(0)
+{
+ SET_JOB_RUN_STAT_TYPE(this, type, 0);
+}
+
+void RenderBarrierJob::waitForDependencies()
+{
+ qCDebug(Jobs) << Q_FUNC_INFO << m_type << "waiting for job on" << QThread::currentThread();
+ m_begin.acquire();
+ qCDebug(Jobs) << Q_FUNC_INFO << m_type << "done waiting for job on" << QThread::currentThread();
+ Q_ASSERT(m_begin.available() == 0);
+}
+
+void RenderBarrierJob::allowToProceed()
+{
+ qCDebug(Jobs) << Q_FUNC_INFO << m_type << "releasing job on" << QThread::currentThread();
+ m_end.release();
+}
+
+void RenderBarrierJob::run()
+{
+ qCDebug(Jobs) << Q_FUNC_INFO << m_type << "job releasing render thread on" << QThread::currentThread();
+ m_begin.release();
+ qCDebug(Jobs) << Q_FUNC_INFO << m_type << "job waiting for render thread on" << QThread::currentThread();
+ m_end.acquire();
+ qCDebug(Jobs) << Q_FUNC_INFO << m_type << "job done on" << QThread::currentThread();
+ Q_ASSERT(m_end.available() == 0);
+}
+
+} // namespace Render
+} // namespace Qt3DRender
+
+QT_END_NAMESPACE
diff --git a/src/render/backend/renderbarrierjob_p.h b/src/render/backend/renderbarrierjob_p.h
new file mode 100644
index 000000000..a39b2aa83
--- /dev/null
+++ b/src/render/backend/renderbarrierjob_p.h
@@ -0,0 +1,91 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtCore 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$
+**
+****************************************************************************/
+
+#ifndef QT3DRENDER_RENDER_RENDERBARRIERJOB_P_H
+#define QT3DRENDER_RENDER_RENDERBARRIERJOB_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of other Qt classes. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <Qt3DCore/QAspectJob>
+
+#include <Qt3DRender/private/qt3drender_global_p.h>
+#include <Qt3DRender/private/job_common_p.h>
+
+#include <QtCore/qsemaphore.h>
+
+#include <functional>
+
+QT_BEGIN_NAMESPACE
+
+namespace Qt3DRender {
+namespace Render {
+
+class QT3DRENDERSHARED_PRIVATE_EXPORT RenderBarrierJob : public Qt3DCore::QAspectJob
+{
+public:
+ RenderBarrierJob(JobTypes::JobType type);
+ // Called from render thread
+ void waitForDependencies();
+ // Called from render thread
+ void allowToProceed();
+
+ void run() final;
+private:
+ JobTypes::JobType m_type;
+ QSemaphore m_begin;
+ QSemaphore m_end;
+};
+
+using RenderBarrierJobPtr = QSharedPointer<RenderBarrierJob>;
+
+} // namespace Render
+} // namespace Qt3DRender
+
+QT_END_NAMESPACE
+
+#endif // QT3DRENDER_RENDER_RENDERBARRIERJOB_P_H
diff --git a/src/render/backend/renderer.cpp b/src/render/backend/renderer.cpp
index b5b387cc5..f4440a7f8 100644
--- a/src/render/backend/renderer.cpp
+++ b/src/render/backend/renderer.cpp
@@ -89,11 +89,14 @@
#include <Qt3DRender/private/buffercapture_p.h>
#include <Qt3DRender/private/offscreensurfacehelper_p.h>
#include <Qt3DRender/private/renderviewbuilder_p.h>
+#include <Qt3DRender/private/loadtexturedatajob_p.h>
+#include <Qt3DRender/private/renderbarrierjob_p.h>
#include <Qt3DRender/qcameralens.h>
#include <Qt3DCore/private/qeventfilterservice_p.h>
#include <Qt3DCore/private/qabstractaspectjobmanager_p.h>
#include <Qt3DCore/private/qnodecreatedchangegenerator_p.h>
+#include <private/rendersurfaceselector_p.h>
#if QT_CONFIG(qt3d_profile_jobs)
#include <Qt3DCore/private/aspectcommanddebugger_p.h>
@@ -184,10 +187,16 @@ Renderer::Renderer(QRenderAspect::RenderType type)
, 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_loadTextureJob(Render::LoadTextureDataJobPtr::create())
, m_shaderGathererJob(Render::GenericLambdaJobPtr<std::function<void ()>>::create([this] { lookForDirtyShaders(); }, JobTypes::DirtyShaderGathering))
- , m_syncTextureLoadingJob(Render::GenericLambdaJobPtr<std::function<void ()>>::create([] {}, JobTypes::SyncTextureLoading))
+ , m_syncSkeletonLoadingJob(Render::GenericLambdaJobPtr<std::function<void ()>>::create([] {}, JobTypes::SyncSkeletonLoading))
, m_ownedContext(false)
, m_offscreenHelper(nullptr)
+ , m_readRenderQueueSizeBarrierJob(RenderBarrierJobPtr::create(JobTypes::ReadRenderQueueSizeBarrier))
+ , m_beginDrawingBarrierJob(RenderBarrierJobPtr::create(JobTypes::BeginDrawingBarrier))
+ , m_updateGLResourcesBarrierJob(RenderBarrierJobPtr::create(JobTypes::UpdateGLResourcesBarrier))
+ , m_prepareCommandSubmissionBarrierJob(RenderBarrierJobPtr::create(JobTypes::PrepareCommandSubmissionBarrier))
+ , m_endDrawingBarrierJob(RenderBarrierJobPtr::create(JobTypes::EndDrawingBarrier))
#if QT_CONFIG(qt3d_profile_jobs)
, m_commandExecuter(new Qt3DRender::Debug::CommandExecuter(this))
#endif
@@ -198,6 +207,12 @@ Renderer::Renderer(QRenderAspect::RenderType type)
if (m_renderThread)
m_renderThread->waitForStart();
+ // Render thread barrier interdependencies
+ m_beginDrawingBarrierJob->addDependency(m_readRenderQueueSizeBarrierJob);
+ m_updateGLResourcesBarrierJob->addDependency(m_beginDrawingBarrierJob);
+ m_prepareCommandSubmissionBarrierJob->addDependency(m_updateGLResourcesBarrierJob);
+ m_endDrawingBarrierJob->addDependency(m_prepareCommandSubmissionBarrierJob);
+
// Create jobs to update transforms and bounding volumes
// We can only update bounding volumes once all world transforms are known
m_calculateBoundingVolumeJob->addDependency(m_updateTreeEnabledJob);
@@ -207,17 +222,33 @@ Renderer::Renderer(QRenderAspect::RenderType type)
m_updateShaderDataTransformJob->addDependency(m_worldTransformJob);
m_pickBoundingVolumeJob->addDependency(m_expandBoundingVolumeJob);
- // 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);
+ m_updateSkinningPaletteJob->addDependency(m_syncSkeletonLoadingJob);
// All world stuff depends on the RenderEntity's localBoundingVolume
m_updateLevelOfDetailJob->addDependency(m_updateMeshTriangleListJob);
m_pickBoundingVolumeJob->addDependency(m_updateMeshTriangleListJob);
+ // Independent job dependencies
+ m_beginDrawingBarrierJob->addDependency(m_vaoGathererJob);
+
+ // Make sure we do not try to filter techniques before we have proper context
+ m_filterCompatibleTechniqueJob->addDependency(m_beginDrawingBarrierJob);
+
+ // Make sure we have gathered dirty shaders, textures and buffers before calling updateGLResources
+ m_updateGLResourcesBarrierJob->addDependency(m_bufferGathererJob);
+ m_updateGLResourcesBarrierJob->addDependency(m_shaderGathererJob);
+ m_updateGLResourcesBarrierJob->addDependency(m_textureGathererJob);
+
+ // Make sure we load textures after gathering
+ m_loadTextureJob->addDependency(m_updateGLResourcesBarrierJob);
+ m_loadTextureJob->addDependency(m_textureGathererJob);
+
+ // Make sure we prepare command submission after textures are loaded
+ m_prepareCommandSubmissionBarrierJob->addDependency(m_loadTextureJob);
+
+ // Make sure we have found compatible techniques before gathering shaders
+ m_updateGLResourcesBarrierJob->addDependency(m_filterCompatibleTechniqueJob);
m_shaderGathererJob->addDependency(m_filterCompatibleTechniqueJob);
m_filterCompatibleTechniqueJob->setRenderer(this);
@@ -285,6 +316,7 @@ void Renderer::setNodeManagers(NodeManagers *managers)
m_updateSkinningPaletteJob->setManagers(m_nodesManager);
m_updateMeshTriangleListJob->setManagers(m_nodesManager);
m_filterCompatibleTechniqueJob->setManager(m_nodesManager->techniqueManager());
+ m_loadTextureJob->setNodeManagers(m_nodesManager);
}
void Renderer::setServices(QServiceLocator *services)
@@ -396,12 +428,17 @@ void Renderer::shutdown()
m_renderQueue->reset();
if (!m_renderThread) {
+ // Make sure the aspect thread is not left behind
+ m_willRenderPromise.store(0);
+ m_rendererReadySemaphore.release();
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_willRenderPromise.store(0);
+ m_rendererReadySemaphore.release(1);
+ m_renderJobsReadySemaphore.release(1);
m_renderThread->wait();
}
}
@@ -516,7 +553,7 @@ void Renderer::setSceneRoot(QBackendNodeFactory *factory, Entity *sgRoot)
m_updateTreeEnabledJob->setRoot(m_renderSceneRoot);
// Set all flags to dirty
- m_dirtyBits.marked |= AbstractRenderer::AllDirty;
+ m_dirtyBits |= AbstractRenderer::AllDirty;
}
void Renderer::registerEventFilter(QEventFilterService *service)
@@ -535,6 +572,118 @@ RenderSettings *Renderer::settings() const
return m_settings;
}
+bool Renderer::createSurfaceLockAndMakeCurrent()
+{
+ FrameGraphVisitor visitor(nodeManagers()->frameGraphManager());
+ const QVector<FrameGraphNode *> fgLeaves = visitor.traverse(frameGraphRoot());
+ for (FrameGraphNode *leafNode : fgLeaves) {
+ FrameGraphNode *node = leafNode;
+ while (node) {
+ if (!node->isEnabled() || node->nodeType() != FrameGraphNode::Surface) {
+ node = node->parent();
+ continue;
+ }
+ const auto *surfaceSelector = static_cast<const RenderSurfaceSelector *>(node);
+ auto locker = QSharedPointer<SurfaceLocker>::create(surfaceSelector->surface());
+ if (!locker->isSurfaceValid())
+ return false;
+
+ m_surfaceLockers.append(locker);
+ node = node->parent();
+
+ if (!m_graphicsContext->makeCurrent(surfaceSelector->surface()))
+ return false;
+
+ // TODO: The same surface may appear twice here, so we return early to avoid a deadlock.
+ // We may want to fix this at some point and make sure we properly lock multiple
+ // surfaces.
+ return true;
+ }
+ }
+ // No surface found
+ return false;
+}
+
+// Called by render() or Scene3D on render thread
+void Renderer::lockSurfaceAndRender()
+{
+ // Make sure we can lock the surface before rendering. This cannot be done in a job,
+ // because we would otherwise risk rendering into a surface that could get destroyed.
+ // We could lock/unlock while rendering, but that requires a fair amount of extra
+ // logic to be able to recover gracefully from a partially completed render.
+ if (!createSurfaceLockAndMakeCurrent()) {
+ m_surfaceLockers.clear();
+ abortRenderJobs();
+ return;
+ }
+ // Let the aspect thread know that we promise to render
+ m_willRenderPromise.store(1);
+ // Release the aspect thread
+ m_rendererReadySemaphore.release();
+ doRender();
+ // Release the surface lockers
+ m_surfaceLockers.clear();
+}
+
+/*!
+ * \internal
+ * Called by RenderAspect on aspect thread, returns false if
+ * the render thread has not promised to render the next frame.
+ *
+ * If this function returns false, the aspect thread
+ * bails out and does not issue the renderBinJobs.
+ *
+ * This keeps us from ending up in a deadlock where the barriers are
+ * waiting for doRender to release them.
+*/
+bool Renderer::releaseRendererAndRequestPromiseToRender()
+{
+ // Release the render thread, which could be waiting in waitForRenderJobs
+ // or is about to call tryWaitForRenderJobs
+ m_renderJobsReadySemaphore.release();
+ // Wait for the render thread to allow us to continue
+ m_rendererReadySemaphore.acquire();
+ // Check if the render thread has promised to call doRender
+ return m_willRenderPromise.testAndSetOrdered(1, 0);
+}
+
+/*!
+ * \internal
+ * Called by render(), returns false if we are no longer running
+*/
+bool Renderer::waitForRenderJobs()
+{
+ // Wait for the aspect thread to arrive at releaseRendererAndRequestPromiseToRender,
+ // which is right before it will create the renderBinJobs
+ m_renderJobsReadySemaphore.acquire();
+ // Check if we have not shut down in the meantime and rather want to bail out
+ return m_running.load();
+}
+
+/*!
+ * \internal
+ * Called by Scene3D, returns true if we could acquire
+*/
+bool Renderer::tryWaitForRenderJobs(int timeout)
+{
+ // See if the aspect thread has arrived at releaseRendererAndRequestPromiseToRender.
+ // If not, Scene3D needs to continue to avoid locking up the render thread.
+ return m_renderJobsReadySemaphore.tryAcquire(1, timeout) && m_running.load();
+}
+
+/*!
+ * \internal
+ * Called by Scene3D, in shutdown
+*/
+void Renderer::abortRenderJobs()
+{
+ // Make sure the aspect does not start render jobs
+ m_willRenderPromise.store(0);
+ // Release the aspect so it gets out of the job creation
+ m_rendererReadySemaphore.release();
+}
+
+
void Renderer::render()
{
// Traversing the framegraph tree from root to lead node
@@ -551,168 +700,102 @@ void Renderer::render()
// One scene description
// One framegraph description
- while (m_running.load() > 0) {
- doRender();
+ while (waitForRenderJobs()) {
+ lockSurfaceAndRender();
// TO DO: Restore windows exposed detection
// Probably needs to happens some place else though
}
}
-void Renderer::doRender(bool scene3dBlocking)
+/*!
+ * \internal
+ * The doRender function is called either by Renderer::render or
+ * QRenderAspect::tryRenderSynchronous (Scene3D).
+ * Jobs that it depends on are guaranteed to have been spawned by the aspect thread.
+ * It will run once for each set of renderBinJobs.
+ * Throughout the function call, the jobs are kept in sync using barriers.
+ * This guarantees the state of the GL context for the jobs.
+ * It is therefore important that any job that needs this function to be in at a specific point
+ * in the executio depends on the right barrier.
+*/
+void Renderer::doRender()
{
- 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();
+ m_readRenderQueueSizeBarrierJob->waitForDependencies();
+ const bool queueIsComplete = m_renderQueue->isFrameQueueComplete();
const bool queueIsEmpty = m_renderQueue->targetRenderViewCount() == 0;
+ Q_ASSERT(queueIsComplete && !queueIsEmpty);
+ m_readRenderQueueSizeBarrierJob->allowToProceed();
- // 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();
-
+ 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());
+ // 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_graphicsContext->setCurrentStateSet(nullptr);
- beganDrawing = m_graphicsContext->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;
+ // Reset state for each draw if we don't have complete control of the context
+ if (!m_ownedContext)
+ m_graphicsContext->setCurrentStateSet(nullptr);
+ // Initialize context
+ m_beginDrawingBarrierJob->waitForDependencies();
+ m_graphicsContext->beginDrawing();
+ m_beginDrawingBarrierJob->allowToProceed();
+ // Execute commands for buffer uploads, texture updates, shader loading first
+ m_updateGLResourcesBarrierJob->waitForDependencies();
+ updateGLResources();
+ m_updateGLResourcesBarrierJob->allowToProceed();
+ // Update VAO and copy data into commands to allow concurrent submission
+ m_prepareCommandSubmissionBarrierJob->waitForDependencies();
+ prepareCommandsSubmission(renderViews);
+ m_prepareCommandSubmissionBarrierJob->allowToProceed();
+
+ // Proceed to next frame and start preparing frame n + 1
+ m_renderQueue->reset();
+ m_vsyncFrameAdvanceService->proceedToNextFrame();
#if QT_CONFIG(qt3d_profile_jobs)
- if (preprocessingComplete) {
- submissionStatsPart2.startTime = QThreadPooler::m_jobsStatTimer.nsecsElapsed();
- submissionStatsPart1.endTime = submissionStatsPart2.startTime;
- }
+ 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();
- }
- }
+ // 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
+ Renderer::ViewSubmissionResultData 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);
+ // 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);
+ // 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();
- }
+ // 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_graphicsContext->endDrawing(submissionData.lastBoundFBOId == m_graphicsContext->defaultFBO() && surfaceLock.isSurfaceValid());
- }
+ m_endDrawingBarrierJob->waitForDependencies();
+ m_graphicsContext->endDrawing(submissionData.lastBoundFBOId == m_graphicsContext->defaultFBO());
+ m_endDrawingBarrierJob->allowToProceed();
}
// Called by RenderViewJobs
@@ -727,48 +810,8 @@ void Renderer::enqueueRenderView(Render::RenderView *renderView, int submitOrder
// 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);
+ 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
@@ -879,6 +922,7 @@ void Renderer::prepareCommandsSubmission(const QVector<RenderView *> &renderView
const QVector<Qt3DCore::QNodeId> attributeIds = rGeometry->attributes();
for (Qt3DCore::QNodeId attributeId : attributeIds) {
Attribute *attribute = m_nodesManager->attributeManager()->lookupResource(attributeId);
+ Q_ASSERT(attribute);
switch (attribute->attributeType()) {
case QAttribute::IndexAttribute:
indexAttribute = attribute;
@@ -911,6 +955,7 @@ void Renderer::prepareCommandsSubmission(const QVector<RenderView *> &renderView
if (command->m_drawIndirect) {
command->m_indirectAttributeByteOffset = indirectAttribute->byteOffset();
command->m_indirectDrawBuffer = m_nodesManager->bufferManager()->lookupHandle(indirectAttribute->bufferId());
+ Q_ASSERT(!command->m_indirectDrawBuffer.isNull());
} else {
// Use the count specified by the GeometryRender
// If not specify use the indexAttribute count if present
@@ -1026,6 +1071,7 @@ void Renderer::lookForDirtyShaders()
RenderPass *renderPass = m_nodesManager->renderPassManager()->lookupResource(passId);
HShader shaderHandle = m_nodesManager->shaderManager()->lookupHandle(renderPass->shaderProgram());
Shader *shader = m_nodesManager->shaderManager()->data(shaderHandle);
+ Q_ASSERT(renderPass && shader);
ShaderBuilder *shaderBuilder = nullptr;
for (const HShaderBuilder &builderHandle : activeBuilders) {
@@ -1092,6 +1138,7 @@ void Renderer::updateGLResources()
const QVector<HBuffer> dirtyBufferHandles = std::move(m_dirtyBuffers);
for (const HBuffer &handle: dirtyBufferHandles) {
Buffer *buffer = m_nodesManager->bufferManager()->data(handle);
+ Q_ASSERT(buffer);
// Forces creation if it doesn't exit
// Also note the binding point doesn't really matter here, we just upload data
if (!m_graphicsContext->hasGLBufferForBuffer(buffer))
@@ -1108,6 +1155,7 @@ void Renderer::updateGLResources()
ShaderManager *shaderManager = m_nodesManager->shaderManager();
for (const HShader &handle: dirtyShaderHandles) {
Shader *shader = shaderManager->data(handle);
+ Q_ASSERT(shader);
// Compile shader
m_graphicsContext->loadShader(shader, shaderManager);
}
@@ -1118,6 +1166,7 @@ void Renderer::updateGLResources()
const QVector<HTexture> activeTextureHandles = std::move(m_dirtyTextures);
for (const HTexture &handle: activeTextureHandles) {
Texture *texture = m_nodesManager->textureManager()->data(handle);
+ Q_ASSERT(texture);
// Upload/Update texture
updateTexture(texture);
}
@@ -1225,6 +1274,7 @@ void Renderer::updateTexture(Texture *texture)
// Render Thread
void Renderer::cleanupTexture(const Texture *texture)
{
+ Q_ASSERT(texture);
GLTextureManager *glTextureManager = m_nodesManager->glTextureManager();
GLTexture *glTexture = glTextureManager->lookupResource(texture->peerId());
@@ -1277,7 +1327,6 @@ Renderer::ViewSubmissionResultData Renderer::submitRenderViews(const QVector<Ren
// 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
@@ -1285,7 +1334,7 @@ Renderer::ViewSubmissionResultData Renderer::submitRenderViews(const QVector<Ren
// 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()) {
+ if (!surface) {
m_lastFrameCorrect.store(0);
continue;
}
@@ -1302,12 +1351,14 @@ Renderer::ViewSubmissionResultData Renderer::submitRenderViews(const QVector<Ren
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_graphicsContext->beginDrawing(surface)) {
+ if (!m_graphicsContext->makeCurrent(surface)) {
qWarning() << "Failed to make OpenGL context current on surface";
m_lastFrameCorrect.store(0);
continue;
}
+ m_graphicsContext->beginDrawing();
+
previousSurface = surface;
lastBoundFBOId = m_graphicsContext->boundFrameBufferObject();
}
@@ -1446,39 +1497,35 @@ Renderer::ViewSubmissionResultData Renderer::submitRenderViews(const QVector<Ren
void Renderer::markDirty(BackendNodeDirtySet changes, BackendNode *node)
{
Q_UNUSED(node);
- m_dirtyBits.marked |= changes;
+ m_dirtyBits|= changes;
}
Renderer::BackendNodeDirtySet Renderer::dirtyBits()
{
- return m_dirtyBits.marked;
+ return m_dirtyBits;
}
#if defined(QT_BUILD_INTERNAL)
void Renderer::clearDirtyBits(BackendNodeDirtySet changes)
{
- m_dirtyBits.remaining &= ~changes;
- m_dirtyBits.marked &= ~changes;
+ m_dirtyBits &= ~changes;
}
#endif
bool Renderer::shouldRender()
{
+ if (!m_settings)
+ return false;
// 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_dirtyBits != 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);
+ m_vsyncFrameAdvanceService->proceedToNextFrame();
}
// Waits to be told to create jobs for the next frame
@@ -1489,30 +1536,44 @@ 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);
+ // Render thread barrier jobs
+ renderBinJobs.append(m_readRenderQueueSizeBarrierJob);
+ renderBinJobs.append(m_beginDrawingBarrierJob);
+ renderBinJobs.append(m_updateGLResourcesBarrierJob);
+ renderBinJobs.append(m_prepareCommandSubmissionBarrierJob);
+ renderBinJobs.append(m_endDrawingBarrierJob);
+ // Jobs independent of dirty bits
+ 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.push_back(m_vaoGathererJob);
+
+ // Independent job properties
+ m_updateSkinningPaletteJob->setDirtyJoints(m_nodesManager->jointManager()->dirtyJoints());
m_updateLevelOfDetailJob->setFrameGraphRoot(frameGraphRoot());
- const BackendNodeDirtySet dirtyBitsForFrame = m_dirtyBits.marked | m_dirtyBits.remaining;
- m_dirtyBits.marked = 0;
- m_dirtyBits.remaining = 0;
- BackendNodeDirtySet notCleared = 0;
+ // Buffer jobs, depends on dirty buffers
+ const QVector<QAspectJobPtr> bufferJobs = createRenderBufferJobs();
+ for (const QAspectJobPtr &bufferJob : bufferJobs) {
+ m_calculateBoundingVolumeJob->addDependency(bufferJob);
+ m_beginDrawingBarrierJob->addDependency(bufferJob);
+ }
+ renderBinJobs.append(bufferJobs);
+
+ // Jobs dependent on dirty bits
+ const BackendNodeDirtySet dirtyBitsForFrame = m_dirtyBits;
+ m_dirtyBits = 0;
- // Add jobs
const bool entitiesEnabledDirty = dirtyBitsForFrame & AbstractRenderer::EntityEnabledDirty;
- if (entitiesEnabledDirty) {
+ if (entitiesEnabledDirty)
renderBinJobs.push_back(m_updateTreeEnabledJob);
- m_calculateBoundingVolumeJob->addDependency(m_updateTreeEnabledJob);
- }
if (dirtyBitsForFrame & AbstractRenderer::TransformDirty) {
renderBinJobs.push_back(m_worldTransformJob);
@@ -1530,76 +1591,53 @@ QVector<Qt3DCore::QAspectJobPtr> Renderer::renderBinJobs()
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_loadTextureJob);
renderBinJobs.push_back(m_textureGathererJob);
}
+ if (dirtyBitsForFrame & AbstractRenderer::SkeletonDataDirty)
+ renderBinJobs.push_back(m_syncSkeletonLoadingJob);
+
+ if (dirtyBitsForFrame & AbstractRenderer::TechniquesDirty )
+ renderBinJobs.push_back(m_filterCompatibleTechniqueJob);
+
+ if (dirtyBitsForFrame & AbstractRenderer::ShadersDirty)
+ renderBinJobs.push_back(m_shaderGathererJob);
// 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;
- 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.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;
+ // 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);
}
- if (isRunning() && m_graphicsContext->isInitialized()) {
- if (dirtyBitsForFrame & AbstractRenderer::TechniquesDirty )
- renderBinJobs.push_back(m_filterCompatibleTechniqueJob);
- if (dirtyBitsForFrame & AbstractRenderer::ShadersDirty)
- renderBinJobs.push_back(m_shaderGathererJob);
- } else {
- notCleared |= AbstractRenderer::TechniquesDirty;
- notCleared |= AbstractRenderer::ShadersDirty;
+ const int fgBranchCount = fgLeaves.size();
+ for (int i = 0; i < fgBranchCount; ++i) {
+ RenderViewBuilder builder(fgLeaves.at(i), i, this);
+ builder.setLayerCacheNeedsToBeRebuilt(layersCacheNeedsToBeRebuilt);
+ builder.prepareJobs();
+ renderBinJobs.append(builder.buildJobHierachy());
}
- m_dirtyBits.remaining = dirtyBitsForFrame & notCleared;
+ // Set target number of RenderViews
+ m_renderQueue->setTargetRenderViewCount(fgBranchCount);
return renderBinJobs;
}
@@ -1618,9 +1656,9 @@ QAspectJobPtr Renderer::pickBoundingVolumeJob()
return m_pickBoundingVolumeJob;
}
-QAspectJobPtr Renderer::syncTextureLoadingJob()
+QAspectJobPtr Renderer::syncSkeletonLoadingJob()
{
- return m_syncTextureLoadingJob;
+ return m_syncSkeletonLoadingJob;
}
QAspectJobPtr Renderer::expandBoundingVolumeJob()
@@ -1725,7 +1763,7 @@ void Renderer::performCompute(const RenderView *, RenderCommand *command)
command->m_workGroups[2]);
}
// HACK: Reset the compute flag to dirty
- m_dirtyBits.marked |= AbstractRenderer::ComputeDirty;
+ m_dirtyBits |= AbstractRenderer::ComputeDirty;
#if defined(QT3D_RENDER_ASPECT_OPENGL_DEBUG)
int err = m_graphicsContext->openGLContext()->functions()->glGetError();
@@ -1861,11 +1899,13 @@ bool Renderer::updateVAOWithAttributes(Geometry *geometry,
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);
+ Q_ASSERT(attribute);
if (attribute == nullptr)
return false;
Buffer *buffer = m_nodesManager->bufferManager()->lookupResource(attribute->bufferId());
+ Q_ASSERT(buffer);
// Buffer update was already performed at this point
// Just make sure the attribute reference a valid buffer
@@ -1918,6 +1958,7 @@ bool Renderer::requiresVAOAttributeUpdate(Geometry *geometry,
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);
+ Q_ASSERT(attribute);
if (attribute == nullptr)
continue;
diff --git a/src/render/backend/renderer_p.h b/src/render/backend/renderer_p.h
index 987576059..2654b3b61 100644
--- a/src/render/backend/renderer_p.h
+++ b/src/render/backend/renderer_p.h
@@ -143,6 +143,10 @@ class NodeManagers;
class UpdateLevelOfDetailJob;
typedef QSharedPointer<UpdateLevelOfDetailJob> UpdateLevelOfDetailJobPtr;
+class LoadTextureDataJob;
+using LoadTextureDataJobPtr = QSharedPointer<LoadTextureDataJob>;
+class RenderBarrierJob;
+using RenderBarrierJobPtr = QSharedPointer<RenderBarrierJob>;
using SynchronizerJobPtr = GenericLambdaJobPtr<std::function<void()>>;
@@ -170,7 +174,7 @@ public:
void releaseGraphicsResources() Q_DECL_OVERRIDE;
void render() Q_DECL_OVERRIDE;
- void doRender(bool scene3dBlocking = false) Q_DECL_OVERRIDE;
+ void doRender() Q_DECL_OVERRIDE;
void cleanGraphicsResources() Q_DECL_OVERRIDE;
bool isRunning() const Q_DECL_OVERRIDE { return m_running.load(); }
@@ -187,16 +191,28 @@ public:
void clearDirtyBits(BackendNodeDirtySet changes) Q_DECL_OVERRIDE;
#endif
+ void lockSurfaceAndRender() override;
+ bool releaseRendererAndRequestPromiseToRender() override;
+ bool waitForRenderJobs() override;
+ bool tryWaitForRenderJobs(int timeout) override;
+ void abortRenderJobs() override;
+
bool shouldRender() Q_DECL_OVERRIDE;
void skipNextFrame() Q_DECL_OVERRIDE;
QVector<Qt3DCore::QAspectJobPtr> renderBinJobs() Q_DECL_OVERRIDE;
Qt3DCore::QAspectJobPtr pickBoundingVolumeJob() Q_DECL_OVERRIDE;
- Qt3DCore::QAspectJobPtr syncTextureLoadingJob() Q_DECL_OVERRIDE;
+ Qt3DCore::QAspectJobPtr syncSkeletonLoadingJob() Q_DECL_OVERRIDE;
Qt3DCore::QAspectJobPtr expandBoundingVolumeJob() Q_DECL_OVERRIDE;
QVector<Qt3DCore::QAspectJobPtr> createRenderBufferJobs() const;
+ inline RenderBarrierJobPtr readRenderQueueSizeBarrierJob() const { return m_readRenderQueueSizeBarrierJob; }
+ inline RenderBarrierJobPtr beginDrawingBarrierJob() const { return m_beginDrawingBarrierJob; }
+ inline RenderBarrierJobPtr updateGLResourcesBarrierJob() const { return m_updateGLResourcesBarrierJob; }
+ inline RenderBarrierJobPtr prepareCommandSubmissionBarrierJob() const { return m_prepareCommandSubmissionBarrierJob; }
+ inline RenderBarrierJobPtr endDrawingBarrierJob() const { return m_endDrawingBarrierJob; }
+
inline FrameCleanupJobPtr frameCleanupJob() const { return m_cleanupJob; }
inline UpdateShaderDataTransformJobPtr updateShaderDataTransformJob() const { return m_updateShaderDataTransformJob; }
inline CalculateBoundingVolumeJobPtr calculateBoundingVolumeJob() const { return m_calculateBoundingVolumeJob; }
@@ -206,8 +222,9 @@ public:
inline UpdateLevelOfDetailJobPtr updateLevelOfDetailJob() const { return m_updateLevelOfDetailJob; }
inline UpdateMeshTriangleListJobPtr updateMeshTriangleListJob() const { return m_updateMeshTriangleListJob; }
inline FilterCompatibleTechniqueJobPtr filterCompatibleTechniqueJob() const { return m_filterCompatibleTechniqueJob; }
- inline SynchronizerJobPtr textureLoadSyncJob() const { return m_syncTextureLoadingJob; }
+ inline SynchronizerJobPtr textureLoadSyncJob() const { return m_syncSkeletonLoadingJob; }
inline UpdateSkinningPaletteJobPtr updateSkinningPaletteJob() const { return m_updateSkinningPaletteJob; }
+ inline LoadTextureDataJobPtr loadTextureJob() const {return m_loadTextureJob; }
Qt3DCore::QAbstractFrameAdvanceService *frameAdvanceService() const Q_DECL_OVERRIDE;
@@ -250,7 +267,6 @@ public:
const QVector<Qt3DCore::QNodeId> takePendingRenderCaptureSendRequests();
void enqueueRenderView(RenderView *renderView, int submitOrder);
- bool isReadyToSubmit();
QVariant executeCommand(const QStringList &args) Q_DECL_OVERRIDE;
void setOffscreenSurfaceHelper(OffscreenSurfaceHelper *helper) Q_DECL_OVERRIDE;
@@ -277,7 +293,7 @@ public:
private:
#endif
- bool canRender() const;
+ bool hasBeenAskedToTerminate() const;
Qt3DCore::QServiceLocator *m_services;
NodeManagers *m_nodesManager;
@@ -299,7 +315,6 @@ private:
QScopedPointer<RenderThread> m_renderThread;
QScopedPointer<VSyncFrameAdvanceService> m_vsyncFrameAdvanceService;
- QSemaphore m_submitRenderViewsSemaphore;
QSemaphore m_waitForInitializationToBeCompleted;
QAtomicInt m_running;
@@ -310,11 +325,7 @@ private:
QVector<Geometry *> m_dirtyGeometry;
QAtomicInt m_exposed;
- struct DirtyBits {
- BackendNodeDirtySet marked = 0; // marked dirty since last job build
- BackendNodeDirtySet remaining = 0; // remaining dirty after jobs have finished
- };
- DirtyBits m_dirtyBits;
+ BackendNodeDirtySet m_dirtyBits;
QAtomicInt m_lastFrameCorrect;
QOpenGLContext *m_glContext;
@@ -350,15 +361,18 @@ private:
GenericLambdaJobPtr<std::function<void ()>> m_bufferGathererJob;
GenericLambdaJobPtr<std::function<void ()>> m_vaoGathererJob;
GenericLambdaJobPtr<std::function<void ()>> m_textureGathererJob;
+ LoadTextureDataJobPtr m_loadTextureJob;
+
GenericLambdaJobPtr<std::function<void ()>> m_shaderGathererJob;
- SynchronizerJobPtr m_syncTextureLoadingJob;
+ SynchronizerJobPtr m_syncSkeletonLoadingJob;
void lookForAbandonedVaos();
void lookForDirtyBuffers();
void lookForDownloadableBuffers();
void lookForDirtyTextures();
void lookForDirtyShaders();
+ bool createSurfaceLockAndMakeCurrent();
QMutex m_abandonedVaosMutex;
QVector<HVao> m_abandonedVaos;
@@ -380,6 +394,18 @@ private:
QMetaObject::Connection m_contextConnection;
RendererCache m_cache;
+
+ RenderBarrierJobPtr m_readRenderQueueSizeBarrierJob;
+ RenderBarrierJobPtr m_beginDrawingBarrierJob;
+ RenderBarrierJobPtr m_updateGLResourcesBarrierJob;
+ RenderBarrierJobPtr m_prepareCommandSubmissionBarrierJob;
+ RenderBarrierJobPtr m_endDrawingBarrierJob;
+
+ QAtomicInt m_willRenderPromise;
+ QSemaphore m_rendererReadySemaphore;
+ QSemaphore m_renderJobsReadySemaphore;
+
+ QVector<QSharedPointer<SurfaceLocker>> m_surfaceLockers;
};
} // namespace Render
diff --git a/src/render/backend/renderqueue.cpp b/src/render/backend/renderqueue.cpp
index 2fa1cb7d2..db75558a4 100644
--- a/src/render/backend/renderqueue.cpp
+++ b/src/render/backend/renderqueue.cpp
@@ -90,7 +90,6 @@ bool RenderQueue::queueRenderView(RenderView *renderView, uint submissionOrderIn
Q_ASSERT(!m_noRender);
m_currentWorkQueue[submissionOrderIndex] = renderView;
++m_currentRenderViewCount;
- Q_ASSERT(m_currentRenderViewCount <= m_targetRenderViewCount);
return isFrameQueueComplete();
}
diff --git a/src/render/backend/renderview.cpp b/src/render/backend/renderview.cpp
index dd5968420..995d63f3c 100644
--- a/src/render/backend/renderview.cpp
+++ b/src/render/backend/renderview.cpp
@@ -629,6 +629,7 @@ QVector<RenderCommand *> RenderView::buildDrawRenderCommands(const QVector<Entit
const QVector<Qt3DCore::QNodeId> attributeIds = geometry->attributes();
for (Qt3DCore::QNodeId attributeId : attributeIds) {
Attribute *attribute = m_manager->attributeManager()->lookupResource(attributeId);
+ Q_ASSERT(attribute != nullptr);
if (attribute->attributeType() == QAttribute::IndexAttribute)
indexAttribute = attribute;
else if (command->m_attributes.contains(attribute->nameId()))
diff --git a/src/render/backend/renderviewbuilder.cpp b/src/render/backend/renderviewbuilder.cpp
index d4e6bfdde..ce5a63565 100644
--- a/src/render/backend/renderviewbuilder.cpp
+++ b/src/render/backend/renderviewbuilder.cpp
@@ -39,6 +39,8 @@
#include "renderviewbuilder_p.h"
+#include <Qt3DRender/private/renderbarrierjob_p.h>
+
#include <QThread>
QT_BEGIN_NAMESPACE
@@ -81,9 +83,6 @@ public:
// Sort the commands
rv->sort();
-
- // Enqueue our fully populated RenderView with the RenderThread
- m_renderer->enqueueRenderView(rv, m_renderViewJob->submitOrderIndex());
}
private:
@@ -125,13 +124,15 @@ public:
const FilterLayerEntityJobPtr &filterEntityByLayerJob,
const FilterProximityDistanceJobPtr &filterProximityJob,
const QVector<MaterialParameterGathererJobPtr> &materialGathererJobs,
- const QVector<RenderViewBuilderJobPtr> &renderViewBuilderJobs)
+ const QVector<RenderViewBuilderJobPtr> &renderViewBuilderJobs,
+ Renderer *renderer)
: m_renderViewJob(renderViewJob)
, m_frustumCullingJob(frustumCullingJob)
, m_filterEntityByLayerJob(filterEntityByLayerJob)
, m_filterProximityJob(filterProximityJob)
, m_materialGathererJobs(materialGathererJobs)
, m_renderViewBuilderJobs(renderViewBuilderJobs)
+ , m_renderer(renderer)
{}
void operator()()
@@ -157,6 +158,9 @@ public:
// Set whether frustum culling is enabled or not
m_frustumCullingJob->setActive(rv->frustumCulling());
+
+ // Enqueue RenderView with the renderer
+ m_renderer->enqueueRenderView(rv, m_renderViewJob->submitOrderIndex());
}
private:
@@ -166,6 +170,7 @@ private:
FilterProximityDistanceJobPtr m_filterProximityJob;
QVector<MaterialParameterGathererJobPtr> m_materialGathererJobs;
QVector<RenderViewBuilderJobPtr> m_renderViewBuilderJobs;
+ Renderer *m_renderer;
};
class SyncRenderCommandBuilding
@@ -482,7 +487,8 @@ void RenderViewBuilder::prepareJobs()
m_filterEntityByLayerJob,
m_filterProximityJob,
m_materialGathererJobs,
- m_renderViewBuilderJobs),
+ m_renderViewBuilderJobs,
+ m_renderer),
JobTypes::SyncRenderViewInitialization);
}
@@ -508,11 +514,13 @@ QVector<Qt3DCore::QAspectJobPtr> RenderViewBuilder::buildJobHierachy() const
m_setClearDrawBufferIndexJob->addDependency(m_syncRenderViewInitializationJob);
+ m_renderer->readRenderQueueSizeBarrierJob()->addDependency(m_syncRenderViewInitializationJob);
m_syncRenderViewInitializationJob->addDependency(m_renderViewJob);
m_filterProximityJob->addDependency(m_renderer->expandBoundingVolumeJob());
m_filterProximityJob->addDependency(m_syncRenderViewInitializationJob);
+ m_syncRenderCommandBuildingJob->addDependency(m_renderer->updateGLResourcesBarrierJob());
m_syncRenderCommandBuildingJob->addDependency(m_syncRenderViewInitializationJob);
for (const auto &materialGatherer : qAsConst(m_materialGathererJobs)) {
materialGatherer->addDependency(m_syncRenderViewInitializationJob);
@@ -526,10 +534,15 @@ QVector<Qt3DCore::QAspectJobPtr> RenderViewBuilder::buildJobHierachy() const
m_syncRenderCommandBuildingJob->addDependency(m_frustumCullingJob);
for (const auto &renderViewCommandBuilder : qAsConst(m_renderViewBuilderJobs)) {
+ renderViewCommandBuilder->addDependency(m_renderer->updateSkinningPaletteJob());
+ renderViewCommandBuilder->addDependency(m_renderer->updateWorldBoundingVolumeJob());
+ renderViewCommandBuilder->addDependency(m_renderer->updateShaderDataTransformJob());
renderViewCommandBuilder->addDependency(m_syncRenderCommandBuildingJob);
+
m_syncRenderViewCommandBuildersJob->addDependency(renderViewCommandBuilder);
}
+ m_renderer->prepareCommandSubmissionBarrierJob()->addDependency(m_syncRenderViewCommandBuildersJob);
m_renderer->frameCleanupJob()->addDependency(m_syncRenderViewCommandBuildersJob);
m_renderer->frameCleanupJob()->addDependency(m_setClearDrawBufferIndexJob);
diff --git a/src/render/frontend/qrenderaspect.cpp b/src/render/frontend/qrenderaspect.cpp
index 2847bf29b..360aef100 100644
--- a/src/render/frontend/qrenderaspect.cpp
+++ b/src/render/frontend/qrenderaspect.cpp
@@ -150,6 +150,8 @@
#include <private/qrenderpluginfactory_p.h>
#include <private/qrenderplugin_p.h>
+#include <private/framegraphvisitor_p.h>
+#include <private/platformsurfacefilter_p.h>
#include <Qt3DCore/qentity.h>
#include <Qt3DCore/qtransform.h>
@@ -185,7 +187,12 @@ QRenderAspectPrivate::QRenderAspectPrivate(QRenderAspect::RenderType type)
, m_initialized(false)
, m_renderType(type)
, m_offscreenHelper(nullptr)
+ , m_blockingRendermode(false)
{
+ // The blocking mode can be enabled to make sure we render even while
+ // waiting for geometries to load. This is necessary if we want
+ // to call doRender for every QtQuick frame when using Scene3D frame.
+ m_blockingRendermode = !qgetenv("SCENE3D_BLOCKING_RENDERMODE").isEmpty();
m_instances.append(this);
loadSceneParsers();
}
@@ -367,6 +374,11 @@ void QRenderAspectPrivate::registerBackendType(const QMetaObject &obj,
q->registerBackendType(obj, functor);
}
+void QRenderAspectPrivate::abortRenderJobs()
+{
+ m_renderer->abortRenderJobs();
+}
+
/*!
* The constructor creates a new QRenderAspect::QRenderAspect instance with the
* specified \a parent.
@@ -405,9 +417,17 @@ void QRenderAspectPrivate::renderInitialize(QOpenGLContext *context)
}
/*! \internal */
-void QRenderAspectPrivate::renderSynchronous(bool blocking)
+void QRenderAspectPrivate::tryRenderSynchronous()
{
- m_renderer->doRender(blocking);
+ // If the render aspect is slow for some reason and does not build jobs
+ // immediately, we might want to wait for it to make sure we render
+ // one Qt3D frame for each QtQuick frame. If blocking mode is enabled,
+ // we will wait a short time.
+ // TODO By allowing the aspect thread to skip a couple of frames that
+ // are not rendered without being in sync with the QtQuick scene graph,
+ // we can make this into a blocking call and get rid of the timeout.
+ if (m_renderer->tryWaitForRenderJobs(m_blockingRendermode ? 20 : 0))
+ m_renderer->lockSurfaceAndRender();
}
/*!
@@ -443,36 +463,16 @@ QVector<Qt3DCore::QAspectJobPtr> QRenderAspect::jobsToExecute(qint64 time)
// asked for jobs to execute (this function). If that is the case, the RenderSettings will
// be null and we should not generate any jobs.
if (d->m_renderer->isRunning() && d->m_renderer->settings()) {
-
Render::NodeManagers *manager = d->m_renderer->nodeManagers();
- QAspectJobPtr textureLoadingSync = d->m_renderer->syncTextureLoadingJob();
- textureLoadingSync->removeDependency(QWeakPointer<QAspectJob>());
-
- // Launch texture generator jobs
- const QVector<QTextureImageDataGeneratorPtr> pendingImgGen = manager->textureImageDataManager()->pendingGenerators();
- for (const QTextureImageDataGeneratorPtr &imgGen : pendingImgGen) {
- auto loadTextureJob = Render::LoadTextureDataJobPtr::create(imgGen);
- textureLoadingSync->addDependency(loadTextureJob);
- loadTextureJob->setNodeManagers(manager);
- jobs.append(loadTextureJob);
- }
- const QVector<QTextureGeneratorPtr> pendingTexGen = manager->textureDataManager()->pendingGenerators();
- for (const QTextureGeneratorPtr &texGen : pendingTexGen) {
- auto loadTextureJob = Render::LoadTextureDataJobPtr::create(texGen);
- textureLoadingSync->addDependency(loadTextureJob);
- loadTextureJob->setNodeManagers(manager);
- jobs.append(loadTextureJob);
- }
-
- // Launch skeleton loader jobs. We join on the syncTextureLoadingJob for now
- // which should likely be renamed to something more generic or we introduce
- // another synchronizing job for skeleton loading
+ QAspectJobPtr assetLoadingSync = d->m_renderer->syncSkeletonLoadingJob();
+ assetLoadingSync->removeDependency(QWeakPointer<QAspectJob>());
+ // Launch skeleton loader jobs
const QVector<Render::HSkeleton> skeletonsToLoad =
manager->skeletonManager()->dirtySkeletons(Render::SkeletonManager::SkeletonDataDirty);
for (const auto &skeletonHandle : skeletonsToLoad) {
auto loadSkeletonJob = Render::LoadSkeletonJobPtr::create(skeletonHandle);
loadSkeletonJob->setNodeManagers(manager);
- textureLoadingSync->addDependency(loadSkeletonJob);
+ assetLoadingSync->addDependency(loadSkeletonJob);
jobs.append(loadSkeletonJob);
}
@@ -489,7 +489,6 @@ QVector<Qt3DCore::QAspectJobPtr> QRenderAspect::jobsToExecute(qint64 time)
const QVector<QAspectJobPtr> geometryJobs = d->createGeometryRendererJobs();
jobs.append(geometryJobs);
-
// Add all jobs to queue
const Qt3DCore::QAspectJobPtr pickBoundingVolumeJob = d->m_renderer->pickBoundingVolumeJob();
// Note: the getter is also responsible for returning a job ready to run
@@ -505,12 +504,16 @@ QVector<Qt3DCore::QAspectJobPtr> QRenderAspect::jobsToExecute(qint64 time)
return jobs;
}
- // Traverse the current framegraph and create jobs to populate
- // RenderBins with RenderCommands
- // All jobs needed to create the frame and their dependencies are set by
- // renderBinJobs()
- const QVector<QAspectJobPtr> renderBinJobs = d->m_renderer->renderBinJobs();
- jobs.append(renderBinJobs);
+ // Let the rendering thread know that we are ready to spawn jobs if it
+ // can promise us that it will in fact call doRender.
+ if (d->m_renderer->releaseRendererAndRequestPromiseToRender()) {
+ // Traverse the current framegraph and create jobs to populate
+ // RenderBins with RenderCommands
+ // All jobs needed to create the frame and their dependencies are set by
+ // renderBinJobs()
+ const QVector<QAspectJobPtr> renderBinJobs = d->m_renderer->renderBinJobs();
+ jobs.append(renderBinJobs);
+ }
}
return jobs;
}
diff --git a/src/render/frontend/qrenderaspect_p.h b/src/render/frontend/qrenderaspect_p.h
index 26ca091f6..d18ce8b4f 100644
--- a/src/render/frontend/qrenderaspect_p.h
+++ b/src/render/frontend/qrenderaspect_p.h
@@ -90,9 +90,10 @@ public:
void loadSceneParsers();
void loadRenderPlugin(const QString &pluginName);
void renderInitialize(QOpenGLContext *context);
- void renderSynchronous(bool blocking = false);
+ void tryRenderSynchronous();
void renderShutdown();
void registerBackendType(const QMetaObject &, const Qt3DCore::QBackendNodeMapperPtr &functor);
+ void abortRenderJobs();
QVector<Qt3DCore::QAspectJobPtr> createGeometryRendererJobs();
Render::NodeManagers *m_nodeManagers;
@@ -104,6 +105,7 @@ public:
QVector<Render::QRenderPlugin *> m_renderPlugins;
QRenderAspect::RenderType m_renderType;
Render::OffscreenSurfaceHelper *m_offscreenHelper;
+ bool m_blockingRendermode;
static QMutex m_pluginLock;
static QVector<QString> m_pluginConfig;
diff --git a/src/render/geometry/geometryrenderer.cpp b/src/render/geometry/geometryrenderer.cpp
index 00b53fd8d..c23fdadfd 100644
--- a/src/render/geometry/geometryrenderer.cpp
+++ b/src/render/geometry/geometryrenderer.cpp
@@ -42,6 +42,8 @@
#include <Qt3DRender/private/qboundingvolume_p.h>
#include <Qt3DRender/private/qgeometryrenderer_p.h>
#include <Qt3DRender/private/qmesh_p.h>
+#include <Qt3DRender/private/managers_p.h>
+#include <Qt3DRender/private/nodemanagers_p.h>
#include <Qt3DCore/qpropertyupdatedchange.h>
#include <Qt3DCore/qpropertynodeaddedchange.h>
#include <Qt3DCore/qpropertynoderemovedchange.h>
diff --git a/src/render/graphicshelpers/graphicscontext.cpp b/src/render/graphicshelpers/graphicscontext.cpp
index 9e8e3d610..298f6bf91 100644
--- a/src/render/graphicshelpers/graphicscontext.cpp
+++ b/src/render/graphicshelpers/graphicscontext.cpp
@@ -232,13 +232,10 @@ void GraphicsContext::resolveRenderTargetFormat()
#undef RGBA_BITS
}
-bool GraphicsContext::beginDrawing(QSurface *surface)
+void GraphicsContext::beginDrawing()
{
- Q_ASSERT(surface);
Q_ASSERT(m_gl);
- m_surface = surface;
-
// TO DO: Find a way to make to pause work if the window is not exposed
// if (m_surface && m_surface->surfaceClass() == QSurface::Window) {
// qDebug() << Q_FUNC_INFO << 1;
@@ -247,12 +244,6 @@ bool GraphicsContext::beginDrawing(QSurface *surface)
// qDebug() << Q_FUNC_INFO << 2;
// }
- // Makes the surface current on the OpenGLContext
- // and sets the right glHelper
- m_ownCurrent = !(m_gl->surface() == m_surface);
- if (m_ownCurrent && !makeCurrent(m_surface))
- return false;
-
// TODO: cache surface format somewhere rather than doing this every time render surface changes
resolveRenderTargetFormat();
@@ -293,8 +284,6 @@ bool GraphicsContext::beginDrawing(QSurface *surface)
const int shaderPurgePeriod = 600;
if (callCount % shaderPurgePeriod == 0)
m_shaderCache.purge();
-
- return true;
}
void GraphicsContext::clearBackBuffer(QClearBuffers::BufferTypeFlags buffers)
@@ -453,6 +442,7 @@ bool GraphicsContext::makeCurrent(QSurface *surface)
m_glHelper = resolveHighestOpenGLFunctions();
m_glHelpers.insert(surface, m_glHelper);
}
+ m_surface = surface;
return true;
}
@@ -544,7 +534,6 @@ void GraphicsContext::loadShader(Shader *shader, ShaderManager *manager)
shader->setGraphicsContext(this);
shader->setLoaded(true);
- shader->markDirty(AbstractRenderer::AllDirty);
}
}
diff --git a/src/render/graphicshelpers/graphicscontext_p.h b/src/render/graphicshelpers/graphicscontext_p.h
index 24b08e45e..bca841f53 100644
--- a/src/render/graphicshelpers/graphicscontext_p.h
+++ b/src/render/graphicshelpers/graphicscontext_p.h
@@ -113,7 +113,7 @@ public:
int id() const; // unique, small integer ID of this context
- bool beginDrawing(QSurface *surface);
+ void beginDrawing();
void clearBackBuffer(QClearBuffers::BufferTypeFlags buffers);
void endDrawing(bool swapBuffers);
diff --git a/src/render/jobs/job_common_p.h b/src/render/jobs/job_common_p.h
index 56c4346ed..e2ac1ddfb 100644
--- a/src/render/jobs/job_common_p.h
+++ b/src/render/jobs/job_common_p.h
@@ -99,11 +99,16 @@ namespace JobTypes {
UpdateMeshTriangleList,
FilterCompatibleTechniques,
UpdateLevelOfDetail,
- SyncTextureLoading,
+ SyncSkeletonLoading,
LoadSkeleton,
UpdateSkinningPalette,
ProximityFiltering,
- SyncFilterEntityByLayer
+ SyncFilterEntityByLayer,
+ ReadRenderQueueSizeBarrier,
+ BeginDrawingBarrier,
+ UpdateGLResourcesBarrier,
+ PrepareCommandSubmissionBarrier,
+ EndDrawingBarrier
};
} // JobTypes
diff --git a/src/render/jobs/loadtexturedatajob.cpp b/src/render/jobs/loadtexturedatajob.cpp
index 55232d74f..790e41998 100644
--- a/src/render/jobs/loadtexturedatajob.cpp
+++ b/src/render/jobs/loadtexturedatajob.cpp
@@ -49,16 +49,7 @@ QT_BEGIN_NAMESPACE
namespace Qt3DRender {
namespace Render {
-LoadTextureDataJob::LoadTextureDataJob(const QTextureGeneratorPtr &texGen)
- : m_texGen(texGen)
- , m_imgDataGen(nullptr)
-{
- SET_JOB_RUN_STAT_TYPE(this, JobTypes::LoadTextureData, 0);
-}
-
-LoadTextureDataJob::LoadTextureDataJob(const QTextureImageDataGeneratorPtr &imgDataGen)
- : m_texGen(nullptr)
- , m_imgDataGen(imgDataGen)
+LoadTextureDataJob::LoadTextureDataJob()
{
SET_JOB_RUN_STAT_TYPE(this, JobTypes::LoadTextureData, 0);
}
@@ -69,13 +60,20 @@ LoadTextureDataJob::~LoadTextureDataJob()
void LoadTextureDataJob::run()
{
- if (m_texGen) {
- QTextureDataPtr texData = (*m_texGen)();
- m_manager->textureDataManager()->assignData(m_texGen, texData);
+ // NOTE: This must run after Renderer::updateGLResources(),
+ // because that is where pendingGenerators is populated.
+ // We are therefore not able to create one job for each texture
+ // before we add the ability for running jobs (like this) to
+ // spawn new jobs.
+ const QVector<QTextureImageDataGeneratorPtr> pendingImgGen = m_manager->textureImageDataManager()->pendingGenerators();
+ for (const QTextureImageDataGeneratorPtr &imgGen : pendingImgGen) {
+ QTextureImageDataPtr imgData = (*imgGen)();
+ m_manager->textureImageDataManager()->assignData(imgGen, imgData);
}
- if (m_imgDataGen) {
- QTextureImageDataPtr imgData = (*m_imgDataGen)();
- m_manager->textureImageDataManager()->assignData(m_imgDataGen, imgData);
+ const QVector<QTextureGeneratorPtr> pendingTexGen = m_manager->textureDataManager()->pendingGenerators();
+ for (const QTextureGeneratorPtr &texGen : pendingTexGen) {
+ QTextureDataPtr texData = (*texGen)();
+ m_manager->textureDataManager()->assignData(texGen, texData);
}
}
diff --git a/src/render/jobs/loadtexturedatajob_p.h b/src/render/jobs/loadtexturedatajob_p.h
index 36fdd950b..49900a090 100644
--- a/src/render/jobs/loadtexturedatajob_p.h
+++ b/src/render/jobs/loadtexturedatajob_p.h
@@ -67,8 +67,7 @@ class NodeManagers;
class LoadTextureDataJob : public Qt3DCore::QAspectJob
{
public:
- LoadTextureDataJob(const QTextureGeneratorPtr &texGen);
- LoadTextureDataJob(const QTextureImageDataGeneratorPtr &imgDataGen);
+ LoadTextureDataJob();
~LoadTextureDataJob();
inline void setNodeManagers(NodeManagers *manager) { m_manager = manager; }
@@ -77,9 +76,6 @@ protected:
void run() Q_DECL_FINAL;
private:
- QTextureGeneratorPtr m_texGen;
- QTextureImageDataGeneratorPtr m_imgDataGen;
-
NodeManagers *m_manager;
};
diff --git a/src/render/services/vsyncframeadvanceservice.cpp b/src/render/services/vsyncframeadvanceservice.cpp
index 880985aea..81711e536 100644
--- a/src/render/services/vsyncframeadvanceservice.cpp
+++ b/src/render/services/vsyncframeadvanceservice.cpp
@@ -89,7 +89,7 @@ qint64 VSyncFrameAdvanceService::waitForNextFrame()
if (d->m_drivenByRenderThread)
d->m_semaphore.acquire(1);
else
- d->m_semaphore.acquire(d->m_semaphore.available() + 1);
+ d->m_semaphore.acquire(d->m_semaphore.available());
const quint64 currentTime = d->m_elapsed.nsecsElapsed();
qCDebug(VSyncAdvanceService) << "Elapsed nsecs since last call " << currentTime - d->m_elapsedTimeSincePreviousFrame;
diff --git a/tests/auto/render/commons/testrenderer.h b/tests/auto/render/commons/testrenderer.h
index 20ab032b1..6f5f535ee 100644
--- a/tests/auto/render/commons/testrenderer.h
+++ b/tests/auto/render/commons/testrenderer.h
@@ -52,14 +52,14 @@ public:
void shutdown() Q_DECL_OVERRIDE {}
void releaseGraphicsResources() Q_DECL_OVERRIDE {}
void render() Q_DECL_OVERRIDE {}
- void doRender(bool scene3dBlocking = false) Q_DECL_OVERRIDE { Q_UNUSED(scene3dBlocking); }
+ void doRender() Q_DECL_OVERRIDE {}
void cleanGraphicsResources() Q_DECL_OVERRIDE {}
bool isRunning() const Q_DECL_OVERRIDE { return true; }
bool shouldRender() Q_DECL_OVERRIDE { return true; }
void skipNextFrame() Q_DECL_OVERRIDE {}
QVector<Qt3DCore::QAspectJobPtr> renderBinJobs() Q_DECL_OVERRIDE { return QVector<Qt3DCore::QAspectJobPtr>(); }
Qt3DCore::QAspectJobPtr pickBoundingVolumeJob() Q_DECL_OVERRIDE { return Qt3DCore::QAspectJobPtr(); }
- Qt3DCore::QAspectJobPtr syncTextureLoadingJob() Q_DECL_OVERRIDE { return Qt3DCore::QAspectJobPtr(); }
+ Qt3DCore::QAspectJobPtr syncSkeletonLoadingJob() Q_DECL_OVERRIDE { return Qt3DCore::QAspectJobPtr(); }
Qt3DCore::QAspectJobPtr expandBoundingVolumeJob() Q_DECL_OVERRIDE { return Qt3DCore::QAspectJobPtr(); }
void setSceneRoot(Qt3DCore::QBackendNodeFactory *factory, Qt3DRender::Render::Entity *root) Q_DECL_OVERRIDE { Q_UNUSED(factory); Q_UNUSED(root); }
Qt3DRender::Render::Entity *sceneRoot() const Q_DECL_OVERRIDE { return nullptr; }
@@ -80,6 +80,12 @@ public:
void setOffscreenSurfaceHelper(Qt3DRender::Render::OffscreenSurfaceHelper *helper) Q_DECL_OVERRIDE;
QSurfaceFormat format() Q_DECL_OVERRIDE;
+ void lockSurfaceAndRender() override {}
+ bool releaseRendererAndRequestPromiseToRender() override { return true; }
+ bool waitForRenderJobs() override { return true; }
+ bool tryWaitForRenderJobs(int timeout) override { Q_UNUSED(timeout); return true; }
+ void abortRenderJobs() override {}
+
protected:
Qt3DRender::Render::AbstractRenderer::BackendNodeDirtySet m_changes;
Qt3DRender::Render::NodeManagers *m_managers;
diff --git a/tests/auto/render/filtercompatibletechniquejob/tst_filtercompatibletechniquejob.cpp b/tests/auto/render/filtercompatibletechniquejob/tst_filtercompatibletechniquejob.cpp
index 4d4a08a34..803d57fa9 100644
--- a/tests/auto/render/filtercompatibletechniquejob/tst_filtercompatibletechniquejob.cpp
+++ b/tests/auto/render/filtercompatibletechniquejob/tst_filtercompatibletechniquejob.cpp
@@ -93,7 +93,8 @@ public:
{
renderer()->setOpenGLContext(&m_glContext);
d_func()->m_renderer->initialize();
- renderer()->graphicsContext()->beginDrawing(m_window.data());
+ renderer()->graphicsContext()->makeCurrent(m_window.data());
+ renderer()->graphicsContext()->beginDrawing();
}
Render::Renderer *renderer() const
diff --git a/tests/auto/render/geometryrenderer/tst_geometryrenderer.cpp b/tests/auto/render/geometryrenderer/tst_geometryrenderer.cpp
index af074def0..2c6fe12b6 100644
--- a/tests/auto/render/geometryrenderer/tst_geometryrenderer.cpp
+++ b/tests/auto/render/geometryrenderer/tst_geometryrenderer.cpp
@@ -30,6 +30,7 @@
#include <qbackendnodetester.h>
#include <Qt3DRender/private/geometryrenderer_p.h>
#include <Qt3DRender/private/geometryrenderermanager_p.h>
+#include <Qt3DRender/private/nodemanagers_p.h>
#include <Qt3DRender/qgeometry.h>
#include <Qt3DRender/qgeometryfactory.h>
#include <Qt3DCore/qpropertyupdatedchange.h>
@@ -186,6 +187,8 @@ private Q_SLOTS:
// GIVEN
Qt3DRender::Render::GeometryRenderer renderGeometryRenderer;
TestRenderer renderer;
+ Qt3DRender::Render::NodeManagers nodeManagers;
+
renderGeometryRenderer.setRenderer(&renderer);
QVERIFY(!renderGeometryRenderer.isDirty());
diff --git a/tests/auto/render/render.pro b/tests/auto/render/render.pro
index da01dd95f..9355d8310 100644
--- a/tests/auto/render/render.pro
+++ b/tests/auto/render/render.pro
@@ -110,7 +110,8 @@ qtConfig(private_tests) {
proximityfiltering \
qblitframebuffer \
blitframebuffer \
- qcamera
+ qcamera \
+ renderbarrierjob
QT_FOR_CONFIG = 3dcore-private
qtConfig(qt3d-extras) {
diff --git a/tests/auto/render/renderbarrierjob/renderbarrierjob.pro b/tests/auto/render/renderbarrierjob/renderbarrierjob.pro
new file mode 100644
index 000000000..5a988144f
--- /dev/null
+++ b/tests/auto/render/renderbarrierjob/renderbarrierjob.pro
@@ -0,0 +1,9 @@
+TEMPLATE = app
+
+TARGET = tst_renderbarrierjob
+
+QT += 3dcore 3dcore-private 3drender 3drender-private testlib
+
+CONFIG += testcase
+
+SOURCES += tst_renderbarrierjob.cpp
diff --git a/tests/auto/render/renderbarrierjob/tst_renderbarrierjob.cpp b/tests/auto/render/renderbarrierjob/tst_renderbarrierjob.cpp
new file mode 100644
index 000000000..e3b49edd4
--- /dev/null
+++ b/tests/auto/render/renderbarrierjob/tst_renderbarrierjob.cpp
@@ -0,0 +1,118 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt3D module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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 General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** 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-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtTest/QtTest>
+#include <QMutex>
+#include <QWaitCondition>
+#include <QThread>
+#include <Qt3DRender/private/renderbarrierjob_p.h>
+#include <Qt3DRender/private/job_common_p.h>
+
+class TestRenderThread : public QThread
+{
+public:
+ TestRenderThread(Qt3DRender::Render::RenderBarrierJobPtr barrier)
+ : m_barrier(barrier)
+ {}
+
+ void run() override
+ {
+ m_started.store(1);
+ m_writeSemaphore.release();
+ m_readSemaphore.acquire();
+
+ m_barrier->allowToProceed();
+ m_released.store(1);
+ m_writeSemaphore.release();
+ m_readSemaphore.acquire();
+
+ m_barrier->waitForDependencies();
+ m_completed.store(1);
+ }
+
+ Qt3DRender::Render::RenderBarrierJobPtr m_barrier;
+ QSemaphore m_writeSemaphore;
+ QSemaphore m_readSemaphore;
+
+ QAtomicInt m_started;
+ QAtomicInt m_released;
+ QAtomicInt m_completed;
+};
+
+class tst_RenderBarrierJob : public QObject
+{
+ Q_OBJECT
+public :
+ tst_RenderBarrierJob() {}
+ ~tst_RenderBarrierJob() {}
+
+private Q_SLOTS:
+ void shouldControlRenderThread()
+ {
+ // GIVEN a barrier (of any type)
+ auto barrier = Qt3DRender::Render::RenderBarrierJobPtr::create(Qt3DRender::Render::JobTypes::BeginDrawingBarrier);
+ TestRenderThread testThread(barrier);
+
+ // THEN
+ QVERIFY(testThread.m_started.load() == 0);
+ QVERIFY(testThread.m_released.load() == 0);
+ QVERIFY(testThread.m_completed.load() == 0);
+
+ // WHEN
+ testThread.start();
+ testThread.m_writeSemaphore.acquire();
+
+ // THEN thread should have started
+ QVERIFY(testThread.m_started.load() == 1);
+ QVERIFY(testThread.m_released.load() == 0);
+ QVERIFY(testThread.m_completed.load() == 0);
+ testThread.m_readSemaphore.release();
+
+ // WHEN
+ testThread.m_writeSemaphore.acquire();
+
+ // THEN job should be released
+ QVERIFY(testThread.m_started.load() == 1);
+ QVERIFY(testThread.m_released.load() == 1);
+ QVERIFY(testThread.m_completed.load() == 0);
+ testThread.m_readSemaphore.release();
+
+ // WHEN job runs and we wait for thread
+ barrier->run();
+ testThread.wait();
+
+ // THEN thread should be finished
+ QVERIFY(testThread.m_started.load() == 1);
+ QVERIFY(testThread.m_released.load() == 1);
+ QVERIFY(testThread.m_completed.load() == 1);
+ }
+};
+
+QTEST_MAIN(tst_RenderBarrierJob)
+
+#include "tst_renderbarrierjob.moc"
diff --git a/tests/auto/render/renderer/tst_renderer.cpp b/tests/auto/render/renderer/tst_renderer.cpp
index b53c26f83..1d2f172a7 100644
--- a/tests/auto/render/renderer/tst_renderer.cpp
+++ b/tests/auto/render/renderer/tst_renderer.cpp
@@ -34,6 +34,9 @@
#include <Qt3DRender/private/viewportnode_p.h>
#include <Qt3DRender/private/renderview_p.h>
#include <Qt3DRender/private/renderviewbuilder_p.h>
+#include <Qt3DRender/private/offscreensurfacehelper_p.h>
+#include <Qt3DRender/private/loadtexturedatajob_p.h>
+#include <Qt3DRender/private/renderbarrierjob_p.h>
class tst_Renderer : public QObject
{
@@ -63,47 +66,62 @@ private Q_SLOTS:
// NOTE: FilterCompatibleTechniqueJob and ShaderGathererJob cannot run because the context
// is not initialized in this test
- const int singleRenderViewJobCount = 11 + 2 * Qt3DRender::Render::RenderViewBuilder::optimalJobCount();
+ const int renderBarrierJobCount = 5;
+ // Barriers: readRenderQueueSize
+ // beginDrawing
+ // updateGLResources
+ // prepareCommandSubmission
+ // endDrawing
+
+ const int renderViewJobCount = 13 + 2 * Qt3DRender::Render::RenderViewBuilder::optimalJobCount();
// RenderViewBuilder renderViewJob,
// renderableEntityFilterJob,
// lightGatherJob,
// computableEntityFilterJob,
// syncRenderViewInitializationJob,
- // syncFrustumCullingJob,
// filterEntityByLayerJob,
+ // syncFilterEntityByLayerJob,
+ // syncFrustumCullingJob,
// filterProximityJob,
// setClearDrawBufferIndexJob,
// frustumCullingJob,
// syncRenderCommandBuldingJob,
- // syncRenderViewCommandBuilderJob)
- // n * (RenderViewCommandBuildJobs + MaterialGathererJobs
+ // syncRenderViewCommandBuilderJob
+ // n * (RenderViewCommandBuildJobs + MaterialGathererJobs)
+
+ const int flagIndependentRendererJobCount = 6;
+ // Flag independent: updateLevelOfDetailJob
+ // cleanupJob
+ // sendRenderCaptureJob
+ // sendBufferCaptureJob
+ // VAOGatherer
+ // updateSkinningPaletteJob
+
+ const int flagIndependetRenderViewJobCount = renderViewJobCount - 2;
+ // Cached: filterEntityByLayerJob,
+ // syncFilterEntityByLayerJob,
+
// WHEN (nothing dirty, no buffers)
QVector<Qt3DCore::QAspectJobPtr> jobs = renderer.renderBinJobs();
- // THEN (level
+ // THEN
QCOMPARE(jobs.size(),
- 1 + // updateLevelOfDetailJob
- 1 + // cleanupJob
- 1 + // sendRenderCaptureJob
- 1 + // sendBufferCaptureJob
- 1 + // VAOGatherer
- 1 + // updateSkinningPaletteJob
- singleRenderViewJobCount); // Only valid for the first call to renderBinJobs(), since subsequent calls won't have the renderqueue reset
-
+ flagIndependetRenderViewJobCount +
+ renderBarrierJobCount +
+ flagIndependentRendererJobCount);
// WHEN
renderer.markDirty(Qt3DRender::Render::AbstractRenderer::EntityEnabledDirty, nullptr);
jobs = renderer.renderBinJobs();
- // THEN (level
+ // THEN
QCOMPARE(jobs.size(),
- 1 + // updateLevelOfDetailJob
- 1 + // cleanupJob
- 1 + // sendRenderCaptureJob
- 1 + // sendBufferCaptureJob
- 1 + // VAOGatherer
- 1 + // updateSkinningPaletteJob
+ flagIndependetRenderViewJobCount +
+ renderBarrierJobCount +
+ flagIndependentRendererJobCount +
+ 1 + // filterEntityByLayerJob
+ 1 + // syncFilterEntityByLayerJob
1); // EntityEnabledDirty
renderer.clearDirtyBits(Qt3DRender::Render::AbstractRenderer::AllDirty);
@@ -112,17 +130,14 @@ private Q_SLOTS:
renderer.markDirty(Qt3DRender::Render::AbstractRenderer::TransformDirty, nullptr);
jobs = renderer.renderBinJobs();
- // THEN (level
+ // THEN
QCOMPARE(jobs.size(),
- 1 + // updateLevelOfDetailJob
- 1 + // cleanupJob
- 1 + // sendRenderCaptureJob
- 1 + // sendBufferCaptureJob
- 1 + // VAOGatherer
+ flagIndependetRenderViewJobCount +
+ renderBarrierJobCount +
+ flagIndependentRendererJobCount +
1 + // WorldTransformJob
1 + // UpdateWorldBoundingVolume
1 + // UpdateShaderDataTransform
- 1 + // updateSkinningPaletteJob
1); // ExpandBoundingVolumeJob
renderer.clearDirtyBits(Qt3DRender::Render::AbstractRenderer::AllDirty);
@@ -131,16 +146,13 @@ private Q_SLOTS:
renderer.markDirty(Qt3DRender::Render::AbstractRenderer::GeometryDirty, nullptr);
jobs = renderer.renderBinJobs();
- // THEN (level
+ // THEN
QCOMPARE(jobs.size(),
- 1 + // updateLevelOfDetailJob
- 1 + // cleanupJob
- 1 + // sendRenderCaptureJob
- 1 + // sendBufferCaptureJob
- 1 + // VAOGatherer
+ flagIndependetRenderViewJobCount +
+ renderBarrierJobCount +
+ flagIndependentRendererJobCount +
1 + // CalculateBoundingVolumeJob
1 + // UpdateMeshTriangleListJob
- 1 + // updateSkinningPaletteJob
1); // ExpandBoundingVolumeJob
renderer.clearDirtyBits(Qt3DRender::Render::AbstractRenderer::AllDirty);
@@ -149,14 +161,11 @@ private Q_SLOTS:
renderer.markDirty(Qt3DRender::Render::AbstractRenderer::BuffersDirty, nullptr);
jobs = renderer.renderBinJobs();
- // THEN (level
+ // THEN
QCOMPARE(jobs.size(),
- 1 + // updateLevelOfDetailJob
- 1 + // cleanupJob
- 1 + // sendRenderCaptureJob
- 1 + // sendBufferCaptureJob
- 1 + // VAOGatherer
- 1 + // updateSkinningPaletteJob
+ flagIndependetRenderViewJobCount +
+ renderBarrierJobCount +
+ flagIndependentRendererJobCount +
1); // BufferGathererJob
renderer.clearDirtyBits(Qt3DRender::Render::AbstractRenderer::AllDirty);
@@ -165,16 +174,26 @@ private Q_SLOTS:
renderer.markDirty(Qt3DRender::Render::AbstractRenderer::TexturesDirty, nullptr);
jobs = renderer.renderBinJobs();
- // THEN (level
+ // THEN
QCOMPARE(jobs.size(),
- 1 + // updateLevelOfDetailJob
- 1 + // cleanupJob
- 1 + // sendRenderCaptureJob
- 1 + // sendBufferCaptureJob
- 1 + // VAOGatherer
- 1 + // TexturesGathererJob
- 1 + // updateSkinningPaletteJob
- 1); // SyncTexturesGathererJob
+ flagIndependetRenderViewJobCount +
+ renderBarrierJobCount +
+ flagIndependentRendererJobCount +
+ 1 + // LoadTextureDataJob
+ 1); // TexturesGathererJob
+
+ renderer.clearDirtyBits(Qt3DRender::Render::AbstractRenderer::AllDirty);
+
+ // WHEN
+ renderer.markDirty(Qt3DRender::Render::AbstractRenderer::SkeletonDataDirty, nullptr);
+ jobs = renderer.renderBinJobs();
+
+ // THEN
+ QCOMPARE(jobs.size(),
+ flagIndependetRenderViewJobCount +
+ renderBarrierJobCount +
+ flagIndependentRendererJobCount +
+ 1); // SyncSkeletonLoadingJob
renderer.clearDirtyBits(Qt3DRender::Render::AbstractRenderer::AllDirty);
@@ -182,28 +201,45 @@ private Q_SLOTS:
renderer.markDirty(Qt3DRender::Render::AbstractRenderer::AllDirty, nullptr);
jobs = renderer.renderBinJobs();
- // THEN (level
+ // THEN
QCOMPARE(jobs.size(),
- 1 + // updateLevelOfDetailJob
- 1 + // cleanupJob
- 1 + // sendRenderCaptureJob
- 1 + // sendBufferCaptureJob
- 1 + // VAOGatherer
- 1 + // EntityEnabledDirty
+ renderViewJobCount +
+ renderBarrierJobCount +
+ flagIndependentRendererJobCount +
+ 1 + // UpdateTreeEnablee
1 + // WorldTransformJob
1 + // UpdateWorldBoundingVolume
1 + // UpdateShaderDataTransform
- 1 + // ExpandBoundingVolumeJob
1 + // CalculateBoundingVolumeJob
1 + // UpdateMeshTriangleListJob
+ 1 + // ExpandBoundingVolumeJob
1 + // BufferGathererJob
+ 1 + // LoadTextureDataJob
1 + // TexturesGathererJob
- 1 + // updateSkinningPaletteJob
- 1); // SyncTexturesGathererJob
+ 1 + // SyncSkeletonLoadingJob
+ 1 + // FilterCompatibleTechniqueJob
+ 1); // ShaderGathererJob
renderer.clearDirtyBits(Qt3DRender::Render::AbstractRenderer::AllDirty);
+ }
+
+ void checkRenderBarrierJobDependencies()
+ {
+ // GIVEN
+ Qt3DRender::Render::Renderer renderer(Qt3DRender::QRenderAspect::Synchronous);
+
+ // THEN
+ // internal dependencies
+ QVERIFY(renderer.beginDrawingBarrierJob()->dependencies().contains(renderer.readRenderQueueSizeBarrierJob()));
+ QVERIFY(renderer.updateGLResourcesBarrierJob()->dependencies().contains(renderer.beginDrawingBarrierJob()));
+ QVERIFY(renderer.prepareCommandSubmissionBarrierJob()->dependencies().contains(renderer.updateGLResourcesBarrierJob()));
+ QVERIFY(renderer.endDrawingBarrierJob()->dependencies().contains(renderer.prepareCommandSubmissionBarrierJob()));
+ QVERIFY(renderer.filterCompatibleTechniqueJob()->dependencies().contains(renderer.beginDrawingBarrierJob()));
+ QVERIFY(renderer.loadTextureJob()->dependencies().contains(renderer.updateGLResourcesBarrierJob()));
+ QVERIFY(renderer.prepareCommandSubmissionBarrierJob()->dependencies().contains(renderer.loadTextureJob()));
+ QVERIFY(renderer.updateGLResourcesBarrierJob()->dependencies().contains(renderer.filterCompatibleTechniqueJob()));
}
};
diff --git a/tests/auto/render/renderviewbuilder/tst_renderviewbuilder.cpp b/tests/auto/render/renderviewbuilder/tst_renderviewbuilder.cpp
index 21e2e2639..1309249d7 100644
--- a/tests/auto/render/renderviewbuilder/tst_renderviewbuilder.cpp
+++ b/tests/auto/render/renderviewbuilder/tst_renderviewbuilder.cpp
@@ -49,6 +49,7 @@
#include <Qt3DRender/private/qrenderaspect_p.h>
#include <Qt3DRender/private/nodemanagers_p.h>
#include <Qt3DRender/private/managers_p.h>
+#include <Qt3DRender/private/renderbarrierjob_p.h>
QT_BEGIN_NAMESPACE
@@ -290,21 +291,25 @@ private Q_SLOTS:
QVERIFY(renderViewBuilder.frustumCullingJob()->dependencies().contains(renderViewBuilder.syncFrustumCullingJob()));
QVERIFY(renderViewBuilder.frustumCullingJob()->dependencies().contains(testAspect.renderer()->expandBoundingVolumeJob()));
- QCOMPARE(renderViewBuilder.syncRenderCommandBuildingJob()->dependencies().size(), renderViewBuilder.materialGathererJobs().size() + 6);
+ QCOMPARE(renderViewBuilder.syncRenderCommandBuildingJob()->dependencies().size(), renderViewBuilder.materialGathererJobs().size() + 7);
QVERIFY(renderViewBuilder.syncRenderCommandBuildingJob()->dependencies().contains(renderViewBuilder.syncRenderViewInitializationJob()));
QVERIFY(renderViewBuilder.syncRenderCommandBuildingJob()->dependencies().contains(renderViewBuilder.renderableEntityFilterJob()));
QVERIFY(renderViewBuilder.syncRenderCommandBuildingJob()->dependencies().contains(renderViewBuilder.computableEntityFilterJob()));
QVERIFY(renderViewBuilder.syncRenderCommandBuildingJob()->dependencies().contains(renderViewBuilder.filterProximityJob()));
QVERIFY(renderViewBuilder.syncRenderCommandBuildingJob()->dependencies().contains(renderViewBuilder.lightGathererJob()));
QVERIFY(renderViewBuilder.syncRenderCommandBuildingJob()->dependencies().contains(renderViewBuilder.frustumCullingJob()));
+ QVERIFY(renderViewBuilder.syncRenderCommandBuildingJob()->dependencies().contains(testAspect.renderer()->updateGLResourcesBarrierJob()));
for (const auto materialGatherer : renderViewBuilder.materialGathererJobs()) {
QVERIFY(renderViewBuilder.syncRenderCommandBuildingJob()->dependencies().contains(materialGatherer));
}
// Step 5
for (const auto renderViewBuilderJob : renderViewBuilder.renderViewBuilderJobs()) {
- QCOMPARE(renderViewBuilderJob->dependencies().size(), 1);
- QCOMPARE(renderViewBuilderJob->dependencies().first().data(), renderViewBuilder.syncRenderCommandBuildingJob().data());
+ QCOMPARE(renderViewBuilderJob->dependencies().size(), 4);
+ QVERIFY(renderViewBuilderJob->dependencies().contains(renderViewBuilder.syncRenderCommandBuildingJob()));
+ QVERIFY(renderViewBuilderJob->dependencies().contains(testAspect.renderer()->updateSkinningPaletteJob()));
+ QVERIFY(renderViewBuilderJob->dependencies().contains(testAspect.renderer()->updateWorldBoundingVolumeJob()));
+ QVERIFY(renderViewBuilderJob->dependencies().contains(testAspect.renderer()->updateShaderDataTransformJob()));
}
// Step 6
@@ -362,7 +367,7 @@ private Q_SLOTS:
QVERIFY(renderViewBuilder.frustumCullingJob()->dependencies().contains(renderViewBuilder.syncFrustumCullingJob()));
QVERIFY(renderViewBuilder.frustumCullingJob()->dependencies().contains(testAspect.renderer()->expandBoundingVolumeJob()));
- QCOMPARE(renderViewBuilder.syncRenderCommandBuildingJob()->dependencies().size(), renderViewBuilder.materialGathererJobs().size() + 7);
+ QCOMPARE(renderViewBuilder.syncRenderCommandBuildingJob()->dependencies().size(), renderViewBuilder.materialGathererJobs().size() + 8);
QVERIFY(renderViewBuilder.syncRenderCommandBuildingJob()->dependencies().contains(renderViewBuilder.syncRenderViewInitializationJob()));
QVERIFY(renderViewBuilder.syncRenderCommandBuildingJob()->dependencies().contains(renderViewBuilder.renderableEntityFilterJob()));
QVERIFY(renderViewBuilder.syncRenderCommandBuildingJob()->dependencies().contains(renderViewBuilder.computableEntityFilterJob()));
@@ -370,14 +375,18 @@ private Q_SLOTS:
QVERIFY(renderViewBuilder.syncRenderCommandBuildingJob()->dependencies().contains(renderViewBuilder.lightGathererJob()));
QVERIFY(renderViewBuilder.syncRenderCommandBuildingJob()->dependencies().contains(renderViewBuilder.frustumCullingJob()));
QVERIFY(renderViewBuilder.syncRenderCommandBuildingJob()->dependencies().contains(renderViewBuilder.filterProximityJob()));
+ QVERIFY(renderViewBuilder.syncRenderCommandBuildingJob()->dependencies().contains(testAspect.renderer()->updateGLResourcesBarrierJob()));
for (const auto materialGatherer : renderViewBuilder.materialGathererJobs()) {
QVERIFY(renderViewBuilder.syncRenderCommandBuildingJob()->dependencies().contains(materialGatherer));
}
// Step 5
for (const auto renderViewBuilderJob : renderViewBuilder.renderViewBuilderJobs()) {
- QCOMPARE(renderViewBuilderJob->dependencies().size(), 1);
- QCOMPARE(renderViewBuilderJob->dependencies().first().data(), renderViewBuilder.syncRenderCommandBuildingJob().data());
+ QCOMPARE(renderViewBuilderJob->dependencies().size(), 4);
+ QVERIFY(renderViewBuilderJob->dependencies().contains(renderViewBuilder.syncRenderCommandBuildingJob()));
+ QVERIFY(renderViewBuilderJob->dependencies().contains(testAspect.renderer()->updateSkinningPaletteJob()));
+ QVERIFY(renderViewBuilderJob->dependencies().contains(testAspect.renderer()->updateWorldBoundingVolumeJob()));
+ QVERIFY(renderViewBuilderJob->dependencies().contains(testAspect.renderer()->updateShaderDataTransformJob()));
}
// Step 6