diff options
author | Paul Lemire <paul.lemire@kdab.com> | 2017-06-02 08:45:55 +0200 |
---|---|---|
committer | Paul Lemire <paul.lemire@kdab.com> | 2018-03-19 07:49:27 +0000 |
commit | 34f6d8a88677cffa44be05da7e1e2da0cfc2f3b4 (patch) | |
tree | d95b8632aa5a895b1eaa3cbb14891758923d93c9 /src/render/renderers/opengl/renderer | |
parent | e28192812168b676b57dc505b31eed3bfcba0e67 (diff) |
Move Renderer specific classes into new folder
This is another step toward isolating the renderer from the render aspect
Change-Id: I4031675b961d6645b65bbe05cf62d150993038b0
Task-number: QTBUG-61151
Reviewed-by: Paul Lemire <paul.lemire@kdab.com>
Diffstat (limited to 'src/render/renderers/opengl/renderer')
21 files changed, 6481 insertions, 0 deletions
diff --git a/src/render/renderers/opengl/renderer/commandthread.cpp b/src/render/renderers/opengl/renderer/commandthread.cpp new file mode 100644 index 000000000..387fc1113 --- /dev/null +++ b/src/render/renderers/opengl/renderer/commandthread.cpp @@ -0,0 +1,195 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "commandthread_p.h" +#include <Qt3DRender/private/glcommands_p.h> +#include <Qt3DRender/private/offscreensurfacehelper_p.h> +#include <Qt3DRender/private/graphicscontext_p.h> +#include <Qt3DRender/private/shadercache_p.h> +#include <QOpenGLContext> +#include <QOffscreenSurface> +#include <QDebug> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { + +namespace Render { + +CommandThread::CommandThread(Renderer *renderer) + : QThread() + , m_renderer(renderer) + , m_waitForStartSemaphore(0) + , m_initializedSemaphore(0) + , m_commandRequestedSemaphore(0) + , m_commandExecutionSemaphore(0) + , m_mainContext(nullptr) + , m_shaderCache(nullptr) + , m_offsreenSurfaceHelper(nullptr) + , m_currentCommand(nullptr) + , m_running(0) +{ +} + +CommandThread::~CommandThread() +{ +} + +void CommandThread::setShaderCache(ShaderCache *shaderCache) +{ + m_shaderCache = shaderCache; +} + +// Called by RenderThread or MainThread (Scene3d) +void CommandThread::initialize(QOpenGLContext *mainContext, OffscreenSurfaceHelper *offsreenSurfaceHelper) +{ + // Start the thread + start(); + + // Wait for thread to be started + m_waitForStartSemaphore.acquire(); + + m_mainContext = mainContext; + m_offsreenSurfaceHelper = offsreenSurfaceHelper; + Q_ASSERT(m_mainContext && offsreenSurfaceHelper); + m_running.fetchAndStoreOrdered(1); + + // Allow thread to proceed + m_initializedSemaphore.release(); +} + +// Called by RenderThread or MainThread (Scene3D) +void CommandThread::shutdown() +{ + m_running.fetchAndStoreOrdered(0); + + // Unblock thread + m_commandRequestedSemaphore.release(1); + + // Wait for thread to exit + wait(); + + // Reset semaphores (in case we ever want to restart) + m_waitForStartSemaphore.acquire(m_waitForStartSemaphore.available()); + m_initializedSemaphore.acquire(m_initializedSemaphore.available()); + m_commandRequestedSemaphore.acquire(m_commandRequestedSemaphore.available()); + m_commandExecutionSemaphore.acquire(m_commandExecutionSemaphore.available()); + m_localContext.reset(); +} + +// Any thread can call this, this is a blocking command +void CommandThread::executeCommand(GLCommand *command) +{ + if (!isRunning()) + return; + + // We lock to prevent any other call to executeCommand to be executed + // before we have received the result of our command + m_blockingCallerMutex.lock(); + + // Store command to be executed + m_currentCommand = command; + + // Allow thread to proceed and execute command + m_commandRequestedSemaphore.release(); + + // Wait for thread to be done + m_commandExecutionSemaphore.acquire(); + + // Reset command + m_currentCommand = nullptr; + + // Unlock blocking semaphore so that other calls to executeCommand + // can proceed + m_blockingCallerMutex.unlock(); +} + +void CommandThread::run() +{ + // Allow initialize to proceed + m_waitForStartSemaphore.release(); + + // Wait for initialize to be completed + m_initializedSemaphore.acquire(); + + Q_ASSERT(m_mainContext && m_shaderCache); + + // Initialize shared context and resources for the thread + m_localContext.reset(new QOpenGLContext()); + m_localContext->setShareContext(m_mainContext); + m_localContext->create(); + + // Initialize GraphicsContext + m_graphicsContext.reset(new GraphicsContext()); + m_graphicsContext->setShaderCache(m_shaderCache); + m_graphicsContext->setOpenGLContext(m_localContext.data()); + + bool initialized = false; + while (true) { + + // Wait for command + m_commandRequestedSemaphore.acquire(); + + // Are we still running? + if (!m_running.load()) { + m_graphicsContext->doneCurrent(); + // to prevent executeCommand being locked + m_commandExecutionSemaphore.release(); + break; + } + + if (Q_UNLIKELY(!initialized)) { + QOffscreenSurface *offscreenSurface = m_offsreenSurfaceHelper->offscreenSurface(); + Q_ASSERT(offscreenSurface); + m_graphicsContext->makeCurrent(offscreenSurface); + initialized = true; + } + + m_currentCommand->execute(m_renderer, m_graphicsContext.data()); + + // Allow caller to proceed as we are done with the command + m_commandExecutionSemaphore.release(); + } +} + +} // Render + +} // Qt3DRender + +QT_END_NAMESPACE diff --git a/src/render/renderers/opengl/renderer/commandthread_p.h b/src/render/renderers/opengl/renderer/commandthread_p.h new file mode 100644 index 000000000..0508675c4 --- /dev/null +++ b/src/render/renderers/opengl/renderer/commandthread_p.h @@ -0,0 +1,115 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT3DRENDER_RENDER_COMMANDTHREAD_P_H +#define QT3DRENDER_RENDER_COMMANDTHREAD_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 <QtCore/QThread> +#include <QtCore/QSemaphore> +#include <QtCore/QMutex> + +QT_BEGIN_NAMESPACE + +class QOpenGLContext; + +namespace Qt3DRender { + +namespace Render { + +class Renderer; +class GLCommand; +class OffscreenSurfaceHelper; +class GraphicsContext; +class ShaderCache; + +class CommandThread : public QThread +{ + Q_OBJECT +public: + explicit CommandThread(Renderer *renderer); + ~CommandThread(); + + Render::Renderer* renderer() const { return m_renderer; } + + void setShaderCache(ShaderCache *shaderCache); + ShaderCache *shaderCache() const { return m_shaderCache; } + + void initialize(QOpenGLContext *mainContext, OffscreenSurfaceHelper *offsreenSurfaceHelper); + void shutdown(); + + void executeCommand(GLCommand *command); + +private: + void run() override; + void executeCommandInternal(Qt3DRender::Render::GLCommand *command); + +private: + Renderer* m_renderer; + QSemaphore m_waitForStartSemaphore; + QSemaphore m_initializedSemaphore; + QSemaphore m_commandRequestedSemaphore; + QSemaphore m_commandExecutionSemaphore; + QMutex m_blockingCallerMutex; + QOpenGLContext *m_mainContext; + ShaderCache *m_shaderCache; + OffscreenSurfaceHelper *m_offsreenSurfaceHelper; + QScopedPointer<QOpenGLContext> m_localContext; + QScopedPointer<GraphicsContext> m_graphicsContext; + GLCommand *m_currentCommand; + QAtomicInt m_running; +}; + +} // Render + +} // Qt3DRender + +QT_END_NAMESPACE + +#endif // QT3DRENDER_RENDER_COMMANDTHREAD_P_H diff --git a/src/render/renderers/opengl/renderer/glcommands.cpp b/src/render/renderers/opengl/renderer/glcommands.cpp new file mode 100644 index 000000000..fd7ee9fe8 --- /dev/null +++ b/src/render/renderers/opengl/renderer/glcommands.cpp @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "glcommands_p.h" +#include <Qt3DRender/private/renderer_p.h> +#include <Qt3DRender/private/graphicscontext_p.h> +#include <Qt3DRender/private/nodemanagers_p.h> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { + +namespace Render { + +LoadShaderCommand::LoadShaderCommand(Shader *shader) + : m_shader(shader) +{ + Q_ASSERT(m_shader); +} + +void LoadShaderCommand::execute(Renderer *renderer, GraphicsContext *ctx) +{ + NodeManagers *nodeManagers = renderer->nodeManagers(); + ctx->loadShader(m_shader, nodeManagers->shaderManager()); +} + +} // Render + +} // Qt3DRender + +QT_END_NAMESPACE diff --git a/src/render/renderers/opengl/renderer/glcommands_p.h b/src/render/renderers/opengl/renderer/glcommands_p.h new file mode 100644 index 000000000..5ed360759 --- /dev/null +++ b/src/render/renderers/opengl/renderer/glcommands_p.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT3DRENDER_RENDER_GLCOMMANDS_P_H +#define QT3DRENDER_RENDER_GLCOMMANDS_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 <Qt3DRender/qt3drender_global.h> + +QT_BEGIN_NAMESPACE + + +namespace Qt3DRender { + +namespace Render { + +class GraphicsContext; +class Renderer; +class Shader; + +class GLCommand +{ +public: + virtual void execute(Renderer *renderer, GraphicsContext *ctx) = 0; +}; + +class Q_AUTOTEST_EXPORT LoadShaderCommand : public GLCommand +{ +public: + explicit LoadShaderCommand(Shader *shader); + Shader *shader() const { return m_shader; } + void execute(Renderer *renderer, GraphicsContext *ctx) Q_DECL_OVERRIDE; + +private: + Shader *m_shader = nullptr; +}; +} // Render + +} // Qt3DRender + +QT_END_NAMESPACE + +#endif // QT3DRENDER_RENDER_GLCOMMANDS_P_H diff --git a/src/render/renderers/opengl/renderer/openglvertexarrayobject.cpp b/src/render/renderers/opengl/renderer/openglvertexarrayobject.cpp new file mode 100644 index 000000000..0c4fd8c9d --- /dev/null +++ b/src/render/renderers/opengl/renderer/openglvertexarrayobject.cpp @@ -0,0 +1,160 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Klaralvdalens Datakonsult AB (KDAB). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "openglvertexarrayobject_p.h" +#include <Qt3DRender/private/submissioncontext_p.h> +#include <Qt3DRender/private/renderer_p.h> +#include <Qt3DRender/private/nodemanagers_p.h> +#include <Qt3DRender/private/managers_p.h> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { +namespace Render { + +OpenGLVertexArrayObject::OpenGLVertexArrayObject() + : m_ctx(nullptr) + , m_specified(false) + , m_supportsVao(false) +{} + +void OpenGLVertexArrayObject::bind() +{ + Q_ASSERT(m_ctx); + if (m_supportsVao) { + Q_ASSERT(!m_vao.isNull()); + Q_ASSERT(m_vao->isCreated()); + m_vao->bind(); + } else { + // Unbind any other VAO that may have been bound and not released correctly + if (m_ctx->m_currentVAO != nullptr && m_ctx->m_currentVAO != this) + m_ctx->m_currentVAO->release(); + + m_ctx->m_currentVAO = this; + // We need to specify array and vertex attributes + for (const SubmissionContext::VAOVertexAttribute &attr : qAsConst(m_vertexAttributes)) + m_ctx->enableAttribute(attr); + if (!m_indexAttribute.isNull()) + m_ctx->bindGLBuffer(m_ctx->m_renderer->nodeManagers()->glBufferManager()->data(m_indexAttribute), + GLBuffer::IndexBuffer); + } +} + +void OpenGLVertexArrayObject::release() +{ + Q_ASSERT(m_ctx); + if (m_supportsVao) { + Q_ASSERT(!m_vao.isNull()); + Q_ASSERT(m_vao->isCreated()); + m_vao->release(); + } else { + if (m_ctx->m_currentVAO == this) { + for (const SubmissionContext::VAOVertexAttribute &attr : qAsConst(m_vertexAttributes)) + m_ctx->disableAttribute(attr); + m_ctx->m_currentVAO = nullptr; + } + } +} + +// called from Render thread +void OpenGLVertexArrayObject::create(SubmissionContext *ctx, const VAOIdentifier &key) +{ + QMutexLocker lock(&m_mutex); + + Q_ASSERT(!m_ctx && !m_vao); + + m_ctx = ctx; + m_supportsVao = m_ctx->supportsVAO(); + if (m_supportsVao) { + m_vao.reset(new QOpenGLVertexArrayObject()); + m_vao->create(); + } + m_owners = key; +} + +// called from Render thread +void OpenGLVertexArrayObject::destroy() +{ + QMutexLocker locker(&m_mutex); + + Q_ASSERT(m_ctx); + cleanup(); +} + +void OpenGLVertexArrayObject::cleanup() +{ + m_vao.reset(); + m_ctx = nullptr; + m_specified = false; + m_supportsVao = false; + m_indexAttribute = SubmissionContext::VAOIndexAttribute(); + m_vertexAttributes.clear(); +} + +// called from job +bool OpenGLVertexArrayObject::isAbandoned(GeometryManager *geomMgr, ShaderManager *shaderMgr) +{ + QMutexLocker lock(&m_mutex); + + if (!m_ctx) + return false; + + const bool geometryExists = (geomMgr->data(m_owners.first) != nullptr); + const bool shaderExists = (shaderMgr->data(m_owners.second) != nullptr); + + return !geometryExists || !shaderExists; +} + +void OpenGLVertexArrayObject::saveVertexAttribute(const SubmissionContext::VAOVertexAttribute &attr) +{ + // Remove any vertexAttribute already at location + for (auto i = m_vertexAttributes.size() - 1; i >= 0; --i) { + if (m_vertexAttributes.at(i).location == attr.location) { + m_vertexAttributes.removeAt(i); + break; + } + } + m_vertexAttributes.push_back(attr); +} + + +} // namespace Render +} // namespace Qt3DRender + +QT_END_NAMESPACE diff --git a/src/render/renderers/opengl/renderer/openglvertexarrayobject_p.h b/src/render/renderers/opengl/renderer/openglvertexarrayobject_p.h new file mode 100644 index 000000000..eee837221 --- /dev/null +++ b/src/render/renderers/opengl/renderer/openglvertexarrayobject_p.h @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Klaralvdalens Datakonsult AB (KDAB). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef OPENGLVERTEXARRAYOBJECT_H +#define OPENGLVERTEXARRAYOBJECT_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 <QtGui/qopenglvertexarrayobject.h> +#include <Qt3DRender/private/submissioncontext_p.h> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { +namespace Render { + +class GeometryManager; +class ShaderManager; + +typedef QPair<HGeometry, HShader> VAOIdentifier; + +class OpenGLVertexArrayObject +{ +public: + OpenGLVertexArrayObject(); + + void bind(); + void release(); + + void create(SubmissionContext *ctx, const VAOIdentifier &key); + void destroy(); + void cleanup(); + + bool isAbandoned(GeometryManager *geomMgr, ShaderManager *shaderMgr); + + QOpenGLVertexArrayObject *vao() { return m_vao.data(); } + const QOpenGLVertexArrayObject *vao() const { return m_vao.data(); } + + void setSpecified(bool b) { m_specified = b; } + bool isSpecified() const { return m_specified; } + + +private: + QMutex m_mutex; + SubmissionContext *m_ctx; + QScopedPointer<QOpenGLVertexArrayObject> m_vao; + bool m_specified; + bool m_supportsVao; + VAOIdentifier m_owners; + + friend class SubmissionContext; + + void saveVertexAttribute(const SubmissionContext::VAOVertexAttribute &attr); + inline void saveIndexAttribute(HGLBuffer glBufferHandle) { m_indexAttribute = glBufferHandle; } + + QVector<SubmissionContext::VAOVertexAttribute> m_vertexAttributes; + SubmissionContext::VAOIndexAttribute m_indexAttribute; +}; + +} // namespace Render +} // namespace Qt3DRender + +QT_END_NAMESPACE + +#endif // OPENGLVERTEXARRAYOBJECT_H diff --git a/src/render/renderers/opengl/renderer/rendercommand.cpp b/src/render/renderers/opengl/renderer/rendercommand.cpp new file mode 100644 index 000000000..e60b17668 --- /dev/null +++ b/src/render/renderers/opengl/renderer/rendercommand.cpp @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "rendercommand_p.h" + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { +namespace Render { + +RenderCommand::RenderCommand() + : m_stateSet(nullptr) + , m_depth(0.0f) + , m_changeCost(0) + , m_type(RenderCommand::Draw) + , m_primitiveCount(0) + , m_primitiveType(QGeometryRenderer::Triangles) + , m_restartIndexValue(-1) + , m_firstInstance(0) + , m_firstVertex(0) + , m_verticesPerPatch(0) + , m_instanceCount(0) + , m_indexOffset(0) + , m_indexAttributeByteOffset(0) + , m_indexAttributeDataType(GL_UNSIGNED_SHORT) + , m_indirectAttributeByteOffset(0) + , m_drawIndexed(false) + , m_drawIndirect(false) + , m_primitiveRestartEnabled(false) + , m_isValid(false) +{ + m_workGroups[0] = 0; + m_workGroups[1] = 0; + m_workGroups[2] = 0; +} + +} // namespace Render +} // namespace Qt3DRender + +QT_END_NAMESPACE diff --git a/src/render/renderers/opengl/renderer/rendercommand_p.h b/src/render/renderers/opengl/renderer/rendercommand_p.h new file mode 100644 index 000000000..67e02d35b --- /dev/null +++ b/src/render/renderers/opengl/renderer/rendercommand_p.h @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB). +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT3DRENDER_RENDER_RENDERCOMMAND_H +#define QT3DRENDER_RENDER_RENDERCOMMAND_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 <qglobal.h> +#include <Qt3DRender/private/shaderparameterpack_p.h> +#include <Qt3DRender/private/handle_types_p.h> +#include <Qt3DRender/qgeometryrenderer.h> +#include <QOpenGLShaderProgram> +#include <QOpenGLTexture> +#include <QMatrix4x4> + +QT_BEGIN_NAMESPACE + +class QOpenGLVertexArrayObject; + +namespace Qt3DRender { + +namespace Render { + +class RenderStateSet; + +class Q_AUTOTEST_EXPORT RenderCommand +{ +public: + RenderCommand(); + + HVao m_vao; // VAO used during the submission step to store all states and VBOs + HShader m_shader; // Shader for given pass and mesh + HMaterial m_material; // Purely used to ease sorting (minimize stage changes, binding changes ....) + ShaderParameterPack m_parameterPack; // Might need to be reworked so as to be able to destroy the + // Texture while submission is happening. + RenderStateSet *m_stateSet; + + HGeometry m_geometry; + HGeometryRenderer m_geometryRenderer; + + HBuffer m_indirectDrawBuffer; // Reference to indirect draw buffer (valid only m_drawIndirect == true) + + // A QAttribute pack might be interesting + // This is a temporary fix in the meantime, to remove the hacked methods in Technique + QVector<int> m_attributes; + + float m_depth; + int m_changeCost; + uint m_shaderDna; + + enum CommandType { + Draw, + Compute + }; + + CommandType m_type; + int m_workGroups[3]; + + // Values filled for draw calls + GLsizei m_primitiveCount; + QGeometryRenderer::PrimitiveType m_primitiveType; + int m_restartIndexValue; + int m_firstInstance; + int m_firstVertex; + int m_verticesPerPatch; + int m_instanceCount; + int m_indexOffset; + uint m_indexAttributeByteOffset; + GLint m_indexAttributeDataType; + uint m_indirectAttributeByteOffset; + bool m_drawIndexed; + bool m_drawIndirect; + bool m_primitiveRestartEnabled; + bool m_isValid; +}; + +} // namespace Render + +} // namespace Qt3DRender + +QT_END_NAMESPACE + +#endif // QT3DRENDER_RENDER_RENDERCOMMAND_H diff --git a/src/render/renderers/opengl/renderer/renderer.cpp b/src/render/renderers/opengl/renderer/renderer.cpp new file mode 100644 index 000000000..2611fb6cc --- /dev/null +++ b/src/render/renderers/opengl/renderer/renderer.cpp @@ -0,0 +1,2067 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB). +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "renderer_p.h" + +#include <Qt3DCore/qentity.h> + +#include <Qt3DRender/qmaterial.h> +#include <Qt3DRender/qmesh.h> +#include <Qt3DRender/qrenderpass.h> +#include <Qt3DRender/qshaderprogram.h> +#include <Qt3DRender/qtechnique.h> +#include <Qt3DRender/qrenderaspect.h> +#include <Qt3DRender/qeffect.h> + +#include <Qt3DRender/private/qsceneimporter_p.h> +#include <Qt3DRender/private/renderstates_p.h> +#include <Qt3DRender/private/cameraselectornode_p.h> +#include <Qt3DRender/private/framegraphvisitor_p.h> +#include <Qt3DRender/private/graphicscontext_p.h> +#include <Qt3DRender/private/cameralens_p.h> +#include <Qt3DRender/private/rendercommand_p.h> +#include <Qt3DRender/private/entity_p.h> +#include <Qt3DRender/private/renderlogging_p.h> +#include <Qt3DRender/private/material_p.h> +#include <Qt3DRender/private/renderpassfilternode_p.h> +#include <Qt3DRender/private/renderqueue_p.h> +#include <Qt3DRender/private/shader_p.h> +#include <Qt3DRender/private/buffer_p.h> +#include <Qt3DRender/private/glbuffer_p.h> +#include <Qt3DRender/private/renderstateset_p.h> +#include <Qt3DRender/private/technique_p.h> +#include <Qt3DRender/private/renderthread_p.h> +#include <Qt3DRender/private/renderview_p.h> +#include <Qt3DRender/private/scenemanager_p.h> +#include <Qt3DRender/private/techniquefilternode_p.h> +#include <Qt3DRender/private/viewportnode_p.h> +#include <Qt3DRender/private/vsyncframeadvanceservice_p.h> +#include <Qt3DRender/private/pickeventfilter_p.h> +#include <Qt3DRender/private/managers_p.h> +#include <Qt3DRender/private/buffermanager_p.h> +#include <Qt3DRender/private/nodemanagers_p.h> +#include <Qt3DRender/private/gltexturemanager_p.h> +#include <Qt3DRender/private/gltexture_p.h> +#include <Qt3DRender/private/geometryrenderermanager_p.h> +#include <Qt3DRender/private/techniquemanager_p.h> +#include <Qt3DRender/private/openglvertexarrayobject_p.h> +#include <Qt3DRender/private/platformsurfacefilter_p.h> +#include <Qt3DRender/private/loadbufferjob_p.h> +#include <Qt3DRender/private/rendercapture_p.h> +#include <Qt3DRender/private/updatelevelofdetailjob_p.h> +#include <Qt3DRender/private/buffercapture_p.h> +#include <Qt3DRender/private/offscreensurfacehelper_p.h> +#include <Qt3DRender/private/renderviewbuilder_p.h> +#include <Qt3DRender/private/commandthread_p.h> +#include <Qt3DRender/private/glcommands_p.h> + +#include <Qt3DRender/qcameralens.h> +#include <Qt3DCore/private/qeventfilterservice_p.h> +#include <Qt3DCore/private/qabstractaspectjobmanager_p.h> +#include <Qt3DCore/private/qnodecreatedchangegenerator_p.h> + +#if QT_CONFIG(qt3d_profile_jobs) +#include <Qt3DCore/private/aspectcommanddebugger_p.h> +#endif + +#include <QStack> +#include <QOffscreenSurface> +#include <QSurface> +#include <QElapsedTimer> +#include <QLibraryInfo> +#include <QPluginLoader> +#include <QDir> +#include <QUrl> +#include <QOffscreenSurface> +#include <QWindow> + +#include <QtGui/private/qopenglcontext_p.h> + +// For Debug purposes only +#include <QThread> + + +#if QT_CONFIG(qt3d_profile_jobs) +#include <Qt3DCore/private/qthreadpooler_p.h> +#include <Qt3DRender/private/job_common_p.h> +#include <Qt3DRender/private/commandexecuter_p.h> +#endif + +#include <Qt3DRender/private/frameprofiler_p.h> + +QT_BEGIN_NAMESPACE + +using namespace Qt3DCore; + +namespace Qt3DRender { +namespace Render { +/*! + \internal + + Renderer shutdown procedure: + + Since the renderer relies on the surface and OpenGLContext to perform its cleanup, + it is shutdown when the surface is set to nullptr + + When the surface is set to nullptr this will request the RenderThread to terminate + and will prevent createRenderBinJobs from returning a set of jobs as there is nothing + more to be rendered. + + In turn, this will call shutdown which will make the OpenGL context current one last time + to allow cleanups requiring a call to QOpenGLContext::currentContext to execute properly. + At the end of that function, the GraphicsContext is set to null. + + At this point though, the QAspectThread is still running its event loop and will only stop + a short while after. + */ + +Renderer::Renderer(QRenderAspect::RenderType type) + : m_services(nullptr) + , m_nodesManager(nullptr) + , m_renderSceneRoot(nullptr) + , m_defaultRenderStateSet(nullptr) + , m_submissionContext(nullptr) + , m_renderQueue(new RenderQueue()) + , m_renderThread(type == QRenderAspect::Threaded ? new RenderThread(this) : nullptr) + , m_commandThread(new CommandThread(this)) + , m_vsyncFrameAdvanceService(new VSyncFrameAdvanceService(m_renderThread != nullptr)) + , m_waitForInitializationToBeCompleted(0) + , m_pickEventFilter(new PickEventFilter()) + , m_exposed(0) + , m_lastFrameCorrect(0) + , m_glContext(nullptr) + , m_shareContext(nullptr) + , m_shaderCache(new ShaderCache()) + , m_pickBoundingVolumeJob(PickBoundingVolumeJobPtr::create()) + , m_rayCastingJob(RayCastingJobPtr::create()) + , m_time(0) + , m_settings(nullptr) + , m_updateShaderDataTransformJob(Render::UpdateShaderDataTransformJobPtr::create()) + , m_cleanupJob(Render::FrameCleanupJobPtr::create()) + , m_worldTransformJob(Render::UpdateWorldTransformJobPtr::create()) + , m_expandBoundingVolumeJob(Render::ExpandBoundingVolumeJobPtr::create()) + , m_calculateBoundingVolumeJob(Render::CalculateBoundingVolumeJobPtr::create()) + , m_updateWorldBoundingVolumeJob(Render::UpdateWorldBoundingVolumeJobPtr::create()) + , m_updateTreeEnabledJob(Render::UpdateTreeEnabledJobPtr::create()) + , m_sendRenderCaptureJob(Render::SendRenderCaptureJobPtr::create(this)) + , m_sendBufferCaptureJob(Render::SendBufferCaptureJobPtr::create()) + , m_updateSkinningPaletteJob(Render::UpdateSkinningPaletteJobPtr::create()) + , m_updateLevelOfDetailJob(Render::UpdateLevelOfDetailJobPtr::create()) + , m_updateMeshTriangleListJob(Render::UpdateMeshTriangleListJobPtr::create()) + , m_filterCompatibleTechniqueJob(Render::FilterCompatibleTechniqueJobPtr::create()) + , m_bufferGathererJob(Render::GenericLambdaJobPtr<std::function<void ()>>::create([this] { lookForDirtyBuffers(); }, JobTypes::DirtyBufferGathering)) + , m_vaoGathererJob(Render::GenericLambdaJobPtr<std::function<void ()>>::create([this] { lookForAbandonedVaos(); }, JobTypes::DirtyVaoGathering)) + , m_textureGathererJob(Render::GenericLambdaJobPtr<std::function<void ()>>::create([this] { lookForDirtyTextures(); }, JobTypes::DirtyTextureGathering)) + , m_introspectShaderJob(Render::GenericLambdaJobPtr<std::function<void ()>>::create([this] { reloadDirtyShaders(); }, JobTypes::DirtyShaderGathering)) + , m_syncTextureLoadingJob(Render::GenericLambdaJobPtr<std::function<void ()>>::create([] {}, JobTypes::SyncTextureLoading)) + , m_ownedContext(false) + , m_offscreenHelper(nullptr) + #if QT_CONFIG(qt3d_profile_jobs) + , m_commandExecuter(new Qt3DRender::Debug::CommandExecuter(this)) + #endif +{ + // Set renderer as running - it will wait in the context of the + // RenderThread for RenderViews to be submitted + m_running.fetchAndStoreOrdered(1); + if (m_renderThread) + m_renderThread->waitForStart(); + + // Create jobs to update transforms and bounding volumes + // We can only update bounding volumes once all world transforms are known + m_updateWorldBoundingVolumeJob->addDependency(m_worldTransformJob); + m_updateWorldBoundingVolumeJob->addDependency(m_calculateBoundingVolumeJob); + m_expandBoundingVolumeJob->addDependency(m_updateWorldBoundingVolumeJob); + m_updateShaderDataTransformJob->addDependency(m_worldTransformJob); + m_pickBoundingVolumeJob->addDependency(m_expandBoundingVolumeJob); + // m_calculateBoundingVolumeJob's dependency on m_updateTreeEnabledJob is set in renderBinJobs + + // Dirty texture gathering depends on m_syncTextureLoadingJob + // m_syncTextureLoadingJob will depend on the texture loading jobs + m_textureGathererJob->addDependency(m_syncTextureLoadingJob); + + // Ensures all skeletons are loaded before we try to update them + m_updateSkinningPaletteJob->addDependency(m_syncTextureLoadingJob); + + // All world stuff depends on the RenderEntity's localBoundingVolume + m_updateLevelOfDetailJob->addDependency(m_updateMeshTriangleListJob); + m_pickBoundingVolumeJob->addDependency(m_updateMeshTriangleListJob); + m_rayCastingJob->addDependency(m_updateMeshTriangleListJob); + + m_introspectShaderJob->addDependency(m_filterCompatibleTechniqueJob); + + m_filterCompatibleTechniqueJob->setRenderer(this); + + m_defaultRenderStateSet = new RenderStateSet; + m_defaultRenderStateSet->addState(RenderStateSet::createState<DepthTest>(GL_LESS)); + m_defaultRenderStateSet->addState(RenderStateSet::createState<CullFace>(GL_BACK)); + m_defaultRenderStateSet->addState(RenderStateSet::createState<ColorMask>(true, true, true, true)); +} + +Renderer::~Renderer() +{ + // If using a threaded rendering approach, tell the thread to exit + // and wait for it to be done + m_running.fetchAndStoreOrdered(0); + if (m_renderThread) + m_renderThread->wait(); + + delete m_renderQueue; + delete m_defaultRenderStateSet; + delete m_shaderCache; + + if (!m_ownedContext) + QObject::disconnect(m_contextConnection); +} + +void Renderer::dumpInfo() const +{ + qDebug() << Q_FUNC_INFO << "t =" << m_time; + + const ShaderManager *shaderManager = m_nodesManager->shaderManager(); + qDebug() << "=== Shader Manager ==="; + qDebug() << *shaderManager; + + const TextureManager *textureManager = m_nodesManager->textureManager(); + qDebug() << "=== Texture Manager ==="; + qDebug() << *textureManager; + + const TextureImageManager *textureImageManager = m_nodesManager->textureImageManager(); + qDebug() << "=== Texture Image Manager ==="; + qDebug() << *textureImageManager; +} + +qint64 Renderer::time() const +{ + return m_time; +} + +void Renderer::setTime(qint64 time) +{ + m_time = time; +} + +void Renderer::setNodeManagers(NodeManagers *managers) +{ + m_nodesManager = managers; + + m_updateShaderDataTransformJob->setManagers(m_nodesManager); + m_cleanupJob->setManagers(m_nodesManager); + m_calculateBoundingVolumeJob->setManagers(m_nodesManager); + m_pickBoundingVolumeJob->setManagers(m_nodesManager); + m_rayCastingJob->setManagers(m_nodesManager); + m_updateWorldBoundingVolumeJob->setManager(m_nodesManager->renderNodesManager()); + m_sendRenderCaptureJob->setManagers(m_nodesManager); + m_sendBufferCaptureJob->setManagers(m_nodesManager); + m_updateLevelOfDetailJob->setManagers(m_nodesManager); + m_updateSkinningPaletteJob->setManagers(m_nodesManager); + m_updateMeshTriangleListJob->setManagers(m_nodesManager); + m_filterCompatibleTechniqueJob->setManager(m_nodesManager->techniqueManager()); +} + +void Renderer::setServices(QServiceLocator *services) +{ + m_services = services; + + m_nodesManager->sceneManager()->setDownloadService(m_services->downloadHelperService()); +} + +NodeManagers *Renderer::nodeManagers() const +{ + return m_nodesManager; +} + +/*! + \internal + + Return context which can be used to share resources safely + with qt3d main render context. +*/ +QOpenGLContext *Renderer::shareContext() const +{ + QMutexLocker lock(&m_shareContextMutex); + return m_shareContext ? m_shareContext + : (m_submissionContext->openGLContext() + ? m_submissionContext->openGLContext()->shareContext() + : nullptr); +} + +// Executed in the command thread +void Renderer::loadShader(Shader *shader) const +{ + Profiling::GLTimeRecorder recorder(Profiling::ShaderUpload); + LoadShaderCommand cmd(shader); + m_commandThread->executeCommand(&cmd); +} + +void Renderer::setOpenGLContext(QOpenGLContext *context) +{ + m_glContext = context; +} + +// Called in RenderThread context by the run method of RenderThread +// RenderThread has locked the mutex already and unlocks it when this +// method termintates +void Renderer::initialize() +{ + m_submissionContext.reset(new SubmissionContext); + m_submissionContext->setRenderer(this); + + QOpenGLContext* ctx = m_glContext; + + { + QMutexLocker lock(&m_shareContextMutex); + // If we are using our own context (not provided by QtQuick), + // we need to create it + if (!m_glContext) { + ctx = new QOpenGLContext; + ctx->setShareContext(qt_gl_global_share_context()); + + // TO DO: Shouldn't we use the highest context available and trust + // QOpenGLContext to fall back on the best lowest supported ? + const QByteArray debugLoggingMode = qgetenv("QT3DRENDER_DEBUG_LOGGING"); + + if (!debugLoggingMode.isEmpty()) { + QSurfaceFormat sf = ctx->format(); + sf.setOption(QSurfaceFormat::DebugContext); + ctx->setFormat(sf); + } + + // Create OpenGL context + if (ctx->create()) + qCDebug(Backend) << "OpenGL context created with actual format" << ctx->format(); + else + qCWarning(Backend) << Q_FUNC_INFO << "OpenGL context creation failed"; + m_ownedContext = true; + } else { + // Context is not owned by us, so we need to know if it gets destroyed + m_contextConnection = QObject::connect(m_glContext, &QOpenGLContext::aboutToBeDestroyed, + [this] { releaseGraphicsResources(); }); + } + + if (!ctx->shareContext()) { + m_shareContext = new QOpenGLContext; + m_shareContext->setFormat(ctx->format()); + m_shareContext->setShareContext(ctx); + m_shareContext->create(); + } + + // Set shader cache on submission context and command thread + m_submissionContext->setShaderCache(m_shaderCache); + m_commandThread->setShaderCache(m_shaderCache); + + // Note: we don't have a surface at this point + // The context will be made current later on (at render time) + m_submissionContext->setOpenGLContext(ctx); + + // Store the format used by the context and queue up creating an + // offscreen surface in the main thread so that it is available + // for use when we want to shutdown the renderer. We need to create + // the offscreen surface on the main thread because on some platforms + // (MS Windows), an offscreen surface is just a hidden QWindow. + m_format = ctx->format(); + QMetaObject::invokeMethod(m_offscreenHelper, "createOffscreenSurface"); + + // Initialize command thread (uses the offscreen surface to make its own ctx current) + m_commandThread->initialize(ctx, m_offscreenHelper); + // Note: the offscreen surface is also used at shutdown time to release resources + // of the submission gl context (when the window is already gone). + // By that time (in releaseGraphicResources), the commandThread has been destroyed + // and the offscreenSurface can be reused + } + + // Awake setScenegraphRoot in case it was waiting + m_waitForInitializationToBeCompleted.release(1); + // Allow the aspect manager to proceed + m_vsyncFrameAdvanceService->proceedToNextFrame(); +} + +/*! + * \internal + * + * Signals for the renderer to stop rendering. If a threaded renderer is in use, + * the render thread will call releaseGraphicsResources() just before the thread exits. + * If rendering synchronously, this function will call releaseGraphicsResources(). + */ +void Renderer::shutdown() +{ + qCDebug(Backend) << Q_FUNC_INFO << "Requesting renderer shutdown"; + m_running.store(0); + + // We delete any renderqueue that we may not have had time to render + // before the surface was destroyed + qDeleteAll(m_renderQueue->nextFrameQueue()); + m_renderQueue->reset(); + + m_commandThread->shutdown(); + + if (!m_renderThread) { + releaseGraphicsResources(); + } else { + // Wake up the render thread in case it is waiting for some renderviews + // to be ready. The isReadyToSubmit() function checks for a shutdown + // having been requested. + m_submitRenderViewsSemaphore.release(1); + m_renderThread->wait(); + } +} + +/*! + \internal + + When using a threaded renderer this function is called in the context of the + RenderThread to do any shutdown and cleanup that needs to be performed in the + thread where the OpenGL context lives. + + When using Scene3D or anything that provides a custom QOpenGLContext (not + owned by Qt3D) this function is called whenever the signal + QOpenGLContext::aboutToBeDestroyed is emitted. In that case this function + is called in the context of the emitter's thread. +*/ +void Renderer::releaseGraphicsResources() +{ + // We may get called twice when running inside of a Scene3D. Once when Qt Quick + // wants to shutdown, and again when the render aspect gets unregistered. So + // check that we haven't already cleaned up before going any further. + if (!m_submissionContext) + return; + + // Try to temporarily make the context current so we can free up any resources + QMutexLocker locker(&m_offscreenSurfaceMutex); + QOffscreenSurface *offscreenSurface = m_offscreenHelper->offscreenSurface(); + if (!offscreenSurface) { + qWarning() << "Failed to make context current: OpenGL resources will not be destroyed"; + return; + } + + QOpenGLContext *context = m_submissionContext->openGLContext(); + Q_ASSERT(context); + if (context->makeCurrent(offscreenSurface)) { + + // Clean up the graphics context and any resources + const QVector<GLTexture*> activeTextures = m_nodesManager->glTextureManager()->activeResources(); + for (GLTexture *tex : activeTextures) + tex->destroyGLTexture(); + + // Do the same thing with buffers + const QVector<HGLBuffer> activeBuffers = m_nodesManager->glBufferManager()->activeHandles(); + for (const HGLBuffer &bufferHandle : activeBuffers) { + GLBuffer *buffer = m_nodesManager->glBufferManager()->data(bufferHandle); + buffer->destroy(m_submissionContext.data()); + } + + // Do the same thing with VAOs + const QVector<HVao> activeVaos = m_nodesManager->vaoManager()->activeHandles(); + for (const HVao &vaoHandle : activeVaos) { + OpenGLVertexArrayObject *vao = m_nodesManager->vaoManager()->data(vaoHandle); + vao->destroy(); + } + + context->doneCurrent(); + } else { + qWarning() << "Failed to make context current: OpenGL resources will not be destroyed"; + } + + if (m_ownedContext) + delete context; + if (m_shareContext) + delete m_shareContext; + + m_submissionContext.reset(nullptr); + qCDebug(Backend) << Q_FUNC_INFO << "Renderer properly shutdown"; +} + +void Renderer::setSurfaceExposed(bool exposed) +{ + qCDebug(Backend) << "Window exposed: " << exposed; + m_exposed.fetchAndStoreOrdered(exposed); +} + +Render::FrameGraphNode *Renderer::frameGraphRoot() const +{ + Q_ASSERT(m_settings); + if (m_nodesManager && m_nodesManager->frameGraphManager() && m_settings) + return m_nodesManager->frameGraphManager()->lookupNode(m_settings->activeFrameGraphID()); + return nullptr; +} + +// QAspectThread context +// Order of execution : +// 1) RenderThread is created -> release 1 of m_waitForInitializationToBeCompleted when started +// 2) setSceneRoot waits to acquire initialization +// 3) submitRenderView -> check for surface +// -> make surface current + create proper glHelper if needed +void Renderer::setSceneRoot(QBackendNodeFactory *factory, Entity *sgRoot) +{ + Q_ASSERT(sgRoot); + Q_UNUSED(factory); + + // If initialization hasn't been completed we must wait + m_waitForInitializationToBeCompleted.acquire(); + + m_renderSceneRoot = sgRoot; + if (!m_renderSceneRoot) + qCWarning(Backend) << "Failed to build render scene"; + m_renderSceneRoot->dump(); + qCDebug(Backend) << Q_FUNC_INFO << "DUMPING SCENE"; + + // Set the scene root on the jobs + m_worldTransformJob->setRoot(m_renderSceneRoot); + m_expandBoundingVolumeJob->setRoot(m_renderSceneRoot); + m_calculateBoundingVolumeJob->setRoot(m_renderSceneRoot); + m_cleanupJob->setRoot(m_renderSceneRoot); + m_pickBoundingVolumeJob->setRoot(m_renderSceneRoot); + m_rayCastingJob->setRoot(m_renderSceneRoot); + m_updateLevelOfDetailJob->setRoot(m_renderSceneRoot); + m_updateSkinningPaletteJob->setRoot(m_renderSceneRoot); + m_updateTreeEnabledJob->setRoot(m_renderSceneRoot); + + // Set all flags to dirty + m_dirtyBits.marked |= AbstractRenderer::AllDirty; +} + +void Renderer::registerEventFilter(QEventFilterService *service) +{ + qCDebug(Backend) << Q_FUNC_INFO << QThread::currentThread(); + service->registerEventFilter(m_pickEventFilter.data(), 1024); +} + +void Renderer::setSettings(RenderSettings *settings) +{ + m_settings = settings; +} + +RenderSettings *Renderer::settings() const +{ + return m_settings; +} + +void Renderer::render() +{ + // Traversing the framegraph tree from root to lead node + // Allows us to define the rendering set up + // Camera, RenderTarget ... + + // Utimately the renderer should be a framework + // For the processing of the list of renderviews + + // Matrice update, bounding volumes computation ... + // Should be jobs + + // namespace Qt3DCore has 2 distincts node trees + // One scene description + // One framegraph description + + while (m_running.load() > 0) { + doRender(); + // TO DO: Restore windows exposed detection + // Probably needs to happens some place else though + } +} + +void Renderer::doRender(bool scene3dBlocking) +{ + Renderer::ViewSubmissionResultData submissionData; + bool hasCleanedQueueAndProceeded = false; + bool preprocessingComplete = false; + bool beganDrawing = false; + const bool canSubmit = isReadyToSubmit(); + + // Lock the mutex to protect access to the renderQueue while we look for its state + QMutexLocker locker(m_renderQueue->mutex()); + bool queueIsComplete = m_renderQueue->isFrameQueueComplete(); + const bool queueIsEmpty = m_renderQueue->targetRenderViewCount() == 0; + + // Scene3D Blocking Mode + if (scene3dBlocking && !queueIsComplete && !queueIsEmpty) { + int i = 0; + // We wait at most 10ms to avoid a case we could never recover from + while (!queueIsComplete && i++ < 10) { + QThread::msleep(1); + qCDebug(Backend) << Q_FUNC_INFO << "Waiting for ready queue (try:" << i << "/ 10)"; + locker.unlock(); + queueIsComplete = m_renderQueue->isFrameQueueComplete(); + locker.relock(); + } + } + + // When using synchronous rendering (QtQuick) + // We are not sure that the frame queue is actually complete + // Since a call to render may not be synched with the completions + // of the RenderViewJobs + // In such a case we return early, waiting for a next call with + // the frame queue complete at this point + + // RenderQueue is complete (but that means it may be of size 0) + if (canSubmit && (queueIsComplete && !queueIsEmpty)) { + const QVector<Render::RenderView *> renderViews = m_renderQueue->nextFrameQueue(); + +#if QT_CONFIG(qt3d_profile_jobs) + // Save start of frame + JobRunStats submissionStatsPart1; + JobRunStats submissionStatsPart2; + submissionStatsPart1.jobId.typeAndInstance[0] = JobTypes::FrameSubmissionPart1; + submissionStatsPart1.jobId.typeAndInstance[1] = 0; + submissionStatsPart1.threadId = reinterpret_cast<quint64>(QThread::currentThreadId()); + submissionStatsPart1.startTime = QThreadPooler::m_jobsStatTimer.nsecsElapsed(); + submissionStatsPart2.jobId.typeAndInstance[0] = JobTypes::FrameSubmissionPart2; + submissionStatsPart2.jobId.typeAndInstance[1] = 0; + submissionStatsPart2.threadId = reinterpret_cast<quint64>(QThread::currentThreadId()); +#endif + + if (canRender()) { + { // Scoped to destroy surfaceLock + QSurface *surface = nullptr; + for (const Render::RenderView *rv: renderViews) { + surface = rv->surface(); + if (surface) + break; + } + + SurfaceLocker surfaceLock(surface); + const bool surfaceIsValid = (surface && surfaceLock.isSurfaceValid()); + if (surfaceIsValid) { + // Reset state for each draw if we don't have complete control of the context + if (!m_ownedContext) + m_submissionContext->setCurrentStateSet(nullptr); + beganDrawing = m_submissionContext->beginDrawing(surface); + if (beganDrawing) { + // 1) Execute commands for buffer uploads, texture updates, shader loading first + updateGLResources(); + // 2) Update VAO and copy data into commands to allow concurrent submission + prepareCommandsSubmission(renderViews); + preprocessingComplete = true; + } + } + } + // 2) Proceed to next frame and start preparing frame n + 1 + m_renderQueue->reset(); + locker.unlock(); // Done protecting RenderQueue + m_vsyncFrameAdvanceService->proceedToNextFrame(); + hasCleanedQueueAndProceeded = true; + +#if QT_CONFIG(qt3d_profile_jobs) + if (preprocessingComplete) { + submissionStatsPart2.startTime = QThreadPooler::m_jobsStatTimer.nsecsElapsed(); + submissionStatsPart1.endTime = submissionStatsPart2.startTime; + } +#endif + // Only try to submit the RenderViews if the preprocessing was successful + // This part of the submission is happening in parallel to the RV building for the next frame + if (preprocessingComplete) { + // 3) Submit the render commands for frame n (making sure we never reference something that could be changing) + // Render using current device state and renderer configuration + submissionData = submitRenderViews(renderViews); + + // Perform any required cleanup of the Graphics resources (Buffers deleted, Shader deleted...) + cleanGraphicsResources(); + } + } + +#if QT_CONFIG(qt3d_profile_jobs) + // Execute the pending shell commands + m_commandExecuter->performAsynchronousCommandExecution(renderViews); +#endif + + // Delete all the RenderViews which will clear the allocators + // that were used for their allocation + qDeleteAll(renderViews); + +#if QT_CONFIG(qt3d_profile_jobs) + if (preprocessingComplete) { + // Save submission elapsed time + submissionStatsPart2.endTime = QThreadPooler::m_jobsStatTimer.nsecsElapsed(); + // Note this is safe since proceedToNextFrame is the one going to trigger + // the write to the file, and this is performed after this step + Qt3DCore::QThreadPooler::addSubmissionLogStatsEntry(submissionStatsPart1); + Qt3DCore::QThreadPooler::addSubmissionLogStatsEntry(submissionStatsPart2); + Profiling::GLTimeRecorder::writeResults(); + } +#endif + } + + // Only reset renderQueue and proceed to next frame if the submission + // succeeded or if we are using a render thread and that is wasn't performed + // already + + // If hasCleanedQueueAndProceeded isn't true this implies that something went wrong + // with the rendering and/or the renderqueue is incomplete from some reason + // (in the case of scene3d the render jobs may be taking too long ....) + // or alternatively it could be complete but empty (RenderQueue of size 0) + if (!hasCleanedQueueAndProceeded && + (m_renderThread || queueIsComplete || queueIsEmpty)) { + // RenderQueue was full but something bad happened when + // trying to render it and therefore proceedToNextFrame was not called + // Note: in this case the renderQueue mutex is still locked + + // Reset the m_renderQueue so that we won't try to render + // with a queue used by a previous frame with corrupted content + // if the current queue was correctly submitted + m_renderQueue->reset(); + + // We allow the RenderTickClock service to proceed to the next frame + // In turn this will allow the aspect manager to request a new set of jobs + // to be performed for each aspect + m_vsyncFrameAdvanceService->proceedToNextFrame(); + } + + // Perform the last swapBuffers calls after the proceedToNextFrame + // as this allows us to gain a bit of time for the preparation of the + // next frame + // Finish up with last surface used in the list of RenderViews + if (beganDrawing) { + SurfaceLocker surfaceLock(submissionData.surface); + // Finish up with last surface used in the list of RenderViews + m_submissionContext->endDrawing(submissionData.lastBoundFBOId == m_submissionContext->defaultFBO() && surfaceLock.isSurfaceValid()); + } +} + +// Called by RenderViewJobs +// When the frameQueue is complete and we are using a renderThread +// we allow the render thread to proceed +void Renderer::enqueueRenderView(Render::RenderView *renderView, int submitOrder) +{ + QMutexLocker locker(m_renderQueue->mutex()); // Prevent out of order execution + // We cannot use a lock free primitive here because: + // - QVector is not thread safe + // - Even if the insert is made correctly, the isFrameComplete call + // could be invalid since depending on the order of execution + // the counter could be complete but the renderview not yet added to the + // buffer depending on whichever order the cpu decides to process this + const bool isQueueComplete = m_renderQueue->queueRenderView(renderView, submitOrder); + locker.unlock(); // We're done protecting the queue at this point + if (isQueueComplete) { + if (m_renderThread && m_running.load()) + Q_ASSERT(m_submitRenderViewsSemaphore.available() == 0); + m_submitRenderViewsSemaphore.release(1); + } +} + +bool Renderer::canRender() const +{ + // Make sure that we've not been told to terminate + if (m_renderThread && !m_running.load()) { + qCDebug(Rendering) << "RenderThread termination requested whilst waiting"; + return false; + } + + // TO DO: Check if all surfaces have been destroyed... + // It may be better if the last window to be closed trigger a call to shutdown + // Rather than having checks for the surface everywhere + + return true; +} + +bool Renderer::isReadyToSubmit() +{ + // If we are using a render thread, make sure that + // we've been told to render before rendering + if (m_renderThread) { // Prevent ouf of order execution + m_submitRenderViewsSemaphore.acquire(1); + + // Check if shutdown has been requested + if (m_running.load() == 0) + return false; + + // When using Thread rendering, the semaphore should only + // be released when the frame queue is complete and there's + // something to render + // The case of shutdown should have been handled just before + Q_ASSERT(m_renderQueue->isFrameQueueComplete()); + } + return true; +} + +// Main thread +QVariant Renderer::executeCommand(const QStringList &args) +{ +#if QT_CONFIG(qt3d_profile_jobs) + return m_commandExecuter->executeCommand(args); +#else + Q_UNUSED(args); +#endif + return QVariant(); +} + +/*! + \internal + Called in the context of the aspect thread from QRenderAspect::onRegistered +*/ +void Renderer::setOffscreenSurfaceHelper(OffscreenSurfaceHelper *helper) +{ + QMutexLocker locker(&m_offscreenSurfaceMutex); + m_offscreenHelper = helper; +} + +QSurfaceFormat Renderer::format() +{ + return m_format; +} + +// When this function is called, we must not be processing the commands for frame n+1 +void Renderer::prepareCommandsSubmission(const QVector<RenderView *> &renderViews) +{ + OpenGLVertexArrayObject *vao = nullptr; + QHash<HVao, bool> updatedTable; + + for (RenderView *rv: renderViews) { + const QVector<RenderCommand *> commands = rv->commands(); + for (RenderCommand *command : commands) { + // Update/Create VAO + if (command->m_type == RenderCommand::Draw) { + Geometry *rGeometry = m_nodesManager->data<Geometry, GeometryManager>(command->m_geometry); + GeometryRenderer *rGeometryRenderer = m_nodesManager->data<GeometryRenderer, GeometryRendererManager>(command->m_geometryRenderer); + Shader *shader = m_nodesManager->data<Shader, ShaderManager>(command->m_shader); + + // We should never have inserted a command for which these are null + // in the first place + Q_ASSERT(rGeometry && rGeometryRenderer && shader); + + // The VAO should be created only once for a QGeometry and a ShaderProgram + // Manager should have a VAO Manager that are indexed by QMeshData and Shader + // RenderCommand should have a handle to the corresponding VAO for the Mesh and Shader + HVao vaoHandle; + + // Create VAO or return already created instance associated with command shader/geometry + // (VAO is emulated if not supported) + createOrUpdateVAO(command, &vaoHandle, &vao); + command->m_vao = vaoHandle; + + // Avoids redoing the same thing for the same VAO + if (!updatedTable.contains(vaoHandle)) { + updatedTable.insert(vaoHandle, true); + + // Do we have any attributes that are dirty ? + const bool requiresPartialVAOUpdate = requiresVAOAttributeUpdate(rGeometry, command); + + // If true, we need to reupload all attributes to set the VAO + // Otherwise only dirty attributes will be updates + const bool requiresFullVAOUpdate = (!vao->isSpecified()) || (rGeometry->isDirty() || rGeometryRenderer->isDirty()); + + // Append dirty Geometry to temporary vector + // so that its dirtiness can be unset later + if (rGeometry->isDirty()) + m_dirtyGeometry.push_back(rGeometry); + + if (!command->m_attributes.isEmpty() && (requiresFullVAOUpdate || requiresPartialVAOUpdate)) { + Profiling::GLTimeRecorder recorder(Profiling::VAOUpload); + // Activate shader + m_submissionContext->activateShader(shader->dna()); + // Bind VAO + vao->bind(); + // Update or set Attributes and Buffers for the given rGeometry and Command + // Note: this fills m_dirtyAttributes as well + if (updateVAOWithAttributes(rGeometry, command, shader, requiresFullVAOUpdate)) + vao->setSpecified(true); + } + } + + // Unset dirtiness on rGeometryRenderer only + // The rGeometry may be shared by several rGeometryRenderer + // so we cannot unset its dirtiness at this point + if (rGeometryRenderer->isDirty()) + rGeometryRenderer->unsetDirty(); + + // Prepare the ShaderParameterPack based on the active uniforms of the shader + shader->prepareUniforms(command->m_parameterPack); + + // TO DO: The step below could be performed by the RenderCommand builder job + { // Scoped to show extent + command->m_isValid = !command->m_attributes.empty(); + if (!command->m_isValid) + continue; + + // Update the draw command with what's going to be needed for the drawing + uint primitiveCount = rGeometryRenderer->vertexCount(); + uint estimatedCount = 0; + Attribute *indexAttribute = nullptr; + Attribute *indirectAttribute = nullptr; + + const QVector<Qt3DCore::QNodeId> attributeIds = rGeometry->attributes(); + for (Qt3DCore::QNodeId attributeId : attributeIds) { + Attribute *attribute = m_nodesManager->attributeManager()->lookupResource(attributeId); + switch (attribute->attributeType()) { + case QAttribute::IndexAttribute: + indexAttribute = attribute; + break; + case QAttribute::DrawIndirectAttribute: + indirectAttribute = attribute; + break; + case QAttribute::VertexAttribute: { + if (command->m_attributes.contains(attribute->nameId())) + estimatedCount = qMax(attribute->count(), estimatedCount); + break; + } + default: + Q_UNREACHABLE(); + break; + } + } + + command->m_drawIndexed = (indexAttribute != nullptr); + command->m_drawIndirect = (indirectAttribute != nullptr); + + // Update the draw command with all the information required for the drawing + if (command->m_drawIndexed) { + command->m_indexAttributeDataType = GraphicsContext::glDataTypeFromAttributeDataType(indexAttribute->vertexBaseType()); + command->m_indexAttributeByteOffset = indexAttribute->byteOffset() + rGeometryRenderer->indexBufferByteOffset(); + } + + // Note: we only care about the primitiveCount when using direct draw calls + // For indirect draw calls it is assumed the buffer was properly set already + if (command->m_drawIndirect) { + command->m_indirectAttributeByteOffset = indirectAttribute->byteOffset(); + command->m_indirectDrawBuffer = m_nodesManager->bufferManager()->lookupHandle(indirectAttribute->bufferId()); + } else { + // Use the count specified by the GeometryRender + // If not specify use the indexAttribute count if present + // Otherwise tries to use the count from the attribute with the highest count + if (primitiveCount == 0) { + if (indexAttribute) + primitiveCount = indexAttribute->count(); + else + primitiveCount = estimatedCount; + } + } + + command->m_primitiveCount = primitiveCount; + command->m_primitiveType = rGeometryRenderer->primitiveType(); + command->m_primitiveRestartEnabled = rGeometryRenderer->primitiveRestartEnabled(); + command->m_restartIndexValue = rGeometryRenderer->restartIndexValue(); + command->m_firstInstance = rGeometryRenderer->firstInstance(); + command->m_instanceCount = rGeometryRenderer->instanceCount(); + command->m_firstVertex = rGeometryRenderer->firstVertex(); + command->m_indexOffset = rGeometryRenderer->indexOffset(); + command->m_verticesPerPatch = rGeometryRenderer->verticesPerPatch(); + } // scope + } else if (command->m_type == RenderCommand::Compute) { + Shader *shader = m_nodesManager->data<Shader, ShaderManager>(command->m_shader); + Q_ASSERT(shader); + + // Prepare the ShaderParameterPack based on the active uniforms of the shader + shader->prepareUniforms(command->m_parameterPack); + } + } + } + + // Make sure we leave nothing bound + if (vao) + vao->release(); + + // Unset dirtiness on Geometry and Attributes + // Note: we cannot do it in the loop above as we want to be sure that all + // the VAO which reference the geometry/attributes are properly updated + for (Attribute *attribute : qAsConst(m_dirtyAttributes)) + attribute->unsetDirty(); + m_dirtyAttributes.clear(); + + for (Geometry *geometry : qAsConst(m_dirtyGeometry)) + geometry->unsetDirty(); + m_dirtyGeometry.clear(); +} + +// Executed in a job +void Renderer::lookForAbandonedVaos() +{ + const QVector<HVao> activeVaos = m_nodesManager->vaoManager()->activeHandles(); + for (const HVao &handle : activeVaos) { + OpenGLVertexArrayObject *vao = m_nodesManager->vaoManager()->data(handle); + + // Make sure to only mark VAOs for deletion that were already created + // (ignore those that might be currently under construction in the render thread) + if (vao && vao->isAbandoned(m_nodesManager->geometryManager(), m_nodesManager->shaderManager())) { + m_abandonedVaosMutex.lock(); + m_abandonedVaos.push_back(handle); + m_abandonedVaosMutex.unlock(); + } + } +} + +// Executed in a job +void Renderer::lookForDirtyBuffers() +{ + const QVector<HBuffer> activeBufferHandles = m_nodesManager->bufferManager()->activeHandles(); + for (const HBuffer &handle: activeBufferHandles) { + Buffer *buffer = m_nodesManager->bufferManager()->data(handle); + if (buffer->isDirty()) + m_dirtyBuffers.push_back(handle); + } +} + +void Renderer::lookForDownloadableBuffers() +{ + m_downloadableBuffers.clear(); + const QVector<HBuffer> activeBufferHandles = m_nodesManager->bufferManager()->activeHandles(); + for (const HBuffer &handle : activeBufferHandles) { + Buffer *buffer = m_nodesManager->bufferManager()->data(handle); + if (buffer->access() & QBuffer::Read) + m_downloadableBuffers.push_back(handle); + } +} + +// Executed in a job +void Renderer::lookForDirtyTextures() +{ + const QVector<HTexture> activeTextureHandles = m_nodesManager->textureManager()->activeHandles(); + for (const HTexture &handle: activeTextureHandles) { + Texture *texture = m_nodesManager->textureManager()->data(handle); + // Dirty meaning that something has changed on the texture + // either properties, parameters, generator or a texture image + if (texture->dirtyFlags() != Texture::NotDirty) + m_dirtyTextures.push_back(handle); + } +} + +// Executed in a job +void Renderer::reloadDirtyShaders() +{ + Q_ASSERT(isRunning()); + const QVector<HTechnique> activeTechniques = m_nodesManager->techniqueManager()->activeHandles(); + const QVector<HShaderBuilder> activeBuilders = m_nodesManager->shaderBuilderManager()->activeHandles(); + for (const HTechnique &techniqueHandle : activeTechniques) { + Technique *technique = m_nodesManager->techniqueManager()->data(techniqueHandle); + // If api of the renderer matches the one from the technique + if (technique->isCompatibleWithRenderer()) { + const auto passIds = technique->renderPasses(); + for (const QNodeId passId : passIds) { + RenderPass *renderPass = m_nodesManager->renderPassManager()->lookupResource(passId); + HShader shaderHandle = m_nodesManager->shaderManager()->lookupHandle(renderPass->shaderProgram()); + Shader *shader = m_nodesManager->shaderManager()->data(shaderHandle); + + ShaderBuilder *shaderBuilder = nullptr; + for (const HShaderBuilder &builderHandle : activeBuilders) { + ShaderBuilder *builder = m_nodesManager->shaderBuilderManager()->data(builderHandle); + if (builder->shaderProgramId() == shader->peerId()) { + shaderBuilder = builder; + break; + } + } + + if (shaderBuilder) { + shaderBuilder->setGraphicsApi(*technique->graphicsApiFilter()); + + for (int i = 0; i <= ShaderBuilder::Compute; i++) { + const auto builderType = static_cast<ShaderBuilder::ShaderType>(i); + if (!shaderBuilder->shaderGraph(builderType).isValid()) + continue; + + if (shaderBuilder->isShaderCodeDirty(builderType)) { + shaderBuilder->generateCode(builderType); + } + + QShaderProgram::ShaderType shaderType = QShaderProgram::Vertex; + switch (builderType) { + case ShaderBuilder::Vertex: + shaderType = QShaderProgram::Vertex; + break; + case ShaderBuilder::TessellationControl: + shaderType = QShaderProgram::TessellationControl; + break; + case ShaderBuilder::TessellationEvaluation: + shaderType = QShaderProgram::TessellationEvaluation; + break; + case ShaderBuilder::Geometry: + shaderType = QShaderProgram::Geometry; + break; + case ShaderBuilder::Fragment: + shaderType = QShaderProgram::Fragment; + break; + case ShaderBuilder::Compute: + shaderType = QShaderProgram::Compute; + break; + } + + const auto code = shaderBuilder->shaderCode(builderType); + shader->setShaderCode(shaderType, code); + } + } + + if (Q_UNLIKELY(shader->hasPendingNotifications())) + shader->submitPendingNotifications(); + // If the shader hasn't be loaded, load it + if (shader != nullptr && !shader->isLoaded()) + loadShader(shader); + } + } + } +} + +// Render Thread +void Renderer::updateGLResources() +{ + { + Profiling::GLTimeRecorder recorder(Profiling::BufferUpload); + const QVector<HBuffer> dirtyBufferHandles = std::move(m_dirtyBuffers); + for (const HBuffer &handle: dirtyBufferHandles) { + Buffer *buffer = m_nodesManager->bufferManager()->data(handle); + // Forces creation if it doesn't exit + // Also note the binding point doesn't really matter here, we just upload data + if (!m_submissionContext->hasGLBufferForBuffer(buffer)) + m_submissionContext->glBufferForRenderBuffer(buffer, GLBuffer::ArrayBuffer); + // Update the glBuffer data + m_submissionContext->updateBuffer(buffer); + buffer->unsetDirty(); + } + } + + { + Profiling::GLTimeRecorder recorder(Profiling::TextureUpload); + const QVector<HTexture> activeTextureHandles = std::move(m_dirtyTextures); + for (const HTexture &handle: activeTextureHandles) { + Texture *texture = m_nodesManager->textureManager()->data(handle); + // Upload/Update texture + updateTexture(texture); + } + } + // When Textures are cleaned up, their id is saved + // so that they can be cleaned up in the render thread + // Note: we perform this step in second so that the previous updateTexture call + // has a chance to find a shared texture + const QVector<Qt3DCore::QNodeId> cleanedUpTextureIds = m_nodesManager->textureManager()->takeTexturesIdsToCleanup(); + for (const Qt3DCore::QNodeId textureCleanedUpId: cleanedUpTextureIds) { + cleanupTexture(m_nodesManager->textureManager()->lookupResource(textureCleanedUpId)); + // We can really release the texture at this point + m_nodesManager->textureManager()->releaseResource(textureCleanedUpId); + } +} + +// Render Thread +void Renderer::updateTexture(Texture *texture) +{ + // Check that the current texture images are still in place, if not, do not update + const bool isValid = texture->isValid(); + if (!isValid) + return; + + // For implementing unique, non-shared, non-cached textures. + // for now, every texture is shared by default + + bool isUnique = false; + + // TO DO: Update the vector once per frame (or in a job) + const QVector<HAttachment> activeRenderTargetOutputs = m_nodesManager->attachmentManager()->activeHandles(); + // A texture is unique if it's being reference by a render target output + for (const HAttachment &attachmentHandle : activeRenderTargetOutputs) { + RenderTargetOutput *attachment = m_nodesManager->attachmentManager()->data(attachmentHandle); + if (attachment->textureUuid() == texture->peerId()) { + isUnique = true; + break; + } + } + + // Try to find the associated GLTexture for the backend Texture + GLTextureManager *glTextureManager = m_nodesManager->glTextureManager(); + GLTexture *glTexture = glTextureManager->lookupResource(texture->peerId()); + + // No GLTexture associated yet -> create it + if (glTexture == nullptr) { + if (isUnique) + glTextureManager->createUnique(texture); + else + glTextureManager->getOrCreateShared(texture); + texture->unsetDirty(); + return; + } + + // if this texture is a shared texture, we might need to look for a new TextureImpl + // and abandon the old one + if (glTextureManager->isShared(glTexture)) { + glTextureManager->abandon(glTexture, texture); + // Check if a shared texture should become unique + if (isUnique) + glTextureManager->createUnique(texture); + else + glTextureManager->getOrCreateShared(texture); + texture->unsetDirty(); + return; + } + + // this texture node is the only one referring to the GLTexture. + // we could thus directly modify the texture. Instead, for non-unique textures, + // we first see if there is already a matching texture present. + if (!isUnique) { + GLTexture *newSharedTex = glTextureManager->findMatchingShared(texture); + if (newSharedTex && newSharedTex != glTexture) { + glTextureManager->abandon(glTexture, texture); + glTextureManager->adoptShared(newSharedTex, texture); + texture->unsetDirty(); + return; + } + } + + // we hold a reference to a unique or exclusive access to a shared texture + // we can thus modify the texture directly. + const Texture::DirtyFlags dirtyFlags = texture->dirtyFlags(); + + if (dirtyFlags.testFlag(Texture::DirtyProperties) && + !glTextureManager->setProperties(glTexture, texture->properties())) + qWarning() << "[Qt3DRender::TextureNode] updateTexture: TextureImpl.setProperties failed, should be non-shared"; + + if (dirtyFlags.testFlag(Texture::DirtyParameters) && + !glTextureManager->setParameters(glTexture, texture->parameters())) + qWarning() << "[Qt3DRender::TextureNode] updateTexture: TextureImpl.setParameters failed, should be non-shared"; + + // Will make the texture requestUpload + if (dirtyFlags.testFlag(Texture::DirtyImageGenerators) && + !glTextureManager->setImages(glTexture, texture->textureImages())) + qWarning() << "[Qt3DRender::TextureNode] updateTexture: TextureImpl.setGenerators failed, should be non-shared"; + + // Will make the texture requestUpload + if (dirtyFlags.testFlag(Texture::DirtyDataGenerator) && + !glTextureManager->setGenerator(glTexture, texture->dataGenerator())) + qWarning() << "[Qt3DRender::TextureNode] updateTexture: TextureImpl.setGenerator failed, should be non-shared"; + + // Unset the dirty flag on the texture + texture->unsetDirty(); +} + +// Render Thread +void Renderer::cleanupTexture(const Texture *texture) +{ + GLTextureManager *glTextureManager = m_nodesManager->glTextureManager(); + GLTexture *glTexture = glTextureManager->lookupResource(texture->peerId()); + + if (glTexture != nullptr) + glTextureManager->abandon(glTexture, texture); +} + +void Renderer::downloadGLBuffers() +{ + lookForDownloadableBuffers(); + const QVector<HBuffer> downloadableHandles = std::move(m_downloadableBuffers); + for (const HBuffer &handle : downloadableHandles) { + Buffer *buffer = m_nodesManager->bufferManager()->data(handle); + QByteArray content = m_submissionContext->downloadBufferContent(buffer); + m_sendBufferCaptureJob->addRequest(QPair<Buffer*, QByteArray>(buffer, content)); + } +} + +// Happens in RenderThread context when all RenderViewJobs are done +// Returns the id of the last bound FBO +Renderer::ViewSubmissionResultData Renderer::submitRenderViews(const QVector<Render::RenderView *> &renderViews) +{ + QElapsedTimer timer; + quint64 queueElapsed = 0; + timer.start(); + + const int renderViewsCount = renderViews.size(); + quint64 frameElapsed = queueElapsed; + m_lastFrameCorrect.store(1); // everything fine until now..... + + qCDebug(Memory) << Q_FUNC_INFO << "rendering frame "; + + // We might not want to render on the default FBO + uint lastBoundFBOId = m_submissionContext->boundFrameBufferObject(); + QSurface *surface = nullptr; + QSurface *previousSurface = nullptr; + for (const Render::RenderView *rv: renderViews) { + previousSurface = rv->surface(); + if (previousSurface) + break; + } + QSurface *lastUsedSurface = nullptr; + + for (int i = 0; i < renderViewsCount; ++i) { + // Initialize GraphicsContext for drawing + // If the RenderView has a RenderStateSet defined + const RenderView *renderView = renderViews.at(i); + + // Check if using the same surface as the previous RenderView. + // If not, we have to free up the context from the previous surface + // and make the context current on the new surface + surface = renderView->surface(); + SurfaceLocker surfaceLock(surface); + + // TO DO: Make sure that the surface we are rendering too has not been unset + + // For now, if we do not have a surface, skip this renderview + // TODO: Investigate if it's worth providing a fallback offscreen surface + // to use when surface is null. Or if we should instead expose an + // offscreensurface to Qt3D. + if (!surface || !surfaceLock.isSurfaceValid()) { + m_lastFrameCorrect.store(0); + continue; + } + + lastUsedSurface = surface; + const bool surfaceHasChanged = surface != previousSurface; + + if (surfaceHasChanged && previousSurface) { + const bool swapBuffers = (lastBoundFBOId == m_submissionContext->defaultFBO()) && PlatformSurfaceFilter::isSurfaceValid(previousSurface); + // We only call swap buffer if we are sure the previous surface is still valid + m_submissionContext->endDrawing(swapBuffers); + } + + if (surfaceHasChanged) { + // If we can't make the context current on the surface, skip to the + // next RenderView. We won't get the full frame but we may get something + if (!m_submissionContext->beginDrawing(surface)) { + qWarning() << "Failed to make OpenGL context current on surface"; + m_lastFrameCorrect.store(0); + continue; + } + + previousSurface = surface; + lastBoundFBOId = m_submissionContext->boundFrameBufferObject(); + } + + // Apply Memory Barrier if needed + if (renderView->memoryBarrier() != QMemoryBarrier::None) + m_submissionContext->memoryBarrier(renderView->memoryBarrier()); + + // Note: the RenderStateSet is allocated once per RV if needed + // and it contains a list of StateVariant value types + RenderStateSet *renderViewStateSet = renderView->stateSet(); + + { + Profiling::GLTimeRecorder recorder(Profiling::StateUpdate); + // Set the RV state if not null, + if (renderViewStateSet != nullptr) + m_submissionContext->setCurrentStateSet(renderViewStateSet); + else + m_submissionContext->setCurrentStateSet(m_defaultRenderStateSet); + } + + // Set RenderTarget ... + // Activate RenderTarget + { + Profiling::GLTimeRecorder recorder(Profiling::RenderTargetUpdate); + m_submissionContext->activateRenderTarget(renderView->renderTargetId(), + renderView->attachmentPack(), + lastBoundFBOId); + } + + { + Profiling::GLTimeRecorder recorder(Profiling::ClearBuffer); + // set color, depth, stencil clear values (only if needed) + auto clearBufferTypes = renderView->clearTypes(); + if (clearBufferTypes & QClearBuffers::ColorBuffer) { + const QVector4D vCol = renderView->globalClearColorBufferInfo().clearColor; + m_submissionContext->clearColor(QColor::fromRgbF(vCol.x(), vCol.y(), vCol.z(), vCol.w())); + } + if (clearBufferTypes & QClearBuffers::DepthBuffer) + m_submissionContext->clearDepthValue(renderView->clearDepthValue()); + if (clearBufferTypes & QClearBuffers::StencilBuffer) + m_submissionContext->clearStencilValue(renderView->clearStencilValue()); + + // Clear BackBuffer + m_submissionContext->clearBackBuffer(clearBufferTypes); + + // if there are ClearColors set for different draw buffers, + // clear each of these draw buffers individually now + const QVector<ClearBufferInfo> clearDrawBuffers = renderView->specificClearColorBufferInfo(); + for (const ClearBufferInfo &clearBuffer : clearDrawBuffers) + m_submissionContext->clearBufferf(clearBuffer.drawBufferIndex, clearBuffer.clearColor); + } + + // Set the Viewport + m_submissionContext->setViewport(renderView->viewport(), renderView->surfaceSize() * renderView->devicePixelRatio()); + + // Execute the render commands + if (!executeCommandsSubmission(renderView)) + m_lastFrameCorrect.store(0); // something went wrong; make sure to render the next frame! + + // executeCommandsSubmission takes care of restoring the stateset to the value + // of gc->currentContext() at the moment it was called (either + // renderViewStateSet or m_defaultRenderStateSet) + if (!renderView->renderCaptureNodeId().isNull()) { + const QRenderCaptureRequest request = renderView->renderCaptureRequest(); + const QSize size = m_submissionContext->renderTargetSize(renderView->surfaceSize() * renderView->devicePixelRatio()); + QRect rect(QPoint(0, 0), size); + if (!request.rect.isEmpty()) + rect = rect.intersected(request.rect); + QImage image; + if (!rect.isEmpty()) { + // Bind fbo as read framebuffer + m_submissionContext->bindFramebuffer(m_submissionContext->activeFBO(), GraphicsHelperInterface::FBORead); + image = m_submissionContext->readFramebuffer(rect); + } else { + qWarning() << "Requested capture rectangle is outside framebuffer"; + } + Render::RenderCapture *renderCapture = + static_cast<Render::RenderCapture*>(m_nodesManager->frameGraphManager()->lookupNode(renderView->renderCaptureNodeId())); + renderCapture->addRenderCapture(request.captureId, image); + addRenderCaptureSendRequest(renderView->renderCaptureNodeId()); + } + + if (renderView->isDownloadBuffersEnable()) + downloadGLBuffers(); + + // Perform BlitFramebuffer operations + if (renderView->hasBlitFramebufferInfo()) { + const auto &blitFramebufferInfo = renderView->blitFrameBufferInfo(); + const QNodeId inputTargetId = blitFramebufferInfo.sourceRenderTargetId; + const QNodeId outputTargetId = blitFramebufferInfo.destinationRenderTargetId; + const QRect inputRect = blitFramebufferInfo.sourceRect; + const QRect outputRect = blitFramebufferInfo.destinationRect; + const QRenderTargetOutput::AttachmentPoint inputAttachmentPoint = blitFramebufferInfo.sourceAttachmentPoint; + const QRenderTargetOutput::AttachmentPoint outputAttachmentPoint = blitFramebufferInfo.destinationAttachmentPoint; + const QBlitFramebuffer::InterpolationMethod interpolationMethod = blitFramebufferInfo.interpolationMethod; + m_submissionContext->blitFramebuffer(inputTargetId, outputTargetId, inputRect, outputRect, lastBoundFBOId, + inputAttachmentPoint, outputAttachmentPoint, + interpolationMethod); + } + + + frameElapsed = timer.elapsed() - frameElapsed; + qCDebug(Rendering) << Q_FUNC_INFO << "Submitted Renderview " << i + 1 << "/" << renderViewsCount << "in " << frameElapsed << "ms"; + frameElapsed = timer.elapsed(); + } + + // Bind lastBoundFBOId back. Needed also in threaded mode. + // lastBoundFBOId != m_graphicsContext->activeFBO() when the last FrameGraph leaf node/renderView + // contains RenderTargetSelector/RenderTarget + if (lastBoundFBOId != m_submissionContext->activeFBO()) + m_submissionContext->bindFramebuffer(lastBoundFBOId, GraphicsHelperInterface::FBOReadAndDraw); + + // Reset state and call doneCurrent if the surface + // is valid and was actually activated + if (surface && m_submissionContext->hasValidGLHelper()) { + // Reset state to the default state if the last stateset is not the + // defaultRenderStateSet + if (m_submissionContext->currentStateSet() != m_defaultRenderStateSet) + m_submissionContext->setCurrentStateSet(m_defaultRenderStateSet); + } + + queueElapsed = timer.elapsed() - queueElapsed; + qCDebug(Rendering) << Q_FUNC_INFO << "Submission of Queue in " << queueElapsed << "ms <=> " << queueElapsed / renderViewsCount << "ms per RenderView <=> Avg " << 1000.0f / (queueElapsed * 1.0f/ renderViewsCount * 1.0f) << " RenderView/s"; + qCDebug(Rendering) << Q_FUNC_INFO << "Submission Completed in " << timer.elapsed() << "ms"; + + // Stores the necessary information to safely perform + // the last swap buffer call + ViewSubmissionResultData resultData; + resultData.lastBoundFBOId = lastBoundFBOId; + resultData.surface = lastUsedSurface; + + return resultData; +} + +void Renderer::markDirty(BackendNodeDirtySet changes, BackendNode *node) +{ + Q_UNUSED(node); + m_dirtyBits.marked |= changes; +} + +Renderer::BackendNodeDirtySet Renderer::dirtyBits() +{ + return m_dirtyBits.marked; +} + +#if defined(QT_BUILD_INTERNAL) +void Renderer::clearDirtyBits(BackendNodeDirtySet changes) +{ + m_dirtyBits.remaining &= ~changes; + m_dirtyBits.marked &= ~changes; +} +#endif + +bool Renderer::shouldRender() +{ + // Only render if something changed during the last frame, or the last frame + // was not rendered successfully (or render-on-demand is disabled) + return (m_settings->renderPolicy() == QRenderSettings::Always + || m_dirtyBits.marked != 0 + || m_dirtyBits.remaining != 0 + || !m_lastFrameCorrect.load()); +} + +void Renderer::skipNextFrame() +{ + Q_ASSERT(m_settings->renderPolicy() != QRenderSettings::Always); + + // make submitRenderViews() actually run + m_renderQueue->setNoRender(); + m_submitRenderViewsSemaphore.release(1); +} + +// Waits to be told to create jobs for the next frame +// Called by QRenderAspect jobsToExecute context of QAspectThread +// Returns all the jobs (and with proper dependency chain) required +// for the rendering of the scene +QVector<Qt3DCore::QAspectJobPtr> Renderer::renderBinJobs() +{ + QVector<QAspectJobPtr> renderBinJobs; + + // Create the jobs to build the frame + const QVector<QAspectJobPtr> bufferJobs = createRenderBufferJobs(); + + // Remove previous dependencies + m_calculateBoundingVolumeJob->removeDependency(QWeakPointer<QAspectJob>()); + m_cleanupJob->removeDependency(QWeakPointer<QAspectJob>()); + + // Set dependencies + for (const QAspectJobPtr &bufferJob : bufferJobs) + m_calculateBoundingVolumeJob->addDependency(bufferJob); + + m_updateLevelOfDetailJob->setFrameGraphRoot(frameGraphRoot()); + + const BackendNodeDirtySet dirtyBitsForFrame = m_dirtyBits.marked | m_dirtyBits.remaining; + m_dirtyBits.marked = 0; + m_dirtyBits.remaining = 0; + BackendNodeDirtySet notCleared = 0; + + // Add jobs + const bool entitiesEnabledDirty = dirtyBitsForFrame & AbstractRenderer::EntityEnabledDirty; + if (entitiesEnabledDirty) { + renderBinJobs.push_back(m_updateTreeEnabledJob); + // This dependency is added here because we clear all dependencies + // at the start of this function. + m_calculateBoundingVolumeJob->addDependency(m_updateTreeEnabledJob); + } + + if (dirtyBitsForFrame & AbstractRenderer::TransformDirty) { + renderBinJobs.push_back(m_worldTransformJob); + renderBinJobs.push_back(m_updateWorldBoundingVolumeJob); + renderBinJobs.push_back(m_updateShaderDataTransformJob); + } + + if (dirtyBitsForFrame & AbstractRenderer::GeometryDirty) { + renderBinJobs.push_back(m_calculateBoundingVolumeJob); + renderBinJobs.push_back(m_updateMeshTriangleListJob); + } + + if (dirtyBitsForFrame & AbstractRenderer::GeometryDirty || + dirtyBitsForFrame & AbstractRenderer::TransformDirty) { + renderBinJobs.push_back(m_expandBoundingVolumeJob); + } + + m_updateSkinningPaletteJob->setDirtyJoints(m_nodesManager->jointManager()->dirtyJoints()); + renderBinJobs.push_back(m_updateSkinningPaletteJob); + renderBinJobs.push_back(m_updateLevelOfDetailJob); + renderBinJobs.push_back(m_cleanupJob); + renderBinJobs.push_back(m_sendRenderCaptureJob); + renderBinJobs.push_back(m_sendBufferCaptureJob); + renderBinJobs.append(bufferJobs); + + // Jobs to prepare GL Resource upload + renderBinJobs.push_back(m_vaoGathererJob); + + if (dirtyBitsForFrame & AbstractRenderer::BuffersDirty) + renderBinJobs.push_back(m_bufferGathererJob); + + if (dirtyBitsForFrame & AbstractRenderer::TexturesDirty) { + renderBinJobs.push_back(m_syncTextureLoadingJob); + renderBinJobs.push_back(m_textureGathererJob); + } + + + // Layer cache is dependent on layers, layer filters and the enabled flag + // on entities + const bool layersDirty = dirtyBitsForFrame & AbstractRenderer::LayersDirty; + const bool layersCacheNeedsToBeRebuilt = layersDirty || entitiesEnabledDirty; + const bool materialDirty = dirtyBitsForFrame & AbstractRenderer::MaterialDirty; + + QMutexLocker lock(m_renderQueue->mutex()); + if (m_renderQueue->wasReset()) { // Have we rendered yet? (Scene3D case) + // Traverse the current framegraph. For each leaf node create a + // RenderView and set its configuration then create a job to + // populate the RenderView with a set of RenderCommands that get + // their details from the RenderNodes that are visible to the + // Camera selected by the framegraph configuration + FrameGraphVisitor visitor(m_nodesManager->frameGraphManager()); + const QVector<FrameGraphNode *> fgLeaves = visitor.traverse(frameGraphRoot()); + + // Remove leaf nodes that no longer exist from cache + const QList<FrameGraphNode *> keys = m_cache.leafNodeCache.keys(); + for (FrameGraphNode *leafNode : keys) { + if (!fgLeaves.contains(leafNode)) + m_cache.leafNodeCache.remove(leafNode); + } + + const int fgBranchCount = fgLeaves.size(); + for (int i = 0; i < fgBranchCount; ++i) { + RenderViewBuilder builder(fgLeaves.at(i), i, this); + builder.setLayerCacheNeedsToBeRebuilt(layersCacheNeedsToBeRebuilt); + builder.setMaterialGathererCacheNeedsToBeRebuilt(materialDirty); + builder.prepareJobs(); + renderBinJobs.append(builder.buildJobHierachy()); + } + + // Set target number of RenderViews + m_renderQueue->setTargetRenderViewCount(fgBranchCount); + } else { + // FilterLayerEntityJob is part of the RenderViewBuilder jobs and must be run later + // if none of those jobs are started this frame + notCleared |= AbstractRenderer::EntityEnabledDirty; + notCleared |= AbstractRenderer::LayersDirty; + } + + if (isRunning() && m_submissionContext->isInitialized()) { + if (dirtyBitsForFrame & AbstractRenderer::TechniquesDirty ) + renderBinJobs.push_back(m_filterCompatibleTechniqueJob); + if (dirtyBitsForFrame & AbstractRenderer::ShadersDirty) + renderBinJobs.push_back(m_introspectShaderJob); + } else { + notCleared |= AbstractRenderer::TechniquesDirty; + notCleared |= AbstractRenderer::ShadersDirty; + } + + m_dirtyBits.remaining = dirtyBitsForFrame & notCleared; + + return renderBinJobs; +} + +QAspectJobPtr Renderer::pickBoundingVolumeJob() +{ + // Set values on pickBoundingVolumeJob + RenderSettings *renderSetting = settings(); + if (renderSetting != nullptr) { + m_pickBoundingVolumeJob->setRenderSettings(renderSetting); + m_pickBoundingVolumeJob->setFrameGraphRoot(frameGraphRoot()); + m_pickBoundingVolumeJob->setMouseEvents(pendingPickingEvents()); + m_pickBoundingVolumeJob->setKeyEvents(pendingKeyEvents()); + } + + return m_pickBoundingVolumeJob; +} + +QAspectJobPtr Renderer::rayCastingJob() +{ + // Set values on rayCastingJob + RenderSettings *renderSetting = settings(); + if (renderSetting != nullptr) { + m_rayCastingJob->setRenderSettings(renderSetting); + m_rayCastingJob->setFrameGraphRoot(frameGraphRoot()); + } + + return m_rayCastingJob; +} + +QAspectJobPtr Renderer::syncTextureLoadingJob() +{ + return m_syncTextureLoadingJob; +} + +QAspectJobPtr Renderer::expandBoundingVolumeJob() +{ + return m_expandBoundingVolumeJob; +} + +QAbstractFrameAdvanceService *Renderer::frameAdvanceService() const +{ + return static_cast<Qt3DCore::QAbstractFrameAdvanceService *>(m_vsyncFrameAdvanceService.data()); +} + +// Called by executeCommands +void Renderer::performDraw(RenderCommand *command) +{ + // Indirect Draw Calls + if (command->m_drawIndirect) { + + // Bind the indirect draw buffer + Buffer *indirectDrawBuffer = m_nodesManager->bufferManager()->data(command->m_indirectDrawBuffer); + if (Q_UNLIKELY(indirectDrawBuffer == nullptr)) { + qWarning() << "Invalid Indirect Draw Buffer - failed to retrieve Buffer"; + return; + } + + // Get GLBuffer from Buffer; + GLBuffer *indirectDrawGLBuffer = m_submissionContext->glBufferForRenderBuffer(indirectDrawBuffer, GLBuffer::DrawIndirectBuffer); + if (Q_UNLIKELY(indirectDrawGLBuffer == nullptr)) { + qWarning() << "Invalid Indirect Draw Buffer - failed to retrieve GLBuffer"; + return; + } + + // Bind GLBuffer + const bool successfullyBound = indirectDrawGLBuffer->bind(m_submissionContext.data(), GLBuffer::DrawIndirectBuffer); + + if (Q_LIKELY(successfullyBound)) { + // TO DO: Handle multi draw variants if attribute count > 1 + if (command->m_drawIndexed) { + m_submissionContext->drawElementsIndirect(command->m_primitiveType, + command->m_indexAttributeDataType, + reinterpret_cast<void*>(quintptr(command->m_indirectAttributeByteOffset))); + } else { + m_submissionContext->drawArraysIndirect(command->m_primitiveType, + reinterpret_cast<void*>(quintptr(command->m_indirectAttributeByteOffset))); + } + } else { + qWarning() << "Failed to bind IndirectDrawBuffer"; + } + + } else { // Direct Draw Calls + + // TO DO: Add glMulti Draw variants + if (command->m_primitiveType == QGeometryRenderer::Patches) + m_submissionContext->setVerticesPerPatch(command->m_verticesPerPatch); + + if (command->m_primitiveRestartEnabled) + m_submissionContext->enablePrimitiveRestart(command->m_restartIndexValue); + + // TO DO: Add glMulti Draw variants + if (command->m_drawIndexed) { + Profiling::GLTimeRecorder recorder(Profiling::DrawElement); + m_submissionContext->drawElementsInstancedBaseVertexBaseInstance(command->m_primitiveType, + command->m_primitiveCount, + command->m_indexAttributeDataType, + reinterpret_cast<void*>(quintptr(command->m_indexAttributeByteOffset)), + command->m_instanceCount, + command->m_indexOffset, + command->m_firstVertex); + } else { + Profiling::GLTimeRecorder recorder(Profiling::DrawArray); + m_submissionContext->drawArraysInstancedBaseInstance(command->m_primitiveType, + command->m_firstInstance, + command->m_primitiveCount, + command->m_instanceCount, + command->m_firstVertex); + } + } + +#if defined(QT3D_RENDER_ASPECT_OPENGL_DEBUG) + int err = m_graphicsContext->openGLContext()->functions()->glGetError(); + if (err) + qCWarning(Rendering) << "GL error after drawing mesh:" << QString::number(err, 16); +#endif + + if (command->m_primitiveRestartEnabled) + m_submissionContext->disablePrimitiveRestart(); +} + +void Renderer::performCompute(const RenderView *, RenderCommand *command) +{ + { + Profiling::GLTimeRecorder recorder(Profiling::ShaderUpdate); + m_submissionContext->activateShader(command->m_shaderDna); + } + { + Profiling::GLTimeRecorder recorder(Profiling::UniformUpdate); + m_submissionContext->setParameters(command->m_parameterPack); + } + { + Profiling::GLTimeRecorder recorder(Profiling::DispatchCompute); + m_submissionContext->dispatchCompute(command->m_workGroups[0], + command->m_workGroups[1], + command->m_workGroups[2]); + } + // HACK: Reset the compute flag to dirty + m_dirtyBits.marked |= AbstractRenderer::ComputeDirty; + +#if defined(QT3D_RENDER_ASPECT_OPENGL_DEBUG) + int err = m_graphicsContext->openGLContext()->functions()->glGetError(); + if (err) + qCWarning(Rendering) << "GL error after drawing mesh:" << QString::number(err, 16); +#endif +} + +void Renderer::createOrUpdateVAO(RenderCommand *command, + HVao *previousVaoHandle, + OpenGLVertexArrayObject **vao) +{ + const VAOIdentifier vaoKey(command->m_geometry, command->m_shader); + + VAOManager *vaoManager = m_nodesManager->vaoManager(); + command->m_vao = vaoManager->lookupHandle(vaoKey); + + if (command->m_vao.isNull()) { + qCDebug(Rendering) << Q_FUNC_INFO << "Allocating new VAO"; + command->m_vao = vaoManager->getOrAcquireHandle(vaoKey); + vaoManager->data(command->m_vao)->create(m_submissionContext.data(), vaoKey); + } + + if (*previousVaoHandle != command->m_vao) { + *previousVaoHandle = command->m_vao; + *vao = vaoManager->data(command->m_vao); + } + Q_ASSERT(*vao); +} + +// Called by RenderView->submit() in RenderThread context +// Returns true, if all RenderCommands were sent to the GPU +bool Renderer::executeCommandsSubmission(const RenderView *rv) +{ + bool allCommandsIssued = true; + + // Render drawing commands + const QVector<RenderCommand *> commands = rv->commands(); + + // Use the graphicscontext to submit the commands to the underlying + // graphics API (OpenGL) + + // Save the RenderView base stateset + RenderStateSet *globalState = m_submissionContext->currentStateSet(); + OpenGLVertexArrayObject *vao = nullptr; + + for (RenderCommand *command : qAsConst(commands)) { + + if (command->m_type == RenderCommand::Compute) { // Compute Call + performCompute(rv, command); + } else { // Draw Command + // Check if we have a valid command that can be drawn + if (!command->m_isValid) { + allCommandsIssued = false; + continue; + } + + vao = m_nodesManager->vaoManager()->data(command->m_vao); + + // something may have went wrong when initializing the VAO + if (!vao->isSpecified()) { + allCommandsIssued = false; + continue; + } + + { + Profiling::GLTimeRecorder recorder(Profiling::ShaderUpdate); + //// We activate the shader here + if (!m_submissionContext->activateShader(command->m_shaderDna)) { + allCommandsIssued = false; + continue; + } + } + + { + Profiling::GLTimeRecorder recorder(Profiling::VAOUpdate); + // Bind VAO + vao->bind(); + } + + { + Profiling::GLTimeRecorder recorder(Profiling::UniformUpdate); + //// Update program uniforms + if (!m_submissionContext->setParameters(command->m_parameterPack)) { + allCommandsIssued = false; + // If we have failed to set uniform (e.g unable to bind a texture) + // we won't perform the draw call which could show invalid content + continue; + } + } + + //// OpenGL State + // TO DO: Make states not dependendent on their backend node for this step + // Set state + RenderStateSet *localState = command->m_stateSet; + + + { + Profiling::GLTimeRecorder recorder(Profiling::StateUpdate); + // Merge the RenderCommand state with the globalState of the RenderView + // Or restore the globalState if no stateSet for the RenderCommand + if (localState != nullptr) { + command->m_stateSet->merge(globalState); + m_submissionContext->setCurrentStateSet(command->m_stateSet); + } else { + m_submissionContext->setCurrentStateSet(globalState); + } + } + // All Uniforms for a pass are stored in the QUniformPack of the command + // Uniforms for Effect, Material and Technique should already have been correctly resolved + // at that point + + //// Draw Calls + performDraw(command); + } + } // end of RenderCommands loop + + // We cache the VAO and release it only at the end of the exectute frame + // We try to minimize VAO binding between RenderCommands + if (vao) + vao->release(); + + // Reset to the state we were in before executing the render commands + m_submissionContext->setCurrentStateSet(globalState); + + return allCommandsIssued; +} + +bool Renderer::updateVAOWithAttributes(Geometry *geometry, + RenderCommand *command, + Shader *shader, + bool forceUpdate) +{ + m_dirtyAttributes.reserve(m_dirtyAttributes.size() + geometry->attributes().size()); + const auto attributeIds = geometry->attributes(); + + for (QNodeId attributeId : attributeIds) { + // TO DO: Improvement we could store handles and use the non locking policy on the attributeManager + Attribute *attribute = m_nodesManager->attributeManager()->lookupResource(attributeId); + + if (attribute == nullptr) + return false; + + Buffer *buffer = m_nodesManager->bufferManager()->lookupResource(attribute->bufferId()); + + // Buffer update was already performed at this point + // Just make sure the attribute reference a valid buffer + if (buffer == nullptr) + return false; + + // Index Attribute + bool attributeWasDirty = false; + if (attribute->attributeType() == QAttribute::IndexAttribute) { + if ((attributeWasDirty = attribute->isDirty()) == true || forceUpdate) + m_submissionContext->specifyIndices(buffer); + // Vertex Attribute + } else if (command->m_attributes.contains(attribute->nameId())) { + if ((attributeWasDirty = attribute->isDirty()) == true || forceUpdate) { + // Find the location for the attribute + const QVector<ShaderAttribute> shaderAttributes = shader->attributes(); + const ShaderAttribute *attributeDescription = nullptr; + for (const ShaderAttribute &shaderAttribute : shaderAttributes) { + if (shaderAttribute.m_nameId == attribute->nameId()) { + attributeDescription = &shaderAttribute; + break; + } + } + if (!attributeDescription || attributeDescription->m_location < 0) + return false; + m_submissionContext->specifyAttribute(attribute, buffer, attributeDescription); + } + } + + // Append attribute to temporary vector so that its dirtiness + // can be cleared at the end of the frame + if (attributeWasDirty) + m_dirtyAttributes.push_back(attribute); + + // Note: We cannot call unsertDirty on the Attribute at this + // point as we don't know if the attributes are being shared + // with other geometry / geometryRenderer in which case they still + // should remain dirty so that VAO for these commands are properly + // updated + } + + return true; +} + +bool Renderer::requiresVAOAttributeUpdate(Geometry *geometry, + RenderCommand *command) const +{ + const auto attributeIds = geometry->attributes(); + + for (QNodeId attributeId : attributeIds) { + // TO DO: Improvement we could store handles and use the non locking policy on the attributeManager + Attribute *attribute = m_nodesManager->attributeManager()->lookupResource(attributeId); + + if (attribute == nullptr) + continue; + + if ((attribute->attributeType() == QAttribute::IndexAttribute && attribute->isDirty()) || + (command->m_attributes.contains(attribute->nameId()) && attribute->isDirty())) + return true; + } + return false; +} + +// Erase graphics related resources that may become unused after a frame +void Renderer::cleanGraphicsResources() +{ + // Clean buffers + const QVector<Qt3DCore::QNodeId> buffersToRelease = m_nodesManager->bufferManager()->takeBuffersToRelease(); + for (Qt3DCore::QNodeId bufferId : buffersToRelease) + m_submissionContext->releaseBuffer(bufferId); + + // Delete abandoned textures + const QVector<GLTexture*> abandonedTextures = m_nodesManager->glTextureManager()->takeAbandonedTextures(); + for (GLTexture *tex : abandonedTextures) { + tex->destroyGLTexture(); + delete tex; + } + + // Delete abandoned VAOs + m_abandonedVaosMutex.lock(); + const QVector<HVao> abandonedVaos = std::move(m_abandonedVaos); + m_abandonedVaosMutex.unlock(); + for (const HVao &vaoHandle : abandonedVaos) { + // might have already been destroyed last frame, but added by the cleanup job before, so + // check if the VAO is really still existent + OpenGLVertexArrayObject *vao = m_nodesManager->vaoManager()->data(vaoHandle); + if (vao) { + vao->destroy(); + m_nodesManager->vaoManager()->release(vaoHandle); + } + } +} + +QList<QPair<QObject *, QMouseEvent>> Renderer::pendingPickingEvents() const +{ + return m_pickEventFilter->pendingMouseEvents(); +} + +QList<QKeyEvent> Renderer::pendingKeyEvents() const +{ + return m_pickEventFilter->pendingKeyEvents(); +} + +const GraphicsApiFilterData *Renderer::contextInfo() const +{ + return m_submissionContext->contextInfo(); +} + +SubmissionContext *Renderer::submissionContext() const +{ + return m_submissionContext.data(); +} + +void Renderer::addRenderCaptureSendRequest(Qt3DCore::QNodeId nodeId) +{ + if (!m_pendingRenderCaptureSendRequests.contains(nodeId)) + m_pendingRenderCaptureSendRequests.push_back(nodeId); +} + +const QVector<Qt3DCore::QNodeId> Renderer::takePendingRenderCaptureSendRequests() +{ + return std::move(m_pendingRenderCaptureSendRequests); +} + +// Returns a vector of jobs to be performed for dirty buffers +// 1 dirty buffer == 1 job, all job can be performed in parallel +QVector<Qt3DCore::QAspectJobPtr> Renderer::createRenderBufferJobs() const +{ + const QVector<QNodeId> dirtyBuffers = m_nodesManager->bufferManager()->takeDirtyBuffers(); + QVector<QAspectJobPtr> dirtyBuffersJobs; + dirtyBuffersJobs.reserve(dirtyBuffers.size()); + + for (const QNodeId bufId : dirtyBuffers) { + Render::HBuffer bufferHandle = m_nodesManager->lookupHandle<Render::Buffer, Render::BufferManager, Render::HBuffer>(bufId); + if (!bufferHandle.isNull()) { + // Create new buffer job + auto job = Render::LoadBufferJobPtr::create(bufferHandle); + job->setNodeManager(m_nodesManager); + dirtyBuffersJobs.push_back(job); + } + } + + return dirtyBuffersJobs; +} + +} // namespace Render +} // namespace Qt3DRender + +QT_END_NAMESPACE diff --git a/src/render/renderers/opengl/renderer/renderer.pri b/src/render/renderers/opengl/renderer/renderer.pri new file mode 100644 index 000000000..34f6064bd --- /dev/null +++ b/src/render/renderers/opengl/renderer/renderer.pri @@ -0,0 +1,27 @@ +INCLUDEPATH += $$PWD + +SOURCES += \ + $$PWD/commandthread.cpp \ + $$PWD/glcommands.cpp \ + $$PWD/openglvertexarrayobject.cpp \ + $$PWD/rendercommand.cpp \ + $$PWD/renderer.cpp \ + $$PWD/renderqueue.cpp \ + $$PWD/renderview.cpp \ + $$PWD/renderviewbuilder.cpp \ + $$PWD/shaderparameterpack.cpp + +HEADERS += \ + $$PWD/commandthread_p.h \ + $$PWD/glcommands_p.h \ + $$PWD/openglvertexarrayobject_p.h \ + $$PWD/renderercache_p.h \ + $$PWD/rendercommand_p.h \ + $$PWD/renderer_p.h \ + $$PWD/renderqueue_p.h \ + $$PWD/renderview_p.h \ + $$PWD/renderviewbuilder_p.h \ + $$PWD/shaderparameterpack_p.h \ + $$PWD/shadervariables_p.h + + diff --git a/src/render/renderers/opengl/renderer/renderer_p.h b/src/render/renderers/opengl/renderer/renderer_p.h new file mode 100644 index 000000000..b4ad0b0fe --- /dev/null +++ b/src/render/renderers/opengl/renderer/renderer_p.h @@ -0,0 +1,405 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB). +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT3DRENDER_RENDER_RENDERER_H +#define QT3DRENDER_RENDER_RENDERER_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 <Qt3DRender/qrenderaspect.h> +#include <Qt3DRender/qtechnique.h> +#include <Qt3DRender/private/shaderparameterpack_p.h> +#include <Qt3DRender/private/handle_types_p.h> +#include <Qt3DRender/private/abstractrenderer_p.h> +#include <Qt3DCore/qaspectjob.h> +#include <Qt3DRender/private/qt3drender_global_p.h> +#include <Qt3DRender/private/pickboundingvolumejob_p.h> +#include <Qt3DRender/private/raycastingjob_p.h> +#include <Qt3DRender/private/rendersettings_p.h> +#include <Qt3DRender/private/renderviewinitializerjob_p.h> +#include <Qt3DRender/private/expandboundingvolumejob_p.h> +#include <Qt3DRender/private/updateworldtransformjob_p.h> +#include <Qt3DRender/private/calcboundingvolumejob_p.h> +#include <Qt3DRender/private/updateshaderdatatransformjob_p.h> +#include <Qt3DRender/private/framecleanupjob_p.h> +#include <Qt3DRender/private/updateworldboundingvolumejob_p.h> +#include <Qt3DRender/private/updatetreeenabledjob_p.h> +#include <Qt3DRender/private/platformsurfacefilter_p.h> +#include <Qt3DRender/private/sendrendercapturejob_p.h> +#include <Qt3DRender/private/sendbuffercapturejob_p.h> +#include <Qt3DRender/private/genericlambdajob_p.h> +#include <Qt3DRender/private/updatemeshtrianglelistjob_p.h> +#include <Qt3DRender/private/filtercompatibletechniquejob_p.h> +#include <Qt3DRender/private/updateskinningpalettejob_p.h> +#include <Qt3DRender/private/renderercache_p.h> + +#include <QHash> +#include <QMatrix4x4> +#include <QObject> + +#include <QOpenGLShaderProgram> +#include <QOpenGLVertexArrayObject> +#include <QOpenGLBuffer> +#include <QMutex> +#include <QWaitCondition> +#include <QAtomicInt> +#include <QScopedPointer> +#include <QSemaphore> + +#include <functional> + +QT_BEGIN_NAMESPACE + +class QSurface; +class QMouseEvent; + +namespace Qt3DCore { +class QEntity; +class QFrameAllocator; +class QEventFilterService; +} + +namespace Qt3DRender { + +class QCamera; +class QMaterial; +class QShaderProgram; +class QMesh; +class QRenderPass; +class QAbstractShapeMesh; +struct GraphicsApiFilterData; +class QSceneImporter; + +#if QT_CONFIG(qt3d_profile_jobs) +namespace Debug { +class CommandExecuter; +} +#endif + +namespace Render { + +class CameraLens; +class SubmissionContext; +class FrameGraphNode; +class Material; +class Technique; +class Shader; +class Entity; +class RenderCommand; +class RenderQueue; +class RenderView; +class Effect; +class RenderPass; +class RenderThread; +class CommandThread; +class RenderStateSet; +class VSyncFrameAdvanceService; +class PickEventFilter; +class NodeManagers; +class ShaderCache; + +class UpdateLevelOfDetailJob; +typedef QSharedPointer<UpdateLevelOfDetailJob> UpdateLevelOfDetailJobPtr; + +using SynchronizerJobPtr = GenericLambdaJobPtr<std::function<void()>>; +using IntrospectShadersJobPtr = GenericLambdaJobPtr<std::function<void()>>; + +class QT3DRENDERSHARED_PRIVATE_EXPORT Renderer : public AbstractRenderer +{ +public: + explicit Renderer(QRenderAspect::RenderType type); + ~Renderer(); + + void dumpInfo() const override; + API api() const override { return AbstractRenderer::OpenGL; } + + qint64 time() const override; + void setTime(qint64 time) override; + + void setNodeManagers(NodeManagers *managers) override; + void setServices(Qt3DCore::QServiceLocator *services) override; + void setSurfaceExposed(bool exposed) override; + + NodeManagers *nodeManagers() const override; + Qt3DCore::QServiceLocator *services() const override { return m_services; } + + void initialize() override; + void shutdown() override; + void releaseGraphicsResources() override; + + void render() override; + void doRender(bool scene3dBlocking = false) override; + void cleanGraphicsResources() override; + + bool isRunning() const override { return m_running.load(); } + + void setSceneRoot(Qt3DCore::QBackendNodeFactory *factory, Entity *sgRoot) override; + Entity *sceneRoot() const override { return m_renderSceneRoot; } + + FrameGraphNode *frameGraphRoot() const override; + + void markDirty(BackendNodeDirtySet changes, BackendNode *node) override; + BackendNodeDirtySet dirtyBits() override; + +#if defined(QT_BUILD_INTERNAL) + void clearDirtyBits(BackendNodeDirtySet changes) override; +#endif + bool shouldRender() override; + void skipNextFrame() override; + + QVector<Qt3DCore::QAspectJobPtr> renderBinJobs() override; + Qt3DCore::QAspectJobPtr pickBoundingVolumeJob() override; + Qt3DCore::QAspectJobPtr rayCastingJob() override; + Qt3DCore::QAspectJobPtr syncTextureLoadingJob() override; + Qt3DCore::QAspectJobPtr expandBoundingVolumeJob() override; + + QVector<Qt3DCore::QAspectJobPtr> createRenderBufferJobs() const; + + inline FrameCleanupJobPtr frameCleanupJob() const { return m_cleanupJob; } + inline UpdateShaderDataTransformJobPtr updateShaderDataTransformJob() const { return m_updateShaderDataTransformJob; } + inline CalculateBoundingVolumeJobPtr calculateBoundingVolumeJob() const { return m_calculateBoundingVolumeJob; } + inline UpdateTreeEnabledJobPtr updateTreeEnabledJob() const { return m_updateTreeEnabledJob; } + inline UpdateWorldTransformJobPtr updateWorldTransformJob() const { return m_worldTransformJob; } + inline UpdateWorldBoundingVolumeJobPtr updateWorldBoundingVolumeJob() const { return m_updateWorldBoundingVolumeJob; } + 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 UpdateSkinningPaletteJobPtr updateSkinningPaletteJob() const { return m_updateSkinningPaletteJob; } + inline IntrospectShadersJobPtr introspectShadersJob() const { return m_introspectShaderJob; } + inline Qt3DCore::QAspectJobPtr bufferGathererJob() const { return m_bufferGathererJob; } + inline Qt3DCore::QAspectJobPtr textureGathererJob() const { return m_textureGathererJob; } + + Qt3DCore::QAbstractFrameAdvanceService *frameAdvanceService() const override; + + void registerEventFilter(Qt3DCore::QEventFilterService *service) override; + + void setSettings(RenderSettings *settings) override; + RenderSettings *settings() const override; + QOpenGLContext *shareContext() const override; + + + // Executed in secondary GL thread + void loadShader(Shader *shader) const override; + + + void updateGLResources(); + void updateTexture(Texture *texture); + void cleanupTexture(const Texture *texture); + void downloadGLBuffers(); + void blitFramebuffer(Qt3DCore::QNodeId inputRenderTargetId, + Qt3DCore::QNodeId outputRenderTargetId, + QRect inputRect, + QRect outputRect, + GLuint defaultFramebuffer); + + void prepareCommandsSubmission(const QVector<RenderView *> &renderViews); + bool executeCommandsSubmission(const RenderView *rv); + bool updateVAOWithAttributes(Geometry *geometry, + RenderCommand *command, + Shader *shader, + bool forceUpdate); + + bool requiresVAOAttributeUpdate(Geometry *geometry, + RenderCommand *command) const; + + void setOpenGLContext(QOpenGLContext *context); + const GraphicsApiFilterData *contextInfo() const; + SubmissionContext *submissionContext() const; + + inline RenderStateSet *defaultRenderState() const { return m_defaultRenderStateSet; } + + QList<QPair<QObject*, QMouseEvent>> pendingPickingEvents() const; + QList<QKeyEvent> pendingKeyEvents() const; + + void addRenderCaptureSendRequest(Qt3DCore::QNodeId nodeId); + const QVector<Qt3DCore::QNodeId> takePendingRenderCaptureSendRequests(); + + void enqueueRenderView(RenderView *renderView, int submitOrder); + bool isReadyToSubmit(); + + QVariant executeCommand(const QStringList &args) override; + void setOffscreenSurfaceHelper(OffscreenSurfaceHelper *helper) override; + QSurfaceFormat format() override; + + struct ViewSubmissionResultData + { + ViewSubmissionResultData() + : lastBoundFBOId(0) + , surface(nullptr) + {} + + uint lastBoundFBOId; + QSurface *surface; + }; + + ViewSubmissionResultData submitRenderViews(const QVector<Render::RenderView *> &renderViews); + + RendererCache *cache() { return &m_cache; } + +#ifdef QT3D_RENDER_UNIT_TESTS +public: +#else + +private: +#endif + bool canRender() const; + + Qt3DCore::QServiceLocator *m_services; + NodeManagers *m_nodesManager; + + // Frame graph root + Qt3DCore::QNodeId m_frameGraphRootUuid; + + Entity *m_renderSceneRoot; + + // Fail safe values that we can use if a RenderCommand + // is missing a shader + RenderStateSet *m_defaultRenderStateSet; + ShaderParameterPack m_defaultUniformPack; + + QScopedPointer<SubmissionContext> m_submissionContext; + QSurfaceFormat m_format; + + RenderQueue *m_renderQueue; + QScopedPointer<RenderThread> m_renderThread; + QScopedPointer<CommandThread> m_commandThread; + QScopedPointer<VSyncFrameAdvanceService> m_vsyncFrameAdvanceService; + + QSemaphore m_submitRenderViewsSemaphore; + QSemaphore m_waitForInitializationToBeCompleted; + + QAtomicInt m_running; + + QScopedPointer<PickEventFilter> m_pickEventFilter; + + QVector<Attribute *> m_dirtyAttributes; + 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; + + QAtomicInt m_lastFrameCorrect; + QOpenGLContext *m_glContext; + QOpenGLContext *m_shareContext; + mutable QMutex m_shareContextMutex; + ShaderCache *m_shaderCache; + PickBoundingVolumeJobPtr m_pickBoundingVolumeJob; + RayCastingJobPtr m_rayCastingJob; + + qint64 m_time; + + RenderSettings *m_settings; + + UpdateShaderDataTransformJobPtr m_updateShaderDataTransformJob; + FrameCleanupJobPtr m_cleanupJob; + UpdateWorldTransformJobPtr m_worldTransformJob; + ExpandBoundingVolumeJobPtr m_expandBoundingVolumeJob; + CalculateBoundingVolumeJobPtr m_calculateBoundingVolumeJob; + UpdateWorldBoundingVolumeJobPtr m_updateWorldBoundingVolumeJob; + UpdateTreeEnabledJobPtr m_updateTreeEnabledJob; + SendRenderCaptureJobPtr m_sendRenderCaptureJob; + SendBufferCaptureJobPtr m_sendBufferCaptureJob; + UpdateSkinningPaletteJobPtr m_updateSkinningPaletteJob; + UpdateLevelOfDetailJobPtr m_updateLevelOfDetailJob; + UpdateMeshTriangleListJobPtr m_updateMeshTriangleListJob; + FilterCompatibleTechniqueJobPtr m_filterCompatibleTechniqueJob; + + QVector<Qt3DCore::QNodeId> m_pendingRenderCaptureSendRequests; + + void performDraw(RenderCommand *command); + void performCompute(const RenderView *rv, RenderCommand *command); + void createOrUpdateVAO(RenderCommand *command, + HVao *previousVAOHandle, + OpenGLVertexArrayObject **vao); + + GenericLambdaJobPtr<std::function<void ()>> m_bufferGathererJob; + GenericLambdaJobPtr<std::function<void ()>> m_vaoGathererJob; + GenericLambdaJobPtr<std::function<void ()>> m_textureGathererJob; + IntrospectShadersJobPtr m_introspectShaderJob; + + SynchronizerJobPtr m_syncTextureLoadingJob; + + void lookForAbandonedVaos(); + void lookForDirtyBuffers(); + void lookForDownloadableBuffers(); + void lookForDirtyTextures(); + void reloadDirtyShaders(); + + QMutex m_abandonedVaosMutex; + QVector<HVao> m_abandonedVaos; + + QVector<HBuffer> m_dirtyBuffers; + QVector<HBuffer> m_downloadableBuffers; + QVector<HTexture> m_dirtyTextures; + + bool m_ownedContext; + + OffscreenSurfaceHelper *m_offscreenHelper; + QMutex m_offscreenSurfaceMutex; + +#if QT_CONFIG(qt3d_profile_jobs) + QScopedPointer<Qt3DRender::Debug::CommandExecuter> m_commandExecuter; + friend class Qt3DRender::Debug::CommandExecuter; +#endif + + QMetaObject::Connection m_contextConnection; + RendererCache m_cache; +}; + +} // namespace Render +} // namespace Qt3DRender + +QT_END_NAMESPACE + +#endif // QT3DRENDER_RENDER_RENDERER_H diff --git a/src/render/renderers/opengl/renderer/renderercache_p.h b/src/render/renderers/opengl/renderer/renderercache_p.h new file mode 100644 index 000000000..2aa50d131 --- /dev/null +++ b/src/render/renderers/opengl/renderer/renderercache_p.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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: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_RENDERERCACHE_P_H +#define QT3DRENDER_RENDER_RENDERERCACHE_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 <Qt3DRender/QFrameGraphNode> + +#include <Qt3DRender/private/entity_p.h> +#include <Qt3DRender/private/renderviewjobutils_p.h> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { + +namespace Render { + +struct RendererCache +{ + struct LeafNodeData + { + QVector<Entity *> filterEntitiesByLayer; + MaterialParameterGathererData materialParameterGatherer; + }; + + QHash<FrameGraphNode *, LeafNodeData> leafNodeCache; + + QMutex *mutex() { return &m_mutex; } + +private: + QMutex m_mutex; +}; + +} // namespace Render + +} // namespace Qt3DRender + +QT_END_NAMESPACE + +#endif // QT3DRENDER_RENDER_RENDERERCACHE_P_H diff --git a/src/render/renderers/opengl/renderer/renderqueue.cpp b/src/render/renderers/opengl/renderer/renderqueue.cpp new file mode 100644 index 000000000..bd9d3ee59 --- /dev/null +++ b/src/render/renderers/opengl/renderer/renderqueue.cpp @@ -0,0 +1,133 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "renderqueue_p.h" +#include <Qt3DRender/private/renderview_p.h> +#include <QThread> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { + +namespace Render { + +RenderQueue::RenderQueue() + : m_noRender(false) + , m_wasReset(true) + , m_targetRenderViewCount(0) + , m_currentRenderViewCount(0) + , m_currentWorkQueue(1) +{ +} + +int RenderQueue::currentRenderViewCount() const +{ + return m_currentRenderViewCount; +} + +/* + * In case the framegraph changed or when the current number of render queue + * needs to be reset. + */ +void RenderQueue::reset() +{ + m_currentRenderViewCount = 0; + m_targetRenderViewCount = 0; + m_currentWorkQueue.clear(); + m_noRender = false; + m_wasReset = true; +} + +void RenderQueue::setNoRender() +{ + Q_ASSERT(m_targetRenderViewCount == 0); + m_noRender = true; +} + +/* + * Queue up a RenderView for the frame being built. + * Thread safe as this is called from the renderer which is locked. + * Returns true if the renderView is complete + */ +bool RenderQueue::queueRenderView(RenderView *renderView, uint submissionOrderIndex) +{ + Q_ASSERT(!m_noRender); + m_currentWorkQueue[submissionOrderIndex] = renderView; + ++m_currentRenderViewCount; + Q_ASSERT(m_currentRenderViewCount <= m_targetRenderViewCount); + return isFrameQueueComplete(); +} + +/* + * Called by the Rendering Thread to retrieve the a frame queue to render. + * A call to reset is required after rendering of the frame. Otherwise under some + * conditions the current but then invalidated frame queue could be reused. + */ +QVector<RenderView *> RenderQueue::nextFrameQueue() +{ + return m_currentWorkQueue; +} + +/* + * Sets the number \a targetRenderViewCount of RenderView objects that make up a frame. + */ +void RenderQueue::setTargetRenderViewCount(int targetRenderViewCount) +{ + Q_ASSERT(!m_noRender); + m_targetRenderViewCount = targetRenderViewCount; + m_currentWorkQueue.resize(targetRenderViewCount); + m_wasReset = false; +} + +/* + * Returns true if all the RenderView objects making up the current frame have been queued. + * Returns false otherwise. + * \note a frameQueue or size 0 is considered incomplete. + */ +bool RenderQueue::isFrameQueueComplete() const +{ + return (m_noRender + || (m_targetRenderViewCount > 0 && m_targetRenderViewCount == m_currentRenderViewCount)); +} + +} // namespace Render + +} // namespace Qt3DRender + +QT_END_NAMESPACE diff --git a/src/render/renderers/opengl/renderer/renderqueue_p.h b/src/render/renderers/opengl/renderer/renderqueue_p.h new file mode 100644 index 000000000..e565115f2 --- /dev/null +++ b/src/render/renderers/opengl/renderer/renderqueue_p.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT3DRENDER_RENDER_RENDERQUEUE_H +#define QT3DRENDER_RENDER_RENDERQUEUE_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 <QVector> +#include <QtGlobal> +#include <QMutex> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { + +namespace Render { + +class RenderView; + +class Q_AUTOTEST_EXPORT RenderQueue +{ +public: + RenderQueue(); + + void setTargetRenderViewCount(int targetRenderViewCount); + int targetRenderViewCount() const { return m_targetRenderViewCount; } + int currentRenderViewCount() const; + bool isFrameQueueComplete() const; + + bool queueRenderView(RenderView *renderView, uint submissionOrderIndex); + QVector<RenderView *> nextFrameQueue(); + void reset(); + + void setNoRender(); + inline bool isNoRender() const { return m_noRender; } + + inline bool wasReset() const { return m_wasReset; } + + inline QMutex *mutex() { return &m_mutex; } + +private: + bool m_noRender; + bool m_wasReset; + int m_targetRenderViewCount; + int m_currentRenderViewCount; + QVector<RenderView *> m_currentWorkQueue; + QMutex m_mutex; +}; + +} // namespace Render + +} // namespace Qt3DRender + +QT_END_NAMESPACE + +#endif // QT3DRENDER_RENDER_RENDERQUEUE_H diff --git a/src/render/renderers/opengl/renderer/renderview.cpp b/src/render/renderers/opengl/renderer/renderview.cpp new file mode 100644 index 000000000..c29448570 --- /dev/null +++ b/src/render/renderers/opengl/renderer/renderview.cpp @@ -0,0 +1,1108 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB). +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "renderview_p.h" +#include <Qt3DRender/qmaterial.h> +#include <Qt3DRender/qrenderaspect.h> +#include <Qt3DRender/qrendertarget.h> +#include <Qt3DRender/qabstractlight.h> +#include <Qt3DRender/private/sphere_p.h> + +#include <Qt3DRender/private/cameraselectornode_p.h> +#include <Qt3DRender/private/framegraphnode_p.h> +#include <Qt3DRender/private/layerfilternode_p.h> +#include <Qt3DRender/private/qparameter_p.h> +#include <Qt3DRender/private/cameralens_p.h> +#include <Qt3DRender/private/rendercommand_p.h> +#include <Qt3DRender/private/effect_p.h> +#include <Qt3DRender/private/entity_p.h> +#include <Qt3DRender/private/renderer_p.h> +#include <Qt3DRender/private/nodemanagers_p.h> +#include <Qt3DRender/private/layer_p.h> +#include <Qt3DRender/private/renderlogging_p.h> +#include <Qt3DRender/private/renderpassfilternode_p.h> +#include <Qt3DRender/private/renderpass_p.h> +#include <Qt3DRender/private/geometryrenderer_p.h> +#include <Qt3DRender/private/renderstateset_p.h> +#include <Qt3DRender/private/techniquefilternode_p.h> +#include <Qt3DRender/private/viewportnode_p.h> +#include <Qt3DRender/private/buffermanager_p.h> +#include <Qt3DRender/private/geometryrenderermanager_p.h> +#include <Qt3DRender/private/rendercapture_p.h> +#include <Qt3DRender/private/buffercapture_p.h> +#include <Qt3DRender/private/stringtoint_p.h> +#include <Qt3DCore/qentity.h> +#include <QtGui/qsurface.h> +#include <algorithm> + +#include <QDebug> +#if defined(QT3D_RENDER_VIEW_JOB_TIMINGS) +#include <QElapsedTimer> +#endif + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { +namespace Render { + + +namespace { + +// register our QNodeId's as a metatype during program loading +const int Q_DECL_UNUSED qNodeIdTypeId = qMetaTypeId<Qt3DCore::QNodeId>(); + +const int MAX_LIGHTS = 8; + +#define LIGHT_POSITION_NAME QLatin1String(".position") +#define LIGHT_TYPE_NAME QLatin1String(".type") +#define LIGHT_COLOR_NAME QLatin1String(".color") +#define LIGHT_INTENSITY_NAME QLatin1String(".intensity") + +int LIGHT_COUNT_NAME_ID = 0; +int LIGHT_POSITION_NAMES[MAX_LIGHTS]; +int LIGHT_TYPE_NAMES[MAX_LIGHTS]; +int LIGHT_COLOR_NAMES[MAX_LIGHTS]; +int LIGHT_INTENSITY_NAMES[MAX_LIGHTS]; +QString LIGHT_STRUCT_NAMES[MAX_LIGHTS]; + +} // anonymous namespace + +bool wasInitialized = false; +RenderView::StandardUniformsNameToTypeHash RenderView::ms_standardUniformSetters; + + +RenderView::StandardUniformsNameToTypeHash RenderView::initializeStandardUniformSetters() +{ + RenderView::StandardUniformsNameToTypeHash setters; + + setters.insert(StringToInt::lookupId(QLatin1String("modelMatrix")), ModelMatrix); + setters.insert(StringToInt::lookupId(QLatin1String("viewMatrix")), ViewMatrix); + setters.insert(StringToInt::lookupId(QLatin1String("projectionMatrix")), ProjectionMatrix); + setters.insert(StringToInt::lookupId(QLatin1String("modelView")), ModelViewMatrix); + setters.insert(StringToInt::lookupId(QLatin1String("viewProjectionMatrix")), ViewProjectionMatrix); + setters.insert(StringToInt::lookupId(QLatin1String("modelViewProjection")), ModelViewProjectionMatrix); + setters.insert(StringToInt::lookupId(QLatin1String("mvp")), ModelViewProjectionMatrix); + setters.insert(StringToInt::lookupId(QLatin1String("inverseModelMatrix")), InverseModelMatrix); + setters.insert(StringToInt::lookupId(QLatin1String("inverseViewMatrix")), InverseViewMatrix); + setters.insert(StringToInt::lookupId(QLatin1String("inverseProjectionMatrix")), InverseProjectionMatrix); + setters.insert(StringToInt::lookupId(QLatin1String("inverseModelView")), InverseModelViewMatrix); + setters.insert(StringToInt::lookupId(QLatin1String("inverseViewProjectionMatrix")), InverseViewProjectionMatrix); + setters.insert(StringToInt::lookupId(QLatin1String("inverseModelViewProjection")), InverseModelViewProjectionMatrix); + setters.insert(StringToInt::lookupId(QLatin1String("modelNormalMatrix")), ModelNormalMatrix); + setters.insert(StringToInt::lookupId(QLatin1String("modelViewNormal")), ModelViewNormalMatrix); + setters.insert(StringToInt::lookupId(QLatin1String("viewportMatrix")), ViewportMatrix); + setters.insert(StringToInt::lookupId(QLatin1String("inverseViewportMatrix")), InverseViewportMatrix); + setters.insert(StringToInt::lookupId(QLatin1String("aspectRatio")), AspectRatio); + setters.insert(StringToInt::lookupId(QLatin1String("exposure")), Exposure); + setters.insert(StringToInt::lookupId(QLatin1String("gamma")), Gamma); + setters.insert(StringToInt::lookupId(QLatin1String("time")), Time); + setters.insert(StringToInt::lookupId(QLatin1String("eyePosition")), EyePosition); + setters.insert(StringToInt::lookupId(QLatin1String("skinningPalette[0]")), SkinningPalette); + + return setters; +} + +// TODO: Move this somewhere global where GraphicsContext::setViewport() can use it too +static QRectF resolveViewport(const QRectF &fractionalViewport, const QSize &surfaceSize) +{ + return QRectF(fractionalViewport.x() * surfaceSize.width(), + (1.0 - fractionalViewport.y() - fractionalViewport.height()) * surfaceSize.height(), + fractionalViewport.width() * surfaceSize.width(), + fractionalViewport.height() * surfaceSize.height()); +} + +static Matrix4x4 getProjectionMatrix(const CameraLens *lens) +{ + if (!lens) + qWarning() << "[Qt3D Renderer] No Camera Lens found. Add a CameraSelector to your Frame Graph or make sure that no entities will be rendered."; + return lens ? lens->projection() : Matrix4x4(); +} + +UniformValue RenderView::standardUniformValue(RenderView::StandardUniform standardUniformType, + Entity *entity, + const Matrix4x4 &model) const +{ + switch (standardUniformType) { + case ModelMatrix: + return UniformValue(model); + case ViewMatrix: + return UniformValue(m_data.m_viewMatrix); + case ProjectionMatrix: + return UniformValue(getProjectionMatrix(m_data.m_renderCameraLens)); + case ModelViewMatrix: + return UniformValue(m_data.m_viewMatrix * model); + case ViewProjectionMatrix: + return UniformValue(getProjectionMatrix(m_data.m_renderCameraLens) * m_data.m_viewMatrix); + case ModelViewProjectionMatrix: + return UniformValue(m_data.m_viewProjectionMatrix * model); + case InverseModelMatrix: + return UniformValue(model.inverted()); + case InverseViewMatrix: + return UniformValue(m_data.m_viewMatrix.inverted()); + case InverseProjectionMatrix: { + return UniformValue(getProjectionMatrix(m_data.m_renderCameraLens).inverted()); + } + case InverseModelViewMatrix: + return UniformValue((m_data.m_viewMatrix * model).inverted()); + case InverseViewProjectionMatrix: { + const Matrix4x4 viewProjectionMatrix = getProjectionMatrix(m_data.m_renderCameraLens) * m_data.m_viewMatrix; + return UniformValue(viewProjectionMatrix.inverted()); + } + case InverseModelViewProjectionMatrix: + return UniformValue((m_data.m_viewProjectionMatrix * model).inverted()); + case ModelNormalMatrix: + return UniformValue(convertToQMatrix4x4(model).normalMatrix()); + case ModelViewNormalMatrix: + return UniformValue(convertToQMatrix4x4(m_data.m_viewMatrix * model).normalMatrix()); + case ViewportMatrix: { + QMatrix4x4 viewportMatrix; + // TO DO: Implement on Matrix4x4 + viewportMatrix.viewport(resolveViewport(m_viewport, m_surfaceSize)); + return UniformValue(Matrix4x4(viewportMatrix)); + } + case InverseViewportMatrix: { + QMatrix4x4 viewportMatrix; + // TO DO: Implement on Matrix4x4 + viewportMatrix.viewport(resolveViewport(m_viewport, m_surfaceSize)); + return UniformValue(Matrix4x4(viewportMatrix.inverted())); + } + case AspectRatio: + return float(m_surfaceSize.width()) / float(m_surfaceSize.height()); + case Exposure: + return UniformValue(m_data.m_renderCameraLens ? m_data.m_renderCameraLens->exposure() : 0.0f); + case Gamma: + return UniformValue(m_gamma); + case Time: + return UniformValue(float(m_renderer->time() / 1000000000.0f)); + case EyePosition: + return UniformValue(m_data.m_eyePos); + case SkinningPalette: { + const Armature *armature = entity->renderComponent<Armature>(); + if (!armature) { + qCWarning(Jobs, "Requesting skinningPalette uniform but no armature set on entity"); + return UniformValue(); + } + return armature->skinningPaletteUniform(); + } + default: + Q_UNREACHABLE(); + return UniformValue(); + } +} + +RenderView::RenderView() + : m_isDownloadBuffersEnable(false) + , m_hasBlitFramebufferInfo(false) + , m_renderer(nullptr) + , m_devicePixelRatio(1.) + , m_viewport(QRectF(0.0f, 0.0f, 1.0f, 1.0f)) + , m_gamma(2.2f) + , m_surface(nullptr) + , m_clearBuffer(QClearBuffers::None) + , m_stateSet(nullptr) + , m_noDraw(false) + , m_compute(false) + , m_frustumCulling(false) + , m_memoryBarrier(QMemoryBarrier::None) + , m_environmentLight(nullptr) +{ + m_workGroups[0] = 1; + m_workGroups[1] = 1; + m_workGroups[2] = 1; + + if (Q_UNLIKELY(!wasInitialized)) { + // Needed as we can control the init order of static/global variables across compile units + // and this hash relies on the static StringToInt class + wasInitialized = true; + RenderView::ms_standardUniformSetters = RenderView::initializeStandardUniformSetters(); + LIGHT_COUNT_NAME_ID = StringToInt::lookupId(QLatin1String("lightCount")); + for (int i = 0; i < MAX_LIGHTS; ++i) { + Q_STATIC_ASSERT_X(MAX_LIGHTS < 10, "can't use the QChar trick anymore"); + LIGHT_STRUCT_NAMES[i] = QLatin1String("lights[") + QLatin1Char(char('0' + i)) + QLatin1Char(']'); + LIGHT_POSITION_NAMES[i] = StringToInt::lookupId(LIGHT_STRUCT_NAMES[i] + LIGHT_POSITION_NAME); + LIGHT_TYPE_NAMES[i] = StringToInt::lookupId(LIGHT_STRUCT_NAMES[i] + LIGHT_TYPE_NAME); + LIGHT_COLOR_NAMES[i] = StringToInt::lookupId(LIGHT_STRUCT_NAMES[i] + LIGHT_COLOR_NAME); + LIGHT_INTENSITY_NAMES[i] = StringToInt::lookupId(LIGHT_STRUCT_NAMES[i] + LIGHT_INTENSITY_NAME); + } + } +} + +RenderView::~RenderView() +{ + delete m_stateSet; + for (RenderCommand *command : qAsConst(m_commands)) { + delete command->m_stateSet; + delete command; + } +} + +namespace { + +template<int SortType> +struct AdjacentSubRangeFinder +{ + static bool adjacentSubRange(RenderCommand *, RenderCommand *) + { + Q_UNREACHABLE(); + return false; + } +}; + +template<> +struct AdjacentSubRangeFinder<QSortPolicy::StateChangeCost> +{ + static bool adjacentSubRange(RenderCommand *a, RenderCommand *b) + { + return a->m_changeCost == b->m_changeCost; + } +}; + +template<> +struct AdjacentSubRangeFinder<QSortPolicy::BackToFront> +{ + static bool adjacentSubRange(RenderCommand *a, RenderCommand *b) + { + return a->m_depth == b->m_depth; + } +}; + +template<> +struct AdjacentSubRangeFinder<QSortPolicy::Material> +{ + static bool adjacentSubRange(RenderCommand *a, RenderCommand *b) + { + return a->m_shaderDna == b->m_shaderDna; + } +}; + +template<> +struct AdjacentSubRangeFinder<QSortPolicy::FrontToBack> +{ + static bool adjacentSubRange(RenderCommand *a, RenderCommand *b) + { + return a->m_depth == b->m_depth; + } +}; + +template<typename Predicate> +int advanceUntilNonAdjacent(const QVector<RenderCommand *> &commands, + const int beg, const int end, Predicate pred) +{ + int i = beg + 1; + while (i < end) { + if (!pred(*(commands.begin() + beg), *(commands.begin() + i))) + break; + ++i; + } + return i; +} + + +using CommandIt = QVector<RenderCommand *>::iterator; + +template<int SortType> +struct SubRangeSorter +{ + static void sortSubRange(CommandIt begin, const CommandIt end) + { + Q_UNUSED(begin); + Q_UNUSED(end); + Q_UNREACHABLE(); + } +}; + +template<> +struct SubRangeSorter<QSortPolicy::StateChangeCost> +{ + static void sortSubRange(CommandIt begin, const CommandIt end) + { + std::stable_sort(begin, end, [] (RenderCommand *a, RenderCommand *b) { + return a->m_changeCost > b->m_changeCost; + }); + } +}; + +template<> +struct SubRangeSorter<QSortPolicy::BackToFront> +{ + static void sortSubRange(CommandIt begin, const CommandIt end) + { + std::stable_sort(begin, end, [] (RenderCommand *a, RenderCommand *b) { + return a->m_depth > b->m_depth; + }); + } +}; + +template<> +struct SubRangeSorter<QSortPolicy::Material> +{ + static void sortSubRange(CommandIt begin, const CommandIt end) + { + // First we sort by shaderDNA + std::stable_sort(begin, end, [] (RenderCommand *a, RenderCommand *b) { + return a->m_shaderDna > b->m_shaderDna; + }); + } +}; + +template<> +struct SubRangeSorter<QSortPolicy::FrontToBack> +{ + static void sortSubRange(CommandIt begin, const CommandIt end) + { + std::stable_sort(begin, end, [] (RenderCommand *a, RenderCommand *b) { + return a->m_depth < b->m_depth; + }); + } +}; + +int findSubRange(const QVector<RenderCommand *> &commands, + const int begin, const int end, + const QSortPolicy::SortType sortType) +{ + switch (sortType) { + case QSortPolicy::StateChangeCost: + return advanceUntilNonAdjacent(commands, begin, end, AdjacentSubRangeFinder<QSortPolicy::StateChangeCost>::adjacentSubRange); + case QSortPolicy::BackToFront: + return advanceUntilNonAdjacent(commands, begin, end, AdjacentSubRangeFinder<QSortPolicy::BackToFront>::adjacentSubRange); + case QSortPolicy::Material: + return advanceUntilNonAdjacent(commands, begin, end, AdjacentSubRangeFinder<QSortPolicy::Material>::adjacentSubRange); + case QSortPolicy::FrontToBack: + return advanceUntilNonAdjacent(commands, begin, end, AdjacentSubRangeFinder<QSortPolicy::FrontToBack>::adjacentSubRange); + default: + Q_UNREACHABLE(); + return end; + } +} + +void sortByMaterial(QVector<RenderCommand *> &commands, int begin, const int end) +{ + // We try to arrange elements so that their rendering cost is minimized for a given shader + int rangeEnd = advanceUntilNonAdjacent(commands, begin, end, AdjacentSubRangeFinder<QSortPolicy::Material>::adjacentSubRange); + while (begin != end) { + if (begin + 1 < rangeEnd) { + std::stable_sort(commands.begin() + begin + 1, commands.begin() + rangeEnd, [] (RenderCommand *a, RenderCommand *b){ + return a->m_material.handle() < b->m_material.handle(); + }); + } + begin = rangeEnd; + rangeEnd = advanceUntilNonAdjacent(commands, begin, end, AdjacentSubRangeFinder<QSortPolicy::Material>::adjacentSubRange); + } +} + +void sortCommandRange(QVector<RenderCommand *> &commands, int begin, const int end, const int level, + const QVector<Qt3DRender::QSortPolicy::SortType> &sortingTypes) +{ + if (level >= sortingTypes.size()) + return; + + switch (sortingTypes.at(level)) { + case QSortPolicy::StateChangeCost: + SubRangeSorter<QSortPolicy::StateChangeCost>::sortSubRange(commands.begin() + begin, commands.begin() + end); + break; + case QSortPolicy::BackToFront: + SubRangeSorter<QSortPolicy::BackToFront>::sortSubRange(commands.begin() + begin, commands.begin() + end); + break; + case QSortPolicy::Material: + // Groups all same shader DNA together + SubRangeSorter<QSortPolicy::Material>::sortSubRange(commands.begin() + begin, commands.begin() + end); + // Group all same material together (same parameters most likely) + sortByMaterial(commands, begin, end); + break; + case QSortPolicy::FrontToBack: + SubRangeSorter<QSortPolicy::FrontToBack>::sortSubRange(commands.begin() + begin, commands.begin() + end); + break; + default: + Q_UNREACHABLE(); + } + + // For all sub ranges of adjacent item for sortType[i] + // Perform filtering with sortType[i + 1] + int rangeEnd = findSubRange(commands, begin, end, sortingTypes.at(level)); + while (begin != end) { + sortCommandRange(commands, begin, rangeEnd, level + 1, sortingTypes); + begin = rangeEnd; + rangeEnd = findSubRange(commands, begin, end, sortingTypes.at(level)); + } +} + +} // anonymous + +void RenderView::sort() +{ + sortCommandRange(m_commands, 0, m_commands.size(), 0, m_data.m_sortingTypes); + + // For RenderCommand with the same shader + // We compute the adjacent change cost + + // Minimize uniform changes + int i = 0; + while (i < m_commands.size()) { + int j = i; + + // Advance while commands share the same shader + while (i < m_commands.size() && m_commands[j]->m_shaderDna == m_commands[i]->m_shaderDna) + ++i; + + if (i - j > 0) { // Several commands have the same shader, so we minimize uniform changes + PackUniformHash cachedUniforms = m_commands[j++]->m_parameterPack.uniforms(); + + while (j < i) { + // We need the reference here as we are modifying the original container + // not the copy + PackUniformHash &uniforms = m_commands.at(j)->m_parameterPack.m_uniforms; + PackUniformHash::iterator it = uniforms.begin(); + const PackUniformHash::iterator end = uniforms.end(); + + while (it != end) { + // We are comparing the values: + // - raw uniform values + // - the texture Node id if the uniform represents a texture + // since all textures are assigned texture units before the RenderCommands + // sharing the same material (shader) are rendered, we can't have the case + // where two uniforms, referencing the same texture eventually have 2 different + // texture unit values + const UniformValue refValue = cachedUniforms.value(it.key()); + if (it.value() == refValue) { + it = uniforms.erase(it); + } else { + cachedUniforms.insert(it.key(), it.value()); + ++it; + } + } + ++j; + } + } + } +} + +void RenderView::setRenderer(Renderer *renderer) +{ + m_renderer = renderer; + m_manager = renderer->nodeManagers(); +} + +void RenderView::addClearBuffers(const ClearBuffers *cb) { + QClearBuffers::BufferTypeFlags type = cb->type(); + + if (type & QClearBuffers::StencilBuffer) { + m_clearStencilValue = cb->clearStencilValue(); + m_clearBuffer |= QClearBuffers::StencilBuffer; + } + if (type & QClearBuffers::DepthBuffer) { + m_clearDepthValue = cb->clearDepthValue(); + m_clearBuffer |= QClearBuffers::DepthBuffer; + } + // keep track of global ClearColor (if set) and collect all DrawBuffer-specific + // ClearColors + if (type & QClearBuffers::ColorBuffer) { + ClearBufferInfo clearBufferInfo; + clearBufferInfo.clearColor = cb->clearColor(); + + if (cb->clearsAllColorBuffers()) { + m_globalClearColorBuffer = clearBufferInfo; + m_clearBuffer |= QClearBuffers::ColorBuffer; + } else { + if (cb->bufferId()) { + const RenderTargetOutput *targetOutput = m_manager->attachmentManager()->lookupResource(cb->bufferId()); + if (targetOutput) { + clearBufferInfo.attchmentPoint = targetOutput->point(); + // Note: a job is later performed to find the drawIndex from the buffer attachment point + // using the AttachmentPack + m_specificClearColorBuffers.push_back(clearBufferInfo); + } + } + } + } +} + +// If we are there, we know that entity had a GeometryRenderer + Material +QVector<RenderCommand *> RenderView::buildDrawRenderCommands(const QVector<Entity *> &entities) const +{ + // Note: since many threads can be building render commands + // we need to ensure that the UniformBlockValueBuilder they are using + // is only accessed from the same thread + UniformBlockValueBuilder *builder = new UniformBlockValueBuilder(); + builder->shaderDataManager = m_manager->shaderDataManager(); + builder->textureManager = m_manager->textureManager(); + m_localData.setLocalData(builder); + + QVector<RenderCommand *> commands; + commands.reserve(entities.size()); + + for (Entity *entity : entities) { + GeometryRenderer *geometryRenderer = nullptr; + HGeometryRenderer geometryRendererHandle = entity->componentHandle<GeometryRenderer>(); + + // There is a geometry renderer with geometry + if ((geometryRenderer = m_manager->geometryRendererManager()->data(geometryRendererHandle)) != nullptr + && geometryRenderer->isEnabled() + && !geometryRenderer->geometryId().isNull()) { + + const Qt3DCore::QNodeId materialComponentId = entity->componentUuid<Material>(); + const HMaterial materialHandle = entity->componentHandle<Material>(); + const QVector<RenderPassParameterData> renderPassData = m_parameters.value(materialComponentId); + HGeometry geometryHandle = m_manager->lookupHandle<Geometry, GeometryManager, HGeometry>(geometryRenderer->geometryId()); + Geometry *geometry = m_manager->data<Geometry, GeometryManager>(geometryHandle); + + // 1 RenderCommand per RenderPass pass on an Entity with a Mesh + for (const RenderPassParameterData &passData : renderPassData) { + // Add the RenderPass Parameters + RenderCommand *command = new RenderCommand(); + + // Project the camera-to-object-center vector onto the camera + // view vector. This gives a depth value suitable as the key + // for BackToFront sorting. + command->m_depth = Vector3D::dotProduct(entity->worldBoundingVolume()->center() - m_data.m_eyePos, m_data.m_eyeViewDir); + + command->m_geometry = geometryHandle; + command->m_geometryRenderer = geometryRendererHandle; + command->m_material = materialHandle; + // For RenderPass based states we use the globally set RenderState + // if no renderstates are defined as part of the pass. That means: + // RenderPass { renderStates: [] } will use the states defined by + // StateSet in the FrameGraph + RenderPass *pass = passData.pass; + if (pass->hasRenderStates()) { + command->m_stateSet = new RenderStateSet(); + addToRenderStateSet(command->m_stateSet, pass->renderStates(), m_manager->renderStateManager()); + + // Merge per pass stateset with global stateset + // so that the local stateset only overrides + if (m_stateSet != nullptr) + command->m_stateSet->merge(m_stateSet); + command->m_changeCost = m_renderer->defaultRenderState()->changeCost(command->m_stateSet); + } + + // Pick which lights to take in to account. + // For now decide based on the distance by taking the MAX_LIGHTS closest lights. + // Replace with more sophisticated mechanisms later. + // Copy vector so that we can sort it concurrently and we only want to sort the one for the current command + QVector<LightSource> lightSources = m_lightSources; + if (lightSources.size() > 1) { + const Vector3D entityCenter = entity->worldBoundingVolume()->center(); + std::sort(lightSources.begin(), lightSources.end(), + [&] (const LightSource &a, const LightSource &b) { + const float distA = entityCenter.distanceToPoint(a.entity->worldBoundingVolume()->center()); + const float distB = entityCenter.distanceToPoint(b.entity->worldBoundingVolume()->center()); + return distA < distB; + }); + } + + ParameterInfoList globalParameters = passData.parameterInfo; + // setShaderAndUniforms can initialize a localData + // make sure this is cleared before we leave this function + setShaderAndUniforms(command, + pass, + globalParameters, + entity, + lightSources.mid(0, std::max(lightSources.size(), MAX_LIGHTS)), + m_environmentLight); + + // Store all necessary information for actual drawing if command is valid + command->m_isValid = !command->m_attributes.empty(); + if (command->m_isValid) { + // Update the draw command with what's going to be needed for the drawing + uint primitiveCount = geometryRenderer->vertexCount(); + uint estimatedCount = 0; + Attribute *indexAttribute = nullptr; + + const QVector<Qt3DCore::QNodeId> attributeIds = geometry->attributes(); + for (Qt3DCore::QNodeId attributeId : attributeIds) { + Attribute *attribute = m_manager->attributeManager()->lookupResource(attributeId); + if (attribute->attributeType() == QAttribute::IndexAttribute) + indexAttribute = attribute; + else if (command->m_attributes.contains(attribute->nameId())) + estimatedCount = qMax(attribute->count(), estimatedCount); + } + + // Update the draw command with all the information required for the drawing + command->m_drawIndexed = (indexAttribute != nullptr); + if (command->m_drawIndexed) { + command->m_indexAttributeDataType = GraphicsContext::glDataTypeFromAttributeDataType(indexAttribute->vertexBaseType()); + command->m_indexAttributeByteOffset = indexAttribute->byteOffset(); + } + + // Use the count specified by the GeometryRender + // If not specified use the indexAttribute count if present + // Otherwise tries to use the count from the attribute with the highest count + if (primitiveCount == 0) { + if (indexAttribute) + primitiveCount = indexAttribute->count(); + else + primitiveCount = estimatedCount; + } + + command->m_primitiveCount = primitiveCount; + command->m_primitiveType = geometryRenderer->primitiveType(); + command->m_primitiveRestartEnabled = geometryRenderer->primitiveRestartEnabled(); + command->m_restartIndexValue = geometryRenderer->restartIndexValue(); + command->m_firstInstance = geometryRenderer->firstInstance(); + command->m_instanceCount = geometryRenderer->instanceCount(); + command->m_firstVertex = geometryRenderer->firstVertex(); + command->m_indexOffset = geometryRenderer->indexOffset(); + command->m_verticesPerPatch = geometryRenderer->verticesPerPatch(); + } + + commands.append(command); + } + } + } + + // We reset the local data once we are done with it + m_localData.setLocalData(nullptr); + + return commands; +} + +QVector<RenderCommand *> RenderView::buildComputeRenderCommands(const QVector<Entity *> &entities) const +{ + // Note: since many threads can be building render commands + // we need to ensure that the UniformBlockValueBuilder they are using + // is only accessed from the same thread + UniformBlockValueBuilder *builder = new UniformBlockValueBuilder(); + builder->shaderDataManager = m_manager->shaderDataManager(); + builder->textureManager = m_manager->textureManager(); + m_localData.setLocalData(builder); + + // If the RenderView contains only a ComputeDispatch then it cares about + // A ComputeDispatch is also implicitely a NoDraw operation + // enabled flag + // layer component + // material/effect/technique/parameters/filters/ + QVector<RenderCommand *> commands; + commands.reserve(entities.size()); + for (Entity *entity : entities) { + ComputeCommand *computeJob = nullptr; + if ((computeJob = entity->renderComponent<ComputeCommand>()) != nullptr + && computeJob->isEnabled()) { + + const Qt3DCore::QNodeId materialComponentId = entity->componentUuid<Material>(); + const QVector<RenderPassParameterData> renderPassData = m_parameters.value(materialComponentId); + + // 1 RenderCommand per RenderPass pass on an Entity with a Mesh + for (const RenderPassParameterData &passData : renderPassData) { + // Add the RenderPass Parameters + ParameterInfoList globalParameters = passData.parameterInfo; + RenderPass *pass = passData.pass; + parametersFromParametersProvider(&globalParameters, m_manager->parameterManager(), pass); + + RenderCommand *command = new RenderCommand(); + command->m_type = RenderCommand::Compute; + command->m_workGroups[0] = std::max(m_workGroups[0], computeJob->x()); + command->m_workGroups[1] = std::max(m_workGroups[1], computeJob->y()); + command->m_workGroups[2] = std::max(m_workGroups[2], computeJob->z()); + setShaderAndUniforms(command, + pass, + globalParameters, + entity, + QVector<LightSource>(), + nullptr); + commands.append(command); + } + } + } + + // We reset the local data once we are done with it + m_localData.setLocalData(nullptr); + + return commands; +} + +void RenderView::updateMatrices() +{ + if (m_data.m_renderCameraNode && m_data.m_renderCameraLens && m_data.m_renderCameraLens->isEnabled()) { + const Matrix4x4 cameraWorld = *(m_data.m_renderCameraNode->worldTransform()); + setViewMatrix(m_data.m_renderCameraLens->viewMatrix(cameraWorld)); + + setViewProjectionMatrix(m_data.m_renderCameraLens->projection() * viewMatrix()); + //To get the eyePosition of the camera, we need to use the inverse of the + //camera's worldTransform matrix. + const Matrix4x4 inverseWorldTransform = viewMatrix().inverted(); + const Vector3D eyePosition(inverseWorldTransform.column(3)); + setEyePosition(eyePosition); + + // Get the viewing direction of the camera. Use the normal matrix to + // ensure non-uniform scale works too. + const QMatrix3x3 normalMat = convertToQMatrix4x4(m_data.m_viewMatrix).normalMatrix(); + // dir = normalize(QVector3D(0, 0, -1) * normalMat) + setEyeViewDirection(Vector3D(-normalMat(2, 0), -normalMat(2, 1), -normalMat(2, 2)).normalized()); + } +} + +void RenderView::setUniformValue(ShaderParameterPack &uniformPack, int nameId, const UniformValue &value) const +{ + // At this point a uniform value can only be a scalar type + // or a Qt3DCore::QNodeId corresponding to a Texture + // ShaderData/Buffers would be handled as UBO/SSBO and would therefore + // not be in the default uniform block + if (value.valueType() == UniformValue::NodeId) { + const Qt3DCore::QNodeId *nodeIds = value.constData<Qt3DCore::QNodeId>(); + + const int uniformArraySize = value.byteSize() / sizeof(Qt3DCore::QNodeId); + for (int i = 0; i < uniformArraySize; ++i) { + const Qt3DCore::QNodeId texId = nodeIds[i]; + const Texture *tex = m_manager->textureManager()->lookupResource(texId); + if (tex != nullptr) + uniformPack.setTexture(nameId, i, texId); + } + + UniformValue textureValue(uniformArraySize * sizeof(int), UniformValue::TextureValue); + std::fill(textureValue.data<int>(), textureValue.data<int>() + uniformArraySize, -1); + uniformPack.setUniform(nameId, textureValue); + } else { + uniformPack.setUniform(nameId, value); + } +} + +void RenderView::setStandardUniformValue(ShaderParameterPack &uniformPack, + int glslNameId, + int nameId, + Entity *entity, + const Matrix4x4 &worldTransform) const +{ + uniformPack.setUniform(glslNameId, standardUniformValue(ms_standardUniformSetters[nameId], entity, worldTransform)); +} + +void RenderView::setUniformBlockValue(ShaderParameterPack &uniformPack, + Shader *shader, + const ShaderUniformBlock &block, + const UniformValue &value) const +{ + Q_UNUSED(shader) + + if (value.valueType() == UniformValue::NodeId) { + + Buffer *buffer = nullptr; + if ((buffer = m_manager->bufferManager()->lookupResource(*value.constData<Qt3DCore::QNodeId>())) != nullptr) { + BlockToUBO uniformBlockUBO; + uniformBlockUBO.m_blockIndex = block.m_index; + uniformBlockUBO.m_bufferID = buffer->peerId(); + uniformBlockUBO.m_needsUpdate = false; + uniformPack.setUniformBuffer(std::move(uniformBlockUBO)); + // Buffer update to GL buffer will be done at render time + } + + + //ShaderData *shaderData = nullptr; + // if ((shaderData = m_manager->shaderDataManager()->lookupResource(value.value<Qt3DCore::QNodeId>())) != nullptr) { + // UBO are indexed by <ShaderId, ShaderDataId> so that a same QShaderData can be used among different shaders + // while still making sure that if they have a different layout everything will still work + // If two shaders define the same block with the exact same layout, in that case the UBO could be shared + // but how do we know that ? We'll need to compare ShaderUniformBlocks + + // Note: we assume that if a buffer is shared across multiple shaders + // then it implies that they share the same layout + + // Temporarly disabled + + // BufferShaderKey uboKey(shaderData->peerId(), + // shader->peerId()); + + // BlockToUBO uniformBlockUBO; + // uniformBlockUBO.m_blockIndex = block.m_index; + // uniformBlockUBO.m_shaderDataID = shaderData->peerId(); + // bool uboNeedsUpdate = false; + + // // build UBO at uboId if not created before + // if (!m_manager->glBufferManager()->contains(uboKey)) { + // m_manager->glBufferManager()->getOrCreateResource(uboKey); + // uboNeedsUpdate = true; + // } + + // // If shaderData has been updated (property has changed or one of the nested properties has changed) + // // foreach property defined in the QShaderData, we try to fill the value of the corresponding active uniform(s) + // // for all the updated properties (all the properties if the UBO was just created) + // if (shaderData->updateViewTransform(*m_data->m_viewMatrix) || uboNeedsUpdate) { + // // Clear previous values remaining in the hash + // m_data->m_uniformBlockBuilder.activeUniformNamesToValue.clear(); + // // Update only update properties if uboNeedsUpdate is true, otherwise update the whole block + // m_data->m_uniformBlockBuilder.updatedPropertiesOnly = uboNeedsUpdate; + // // Retrieve names and description of each active uniforms in the uniform block + // m_data->m_uniformBlockBuilder.uniforms = shader->activeUniformsForUniformBlock(block.m_index); + // // Builds the name-value map for the block + // m_data->m_uniformBlockBuilder.buildActiveUniformNameValueMapStructHelper(shaderData, block.m_name); + // if (!uboNeedsUpdate) + // shaderData->markDirty(); + // // copy the name-value map into the BlockToUBO + // uniformBlockUBO.m_updatedProperties = m_data->m_uniformBlockBuilder.activeUniformNamesToValue; + // uboNeedsUpdate = true; + // } + + // uniformBlockUBO.m_needsUpdate = uboNeedsUpdate; + // uniformPack.setUniformBuffer(std::move(uniformBlockUBO)); + // } + } +} + +void RenderView::setShaderStorageValue(ShaderParameterPack &uniformPack, + Shader *shader, + const ShaderStorageBlock &block, + const UniformValue &value) const +{ + Q_UNUSED(shader) + if (value.valueType() == UniformValue::NodeId) { + Buffer *buffer = nullptr; + if ((buffer = m_manager->bufferManager()->lookupResource(*value.constData<Qt3DCore::QNodeId>())) != nullptr) { + BlockToSSBO shaderStorageBlock; + shaderStorageBlock.m_blockIndex = block.m_index; + shaderStorageBlock.m_bufferID = buffer->peerId(); + uniformPack.setShaderStorageBuffer(shaderStorageBlock); + // Buffer update to GL buffer will be done at render time + } + } +} + +void RenderView::setDefaultUniformBlockShaderDataValue(ShaderParameterPack &uniformPack, Shader *shader, ShaderData *shaderData, const QString &structName) const +{ + UniformBlockValueBuilder *builder = m_localData.localData(); + builder->activeUniformNamesToValue.clear(); + + // Set the view matrix to be used to transform "Transformed" properties in the ShaderData + builder->viewMatrix = m_data.m_viewMatrix; + // Force to update the whole block + builder->updatedPropertiesOnly = false; + // Retrieve names and description of each active uniforms in the uniform block + builder->uniforms = shader->activeUniformsForUniformBlock(-1); + // Build name-value map for the block + builder->buildActiveUniformNameValueMapStructHelper(shaderData, structName); + // Set uniform values for each entrie of the block name-value map + QHash<int, QVariant>::const_iterator activeValuesIt = builder->activeUniformNamesToValue.constBegin(); + const QHash<int, QVariant>::const_iterator activeValuesEnd = builder->activeUniformNamesToValue.constEnd(); + + // TO DO: Make the ShaderData store UniformValue + while (activeValuesIt != activeValuesEnd) { + setUniformValue(uniformPack, activeValuesIt.key(), UniformValue::fromVariant(activeValuesIt.value())); + ++activeValuesIt; + } +} + +void RenderView::setShaderAndUniforms(RenderCommand *command, + RenderPass *rPass, + ParameterInfoList ¶meters, + Entity *entity, + const QVector<LightSource> &activeLightSources, + EnvironmentLight *environmentLight) const +{ + // The VAO Handle is set directly in the renderer thread so as to avoid having to use a mutex here + // Set shader, technique, and effect by basically doing : + // ShaderProgramManager[MaterialManager[frontentEntity->id()]->Effect->Techniques[TechniqueFilter->name]->RenderPasses[RenderPassFilter->name]]; + // The Renderer knows that if one of those is null, a default material / technique / effect as to be used + + // Find all RenderPasses (in order) matching values set in the RenderPassFilter + // Get list of parameters for the Material, Effect, and Technique + // For each ParameterBinder in the RenderPass -> create a QUniformPack + // Once that works, improve that to try and minimize QUniformPack updates + + if (rPass != nullptr) { + // Index Shader by Shader UUID + command->m_shader = m_manager->lookupHandle<Shader, ShaderManager, HShader>(rPass->shaderProgram()); + Shader *shader = m_manager->data<Shader, ShaderManager>(command->m_shader); + if (shader != nullptr && shader->isLoaded()) { + command->m_shaderDna = shader->dna(); + + // Builds the QUniformPack, sets shader standard uniforms and store attributes name / glname bindings + // If a parameter is defined and not found in the bindings it is assumed to be a binding of Uniform type with the glsl name + // equals to the parameter name + const QVector<int> uniformNamesIds = shader->uniformsNamesIds(); + const QVector<int> uniformBlockNamesIds = shader->uniformBlockNamesIds(); + const QVector<int> shaderStorageBlockNamesIds = shader->storageBlockNamesIds(); + const QVector<int> attributeNamesIds = shader->attributeNamesIds(); + + // Set fragData Name and index + // Later on we might want to relink the shader if attachments have changed + // But for now we set them once and for all + QHash<QString, int> fragOutputs; + if (!m_renderTarget.isNull() && !shader->isLoaded()) { + const auto atts = m_attachmentPack.attachments(); + for (const Attachment &att : atts) { + if (att.m_point <= QRenderTargetOutput::Color15) + fragOutputs.insert(att.m_name, att.m_point); + } + } + + if (!uniformNamesIds.isEmpty() || !attributeNamesIds.isEmpty() || + !shaderStorageBlockNamesIds.isEmpty() || !attributeNamesIds.isEmpty()) { + + // Set default standard uniforms without bindings + const Matrix4x4 worldTransform = *(entity->worldTransform()); + for (const int uniformNameId : uniformNamesIds) { + if (ms_standardUniformSetters.contains(uniformNameId)) + setStandardUniformValue(command->m_parameterPack, uniformNameId, uniformNameId, entity, worldTransform); + } + + // Set default attributes + for (const int attributeNameId : attributeNamesIds) + command->m_attributes.push_back(attributeNameId); + + // Parameters remaining could be + // -> uniform scalar / vector + // -> uniform struct / arrays + // -> uniform block / array (4.3) + // -> ssbo block / array (4.3) + + ParameterInfoList::const_iterator it = parameters.cbegin(); + const ParameterInfoList::const_iterator parametersEnd = parameters.cend(); + + while (it != parametersEnd) { + Parameter *param = m_manager->data<Parameter, ParameterManager>(it->handle); + const UniformValue &uniformValue = param->uniformValue(); + if (uniformNamesIds.contains(it->nameId)) { // Parameter is a regular uniform + setUniformValue(command->m_parameterPack, it->nameId, uniformValue); + } else if (uniformBlockNamesIds.indexOf(it->nameId) != -1) { // Parameter is a uniform block + setUniformBlockValue(command->m_parameterPack, shader, shader->uniformBlockForBlockNameId(it->nameId), uniformValue); + } else if (shaderStorageBlockNamesIds.indexOf(it->nameId) != -1) { // Parameters is a SSBO + setShaderStorageValue(command->m_parameterPack, shader, shader->storageBlockForBlockNameId(it->nameId), uniformValue); + } else { // Parameter is a struct + ShaderData *shaderData = nullptr; + if (uniformValue.valueType() == UniformValue::NodeId && + (shaderData = m_manager->shaderDataManager()->lookupResource(*uniformValue.constData<Qt3DCore::QNodeId>())) != nullptr) { + // Try to check if we have a struct or array matching a QShaderData parameter + setDefaultUniformBlockShaderDataValue(command->m_parameterPack, shader, shaderData, StringToInt::lookupString(it->nameId)); + } + // Otherwise: param unused by current shader + } + ++it; + } + + // Lights + + int lightIdx = 0; + for (const LightSource &lightSource : activeLightSources) { + if (lightIdx == MAX_LIGHTS) + break; + Entity *lightEntity = lightSource.entity; + const Vector3D worldPos = lightEntity->worldBoundingVolume()->center(); + for (Light *light : lightSource.lights) { + if (!light->isEnabled()) + continue; + + ShaderData *shaderData = m_manager->shaderDataManager()->lookupResource(light->shaderData()); + if (!shaderData) + continue; + + if (lightIdx == MAX_LIGHTS) + break; + + // Note: implicit conversion of values to UniformValue + setUniformValue(command->m_parameterPack, LIGHT_POSITION_NAMES[lightIdx], worldPos); + setUniformValue(command->m_parameterPack, LIGHT_TYPE_NAMES[lightIdx], int(QAbstractLight::PointLight)); + setUniformValue(command->m_parameterPack, LIGHT_COLOR_NAMES[lightIdx], Vector3D(1.0f, 1.0f, 1.0f)); + setUniformValue(command->m_parameterPack, LIGHT_INTENSITY_NAMES[lightIdx], 0.5f); + + // There is no risk in doing that even if multithreaded + // since we are sure that a shaderData is unique for a given light + // and won't ever be referenced as a Component either + Matrix4x4 *worldTransform = lightEntity->worldTransform(); + if (worldTransform) + shaderData->updateWorldTransform(*worldTransform); + + setDefaultUniformBlockShaderDataValue(command->m_parameterPack, shader, shaderData, LIGHT_STRUCT_NAMES[lightIdx]); + ++lightIdx; + } + } + + if (uniformNamesIds.contains(LIGHT_COUNT_NAME_ID)) + setUniformValue(command->m_parameterPack, LIGHT_COUNT_NAME_ID, UniformValue(qMax(1, lightIdx))); + + // If no active light sources and no environment light, add a default light + if (activeLightSources.isEmpty() && !environmentLight) { + // Note: implicit conversion of values to UniformValue + setUniformValue(command->m_parameterPack, LIGHT_POSITION_NAMES[0], Vector3D(10.0f, 10.0f, 0.0f)); + setUniformValue(command->m_parameterPack, LIGHT_TYPE_NAMES[0], int(QAbstractLight::PointLight)); + setUniformValue(command->m_parameterPack, LIGHT_COLOR_NAMES[0], Vector3D(1.0f, 1.0f, 1.0f)); + setUniformValue(command->m_parameterPack, LIGHT_INTENSITY_NAMES[0], 0.5f); + } + + // Environment Light + int envLightCount = 0; + if (environmentLight && environmentLight->isEnabled()) { + ShaderData *shaderData = m_manager->shaderDataManager()->lookupResource(environmentLight->shaderData()); + if (shaderData) { + setDefaultUniformBlockShaderDataValue(command->m_parameterPack, shader, shaderData, QStringLiteral("envLight")); + envLightCount = 1; + } + } + setUniformValue(command->m_parameterPack, StringToInt::lookupId(QStringLiteral("envLightCount")), envLightCount); + } + // Set frag outputs in the shaders if hash not empty + if (!fragOutputs.isEmpty()) + shader->setFragOutputs(fragOutputs); + } + } + else { + qCWarning(Render::Backend) << Q_FUNC_INFO << "Using default effect as none was provided"; + } +} + +bool RenderView::hasBlitFramebufferInfo() const +{ + return m_hasBlitFramebufferInfo; +} + +void RenderView::setHasBlitFramebufferInfo(bool hasBlitFramebufferInfo) +{ + m_hasBlitFramebufferInfo = hasBlitFramebufferInfo; +} + +BlitFramebufferInfo RenderView::blitFrameBufferInfo() const +{ + return m_blitFrameBufferInfo; +} + +void RenderView::setBlitFrameBufferInfo(const BlitFramebufferInfo &blitFrameBufferInfo) +{ + m_blitFrameBufferInfo = blitFrameBufferInfo; +} + +bool RenderView::isDownloadBuffersEnable() const +{ + return m_isDownloadBuffersEnable; +} + +void RenderView::setIsDownloadBuffersEnable(bool isDownloadBuffersEnable) +{ + m_isDownloadBuffersEnable = isDownloadBuffersEnable; +} + +} // namespace Render +} // namespace Qt3DRender + +QT_END_NAMESPACE diff --git a/src/render/renderers/opengl/renderer/renderview_p.h b/src/render/renderers/opengl/renderer/renderview_p.h new file mode 100644 index 000000000..cb3c74917 --- /dev/null +++ b/src/render/renderers/opengl/renderer/renderview_p.h @@ -0,0 +1,392 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB). +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT3DRENDER_RENDER_RENDERVIEW_H +#define QT3DRENDER_RENDER_RENDERVIEW_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 <Qt3DRender/qparameter.h> +#include <Qt3DRender/qclearbuffers.h> +#include <Qt3DRender/qlayerfilter.h> +#include <Qt3DRender/private/renderer_p.h> +#include <Qt3DRender/private/clearbuffers_p.h> +#include <Qt3DRender/private/cameralens_p.h> +#include <Qt3DRender/private/attachmentpack_p.h> +#include <Qt3DRender/private/handle_types_p.h> +#include <Qt3DRender/private/qsortpolicy_p.h> +#include <Qt3DRender/private/lightsource_p.h> +#include <Qt3DRender/private/qmemorybarrier_p.h> +#include <Qt3DRender/private/qrendercapture_p.h> +#include <Qt3DRender/private/qblitframebuffer_p.h> + +#include <Qt3DCore/private/qframeallocator_p.h> +#include <Qt3DRender/private/aligned_malloc_p.h> + +// TODO: Move out once this is all refactored +#include <Qt3DRender/private/renderviewjobutils_p.h> + +#include <QVector> +#include <QSurface> +#include <QMutex> +#include <QColor> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { + +class QRenderPass; + +namespace Render { + +class Renderer; +class NodeManagers; +class RenderCommand; +class RenderPassFilter; +class TechniqueFilter; +class ViewportNode; +class Effect; +class RenderPass; + +typedef QPair<ShaderUniform, QVariant> ActivePropertyContent; +typedef QPair<QString, ActivePropertyContent > ActiveProperty; + +struct Q_AUTOTEST_EXPORT Plane +{ + explicit Plane(const Vector4D &planeEquation) + : planeEquation(planeEquation) + , normal(Vector3D(planeEquation).normalized()) + , d(planeEquation.w() / Vector3D(planeEquation).length()) + {} + + const Vector4D planeEquation; + const Vector3D normal; + const float d; +}; + +struct Q_AUTOTEST_EXPORT ClearBufferInfo +{ + int drawBufferIndex = 0; + QRenderTargetOutput::AttachmentPoint attchmentPoint = QRenderTargetOutput::Color0; + QVector4D clearColor; +}; + +struct Q_AUTOTEST_EXPORT BlitFramebufferInfo +{ + Qt3DCore::QNodeId sourceRenderTargetId; + Qt3DCore::QNodeId destinationRenderTargetId; + QRect sourceRect; + QRect destinationRect; + Qt3DRender::QRenderTargetOutput::AttachmentPoint sourceAttachmentPoint; + Qt3DRender::QRenderTargetOutput::AttachmentPoint destinationAttachmentPoint; + QBlitFramebuffer::InterpolationMethod interpolationMethod; +}; + +// This class is kind of analogous to RenderBin but I want to avoid trampling +// on that until we get this working + +class Q_AUTOTEST_EXPORT RenderView +{ +public: + RenderView(); + ~RenderView(); + + QT3D_ALIGNED_MALLOC_AND_FREE() + + // TODO: Add a way to specify a sort predicate for the RenderCommands + void sort(); + + void setRenderer(Renderer *renderer); + inline void setSurfaceSize(const QSize &size) Q_DECL_NOTHROW { m_surfaceSize = size; } + inline Renderer *renderer() const Q_DECL_NOTHROW { return m_renderer; } + inline NodeManagers *nodeManagers() const Q_DECL_NOTHROW { return m_manager; } + inline const QSize &surfaceSize() const Q_DECL_NOTHROW { return m_surfaceSize; } + inline void setDevicePixelRatio(qreal r) Q_DECL_NOTHROW { m_devicePixelRatio = r; } + inline qreal devicePixelRatio() const Q_DECL_NOTHROW { return m_devicePixelRatio; } + + inline void setRenderCameraLens(CameraLens *renderCameraLens) Q_DECL_NOTHROW { m_data.m_renderCameraLens = renderCameraLens; } + inline CameraLens *renderCameraLens() const Q_DECL_NOTHROW { return m_data.m_renderCameraLens; } + + inline void setRenderCameraEntity(Entity *renderCameraNode) Q_DECL_NOTHROW { m_data.m_renderCameraNode = renderCameraNode; } + inline Entity *renderCameraEntity() const Q_DECL_NOTHROW { return m_data.m_renderCameraNode; } + + inline void setViewMatrix(const Matrix4x4 &viewMatrix) Q_DECL_NOTHROW { m_data.m_viewMatrix = viewMatrix; } + inline Matrix4x4 viewMatrix() const Q_DECL_NOTHROW { return m_data.m_viewMatrix; } + + inline void setViewProjectionMatrix(const Matrix4x4 &viewProjectionMatrix) Q_DECL_NOTHROW { m_data.m_viewProjectionMatrix = viewProjectionMatrix; } + inline Matrix4x4 viewProjectionMatrix() const Q_DECL_NOTHROW { return m_data.m_viewProjectionMatrix; } + + inline void setEyePosition(const Vector3D &eyePos) Q_DECL_NOTHROW { m_data.m_eyePos = eyePos; } + inline Vector3D eyePosition() const Q_DECL_NOTHROW { return m_data.m_eyePos; } + + inline void setEyeViewDirection(const Vector3D &dir) Q_DECL_NOTHROW { m_data.m_eyeViewDir = dir; } + inline Vector3D eyeViewDirection() const Q_DECL_NOTHROW { return m_data.m_eyeViewDir; } + + inline void appendLayerFilter(const Qt3DCore::QNodeId layerFilterId) Q_DECL_NOTHROW { m_data.m_layerFilterIds.push_back(layerFilterId); } + inline Qt3DCore::QNodeIdVector layerFilters() const Q_DECL_NOTHROW { return m_data.m_layerFilterIds; } + + inline void appendProximityFilterId(const Qt3DCore::QNodeId proximityFilterId) { m_data.m_proximityFilterIds.push_back(proximityFilterId); } + inline Qt3DCore::QNodeIdVector proximityFilterIds() const { return m_data.m_proximityFilterIds; } + + inline void setRenderPassFilter(const RenderPassFilter *rpFilter) Q_DECL_NOTHROW { m_data.m_passFilter = rpFilter; } + inline const RenderPassFilter *renderPassFilter() const Q_DECL_NOTHROW { return m_data.m_passFilter; } + + inline void setTechniqueFilter(const TechniqueFilter *filter) Q_DECL_NOTHROW { m_data.m_techniqueFilter = filter; } + inline const TechniqueFilter *techniqueFilter() const Q_DECL_NOTHROW { return m_data.m_techniqueFilter; } + + inline RenderStateSet *stateSet() const Q_DECL_NOTHROW { return m_stateSet; } + void setStateSet(RenderStateSet *stateSet) Q_DECL_NOTHROW { m_stateSet = stateSet; } + + inline bool noDraw() const Q_DECL_NOTHROW { return m_noDraw; } + void setNoDraw(bool noDraw) Q_DECL_NOTHROW { m_noDraw = noDraw; } + + inline bool isCompute() const Q_DECL_NOTHROW { return m_compute; } + void setCompute(bool compute) Q_DECL_NOTHROW { m_compute = compute; } + + void setComputeWorkgroups(int x, int y, int z) Q_DECL_NOTHROW { m_workGroups[0] = x; m_workGroups[1] = y; m_workGroups[2] = z; } + const int *computeWorkGroups() const Q_DECL_NOTHROW { return m_workGroups; } + inline bool frustumCulling() const Q_DECL_NOTHROW { return m_frustumCulling; } + void setFrustumCulling(bool frustumCulling) Q_DECL_NOTHROW { m_frustumCulling = frustumCulling; } + + inline void setMaterialParameterTable(const MaterialParameterGathererData ¶meters) Q_DECL_NOTHROW { m_parameters = parameters; } + + // TODO: Get rid of this overly complex memory management by splitting out the + // InnerData as a RenderViewConfig struct. This can be created by setRenderViewConfigFromFrameGraphLeafNode + // and passed along with the RenderView to the functions that populate the renderview + inline void setViewport(const QRectF &vp) Q_DECL_NOTHROW { m_viewport = vp; } + inline QRectF viewport() const Q_DECL_NOTHROW { return m_viewport; } + + inline float gamma() const Q_DECL_NOTHROW { return m_gamma; } + inline void setGamma(float gamma) Q_DECL_NOTHROW { m_gamma = gamma; } + + // depth and stencil ClearBuffers are cached locally + // color ClearBuffers are collected, as there may be multiple + // color buffers to be cleared. we need to apply all these at rendering + void addClearBuffers(const ClearBuffers *cb); + inline QVector<ClearBufferInfo> specificClearColorBufferInfo() const { return m_specificClearColorBuffers; } + inline QVector<ClearBufferInfo> &specificClearColorBufferInfo() { return m_specificClearColorBuffers; } + inline ClearBufferInfo globalClearColorBufferInfo() const { return m_globalClearColorBuffer; } + + inline QClearBuffers::BufferTypeFlags clearTypes() const { return m_clearBuffer; } + inline float clearDepthValue() const { return m_clearDepthValue; } + inline int clearStencilValue() const { return m_clearStencilValue; } + + RenderPassList passesAndParameters(ParameterInfoList *parameter, Entity *node, bool useDefaultMaterials = true); + + QVector<RenderCommand *> buildDrawRenderCommands(const QVector<Entity *> &entities) const; + QVector<RenderCommand *> buildComputeRenderCommands(const QVector<Entity *> &entities) const; + void setCommands(QVector<RenderCommand *> &commands) Q_DECL_NOTHROW { m_commands = commands; } + QVector<RenderCommand *> commands() const Q_DECL_NOTHROW { return m_commands; } + + void setAttachmentPack(const AttachmentPack &pack) { m_attachmentPack = pack; } + const AttachmentPack &attachmentPack() const { return m_attachmentPack; } + + void setRenderTargetId(Qt3DCore::QNodeId renderTargetId) Q_DECL_NOTHROW { m_renderTarget = renderTargetId; } + Qt3DCore::QNodeId renderTargetId() const Q_DECL_NOTHROW { return m_renderTarget; } + + void addSortType(const QVector<Qt3DRender::QSortPolicy::SortType> &sortTypes) { m_data.m_sortingTypes.append(sortTypes); } + + void setSurface(QSurface *surface) { m_surface = surface; } + QSurface *surface() const { return m_surface; } + + void setLightSources(const QVector<LightSource> &lightSources) Q_DECL_NOTHROW { m_lightSources = lightSources; } + void setEnvironmentLight(EnvironmentLight *environmentLight) Q_DECL_NOTHROW { m_environmentLight = environmentLight; } + + void updateMatrices(); + + inline void setRenderCaptureNodeId(const Qt3DCore::QNodeId nodeId) Q_DECL_NOTHROW { m_renderCaptureNodeId = nodeId; } + inline const Qt3DCore::QNodeId renderCaptureNodeId() const Q_DECL_NOTHROW { return m_renderCaptureNodeId; } + inline void setRenderCaptureRequest(const QRenderCaptureRequest& request) Q_DECL_NOTHROW { m_renderCaptureRequest = request; } + inline const QRenderCaptureRequest renderCaptureRequest() const Q_DECL_NOTHROW { return m_renderCaptureRequest; } + + void setMemoryBarrier(QMemoryBarrier::Operations barrier) Q_DECL_NOTHROW { m_memoryBarrier = barrier; } + QMemoryBarrier::Operations memoryBarrier() const Q_DECL_NOTHROW { return m_memoryBarrier; } + + // Helps making the size of RenderView smaller + // Contains all the data needed for the actual building of the RenderView + // But that aren't used later by the Renderer + struct InnerData { + InnerData() + : m_renderCameraLens(nullptr) + , m_renderCameraNode(nullptr) + , m_techniqueFilter(nullptr) + , m_passFilter(nullptr) + { + } + CameraLens *m_renderCameraLens; + Entity *m_renderCameraNode; + const TechniqueFilter *m_techniqueFilter; + const RenderPassFilter *m_passFilter; + Matrix4x4 m_viewMatrix; + Matrix4x4 m_viewProjectionMatrix; + Qt3DCore::QNodeIdVector m_layerFilterIds; + QVector<Qt3DRender::QSortPolicy::SortType> m_sortingTypes; + Vector3D m_eyePos; + Vector3D m_eyeViewDir; + Qt3DCore::QNodeIdVector m_proximityFilterIds; + }; + + bool isDownloadBuffersEnable() const; + void setIsDownloadBuffersEnable(bool isDownloadBuffersEnable); + + BlitFramebufferInfo blitFrameBufferInfo() const; + void setBlitFrameBufferInfo(const BlitFramebufferInfo &blitFrameBufferInfo); + + bool hasBlitFramebufferInfo() const; + void setHasBlitFramebufferInfo(bool hasBlitFramebufferInfo); + +private: + void setShaderAndUniforms(RenderCommand *command, + RenderPass *pass, + ParameterInfoList ¶meters, + Entity *entity, + const QVector<LightSource> &activeLightSources, + EnvironmentLight *environmentLight) const; + mutable QThreadStorage<UniformBlockValueBuilder*> m_localData; + + Qt3DCore::QNodeId m_renderCaptureNodeId; + QRenderCaptureRequest m_renderCaptureRequest; + bool m_isDownloadBuffersEnable; + + bool m_hasBlitFramebufferInfo; + BlitFramebufferInfo m_blitFrameBufferInfo; + + Renderer *m_renderer; + NodeManagers *m_manager; + QSize m_surfaceSize; + qreal m_devicePixelRatio; + + InnerData m_data; + + QRectF m_viewport; + float m_gamma; + Qt3DCore::QNodeId m_renderTarget; + QSurface *m_surface; + AttachmentPack m_attachmentPack; + QClearBuffers::BufferTypeFlags m_clearBuffer; + float m_clearDepthValue; + int m_clearStencilValue; + ClearBufferInfo m_globalClearColorBuffer; // global ClearColor + QVector<ClearBufferInfo> m_specificClearColorBuffers; // different draw buffers with distinct colors + RenderStateSet *m_stateSet; + bool m_noDraw:1; + bool m_compute:1; + bool m_frustumCulling:1; + int m_workGroups[3]; + QMemoryBarrier::Operations m_memoryBarrier; + + // We do not use pointers to RenderNodes or Drawable's here so that the + // render aspect is free to change the drawables on the next frame whilst + // the render thread is submitting these commands. + QVector<RenderCommand *> m_commands; + mutable QVector<LightSource> m_lightSources; + EnvironmentLight *m_environmentLight; + + MaterialParameterGathererData m_parameters; + + enum StandardUniform + { + ModelMatrix, + ViewMatrix, + ProjectionMatrix, + ModelViewMatrix, + ViewProjectionMatrix, + ModelViewProjectionMatrix, + InverseModelMatrix, + InverseViewMatrix, + InverseProjectionMatrix, + InverseModelViewMatrix, + InverseViewProjectionMatrix, + InverseModelViewProjectionMatrix, + ModelNormalMatrix, + ModelViewNormalMatrix, + ViewportMatrix, + InverseViewportMatrix, + AspectRatio, + Time, + Exposure, + Gamma, + EyePosition, + SkinningPalette + }; + + typedef QHash<int, StandardUniform> StandardUniformsNameToTypeHash; + static StandardUniformsNameToTypeHash ms_standardUniformSetters; + static StandardUniformsNameToTypeHash initializeStandardUniformSetters(); + + UniformValue standardUniformValue(StandardUniform standardUniformType, + Entity *entity, + const Matrix4x4 &model) const; + + void setUniformValue(ShaderParameterPack &uniformPack, int nameId, const UniformValue &value) const; + void setStandardUniformValue(ShaderParameterPack &uniformPack, + int glslNameId, + int nameId, + Entity *entity, + const Matrix4x4 &worldTransform) const; + void setUniformBlockValue(ShaderParameterPack &uniformPack, + Shader *shader, + const ShaderUniformBlock &block, + const UniformValue &value) const; + void setShaderStorageValue(ShaderParameterPack &uniformPack, + Shader *shader, + const ShaderStorageBlock &block, + const UniformValue &value) const; + void setDefaultUniformBlockShaderDataValue(ShaderParameterPack &uniformPack, + Shader *shader, + ShaderData *shaderData, + const QString &structName) const; +}; + +} // namespace Render +} // namespace Qt3DRender + +QT_END_NAMESPACE + +#endif // QT3DRENDER_RENDER_RENDERVIEW_H diff --git a/src/render/renderers/opengl/renderer/renderviewbuilder.cpp b/src/render/renderers/opengl/renderer/renderviewbuilder.cpp new file mode 100644 index 000000000..d08bd6dd4 --- /dev/null +++ b/src/render/renderers/opengl/renderer/renderviewbuilder.cpp @@ -0,0 +1,672 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Klaralvdalens Datakonsult AB (KDAB). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "renderviewbuilder_p.h" + +#include <QThread> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { + +namespace Render { + +const int RenderViewBuilder::m_optimalParallelJobCount = std::max(QThread::idealThreadCount(), 2); + +namespace { + +class SyncRenderViewCommandBuilders +{ +public: + explicit SyncRenderViewCommandBuilders(const RenderViewInitializerJobPtr &renderViewJob, + const QVector<RenderViewBuilderJobPtr> &renderViewBuilderJobs, + Renderer *renderer) + : m_renderViewJob(renderViewJob) + , m_renderViewBuilderJobs(renderViewBuilderJobs) + , m_renderer(renderer) + {} + + void operator()() + { + // Append all the commands and sort them + RenderView *rv = m_renderViewJob->renderView(); + + int totalCommandCount = 0; + for (const auto &renderViewCommandBuilder : qAsConst(m_renderViewBuilderJobs)) + totalCommandCount += renderViewCommandBuilder->commands().size(); + + QVector<RenderCommand *> commands; + commands.reserve(totalCommandCount); + + // Reduction + for (const auto &renderViewCommandBuilder : qAsConst(m_renderViewBuilderJobs)) + commands += std::move(renderViewCommandBuilder->commands()); + rv->setCommands(commands); + + // Sort the commands + rv->sort(); + + // Enqueue our fully populated RenderView with the RenderThread + m_renderer->enqueueRenderView(rv, m_renderViewJob->submitOrderIndex()); + } + +private: + RenderViewInitializerJobPtr m_renderViewJob; + QVector<RenderViewBuilderJobPtr> m_renderViewBuilderJobs; + Renderer *m_renderer; +}; + +class SyncFrustumCulling +{ +public: + explicit SyncFrustumCulling(const RenderViewInitializerJobPtr &renderViewJob, + const FrustumCullingJobPtr &frustumCulling) + : m_renderViewJob(renderViewJob) + , m_frustumCullingJob(frustumCulling) + {} + + void operator()() + { + RenderView *rv = m_renderViewJob->renderView(); + + // Update matrices now that all transforms have been updated + rv->updateMatrices(); + + // Frustum culling + m_frustumCullingJob->setViewProjection(rv->viewProjectionMatrix()); + } + +private: + RenderViewInitializerJobPtr m_renderViewJob; + FrustumCullingJobPtr m_frustumCullingJob; +}; + +class SyncRenderViewInitialization +{ +public: + explicit SyncRenderViewInitialization(const RenderViewInitializerJobPtr &renderViewJob, + const FrustumCullingJobPtr &frustumCullingJob, + const FilterLayerEntityJobPtr &filterEntityByLayerJob, + const FilterProximityDistanceJobPtr &filterProximityJob, + const QVector<MaterialParameterGathererJobPtr> &materialGathererJobs, + const QVector<RenderViewBuilderJobPtr> &renderViewBuilderJobs) + : m_renderViewJob(renderViewJob) + , m_frustumCullingJob(frustumCullingJob) + , m_filterEntityByLayerJob(filterEntityByLayerJob) + , m_filterProximityJob(filterProximityJob) + , m_materialGathererJobs(materialGathererJobs) + , m_renderViewBuilderJobs(renderViewBuilderJobs) + {} + + void operator()() + { + RenderView *rv = m_renderViewJob->renderView(); + + // Layer filtering + if (!m_filterEntityByLayerJob.isNull()) + m_filterEntityByLayerJob->setLayerFilters(rv->layerFilters()); + + // Proximity filtering + m_filterProximityJob->setProximityFilterIds(rv->proximityFilterIds()); + + // Material Parameter building + for (const auto &materialGatherer : qAsConst(m_materialGathererJobs)) { + materialGatherer->setRenderPassFilter(const_cast<RenderPassFilter *>(rv->renderPassFilter())); + materialGatherer->setTechniqueFilter(const_cast<TechniqueFilter *>(rv->techniqueFilter())); + } + + // Command builders + for (const auto &renderViewCommandBuilder : qAsConst(m_renderViewBuilderJobs)) + renderViewCommandBuilder->setRenderView(rv); + + // Set whether frustum culling is enabled or not + m_frustumCullingJob->setActive(rv->frustumCulling()); + } + +private: + RenderViewInitializerJobPtr m_renderViewJob; + FrustumCullingJobPtr m_frustumCullingJob; + FilterLayerEntityJobPtr m_filterEntityByLayerJob; + FilterProximityDistanceJobPtr m_filterProximityJob; + QVector<MaterialParameterGathererJobPtr> m_materialGathererJobs; + QVector<RenderViewBuilderJobPtr> m_renderViewBuilderJobs; +}; + +class SyncRenderCommandBuilding +{ +public: + explicit SyncRenderCommandBuilding(const RenderViewInitializerJobPtr &renderViewJob, + const FrustumCullingJobPtr &frustumCullingJob, + const FilterProximityDistanceJobPtr &filterProximityJob, + const LightGathererPtr &lightGathererJob, + const RenderableEntityFilterPtr &renderableEntityFilterJob, + const ComputableEntityFilterPtr &computableEntityFilterJob, + const QVector<MaterialParameterGathererJobPtr> &materialGathererJobs, + const QVector<RenderViewBuilderJobPtr> &renderViewBuilderJobs, + Renderer *renderer, + FrameGraphNode *leafNode) + : m_renderViewJob(renderViewJob) + , m_frustumCullingJob(frustumCullingJob) + , m_filterProximityJob(filterProximityJob) + , m_lightGathererJob(lightGathererJob) + , m_renderableEntityFilterJob(renderableEntityFilterJob) + , m_computableEntityFilterJob(computableEntityFilterJob) + , m_materialGathererJobs(materialGathererJobs) + , m_renderViewBuilderJobs(renderViewBuilderJobs) + , m_renderer(renderer) + , m_leafNode(leafNode) + {} + + void operator()() + { + // Set the result of previous job computations + // for final RenderCommand building + RenderView *rv = m_renderViewJob->renderView(); + + if (!rv->noDraw()) { + rv->setEnvironmentLight(m_lightGathererJob->takeEnvironmentLight()); + + // We sort the vector so that the removal can then be performed linearly + + QVector<Entity *> renderableEntities; + const bool isDraw = !rv->isCompute(); + if (isDraw) + renderableEntities = std::move(m_renderableEntityFilterJob->filteredEntities()); + else + renderableEntities = std::move(m_computableEntityFilterJob->filteredEntities()); + + // Filter out entities that weren't selected by the layer filters + std::sort(renderableEntities.begin(), renderableEntities.end()); + + QMutexLocker lock(m_renderer->cache()->mutex()); + const QVector<Entity *> filteredEntities = m_renderer->cache()->leafNodeCache.value(m_leafNode).filterEntitiesByLayer; + lock.unlock(); + // Remove all entities from the compute and renderable vectors that aren't in the filtered layer vector + renderableEntities = RenderViewBuilder::entitiesInSubset(renderableEntities, filteredEntities); + + // Set the light sources, with layer filters applied. + QVector<LightSource> lightSources = m_lightGathererJob->lights(); + for (int i = 0; i < lightSources.count(); ++i) { + if (!filteredEntities.contains(lightSources[i].entity)) + lightSources.removeAt(i--); + } + rv->setLightSources(lightSources); + + if (isDraw) { + // Filter out frustum culled entity for drawable entities + if (rv->frustumCulling()) + renderableEntities = RenderViewBuilder::entitiesInSubset(renderableEntities, m_frustumCullingJob->visibleEntities()); + // Filter out entities which didn't satisfy proximity filtering + renderableEntities = RenderViewBuilder::entitiesInSubset(renderableEntities, m_filterProximityJob->filteredEntities()); + } + + // Split among the number of command builders + int i = 0; + const int m = RenderViewBuilder::optimalJobCount() - 1; + const int packetSize = renderableEntities.size() / RenderViewBuilder::optimalJobCount(); + while (i < m) { + const RenderViewBuilderJobPtr renderViewCommandBuilder = m_renderViewBuilderJobs.at(i); + renderViewCommandBuilder->setRenderables(renderableEntities.mid(i * packetSize, packetSize)); + ++i; + } + m_renderViewBuilderJobs.at(i)->setRenderables(renderableEntities.mid(i * packetSize, packetSize + renderableEntities.size() % (m + 1))); + { + QMutexLocker rendererCacheLock(m_renderer->cache()->mutex()); + rv->setMaterialParameterTable(m_renderer->cache()->leafNodeCache.value(m_leafNode).materialParameterGatherer); + } + } + } + +private: + RenderViewInitializerJobPtr m_renderViewJob; + FrustumCullingJobPtr m_frustumCullingJob; + FilterProximityDistanceJobPtr m_filterProximityJob; + LightGathererPtr m_lightGathererJob; + RenderableEntityFilterPtr m_renderableEntityFilterJob; + ComputableEntityFilterPtr m_computableEntityFilterJob; + QVector<MaterialParameterGathererJobPtr> m_materialGathererJobs; + QVector<RenderViewBuilderJobPtr> m_renderViewBuilderJobs; + Renderer *m_renderer; + FrameGraphNode *m_leafNode; +}; + +class SetClearDrawBufferIndex +{ +public: + explicit SetClearDrawBufferIndex(const RenderViewInitializerJobPtr &renderViewJob) + : m_renderViewJob(renderViewJob) + {} + + void operator()() + { + RenderView *rv = m_renderViewJob->renderView(); + QVector<ClearBufferInfo> &clearBuffersInfo = rv->specificClearColorBufferInfo(); + const AttachmentPack &attachmentPack = rv->attachmentPack(); + for (ClearBufferInfo &clearBufferInfo : clearBuffersInfo) + clearBufferInfo.drawBufferIndex = attachmentPack.getDrawBufferIndex(clearBufferInfo.attchmentPoint); + + } + +private: + RenderViewInitializerJobPtr m_renderViewJob; +}; + +class SyncFilterEntityByLayer +{ +public: + explicit SyncFilterEntityByLayer(const FilterLayerEntityJobPtr &filterEntityByLayerJob, + Renderer *renderer, + FrameGraphNode *leafNode) + : m_filterEntityByLayerJob(filterEntityByLayerJob) + , m_renderer(renderer) + , m_leafNode(leafNode) + { + } + + void operator()() + { + QMutexLocker lock(m_renderer->cache()->mutex()); + // Save the filtered by layer subset into the cache + const QVector<Entity *> filteredEntities = m_filterEntityByLayerJob->filteredEntities(); + RendererCache::LeafNodeData &dataCacheForLeaf = m_renderer->cache()->leafNodeCache[m_leafNode]; + dataCacheForLeaf.filterEntitiesByLayer = filteredEntities; + } + +private: + FilterLayerEntityJobPtr m_filterEntityByLayerJob; + Renderer *m_renderer; + FrameGraphNode *m_leafNode; +}; + +class SyncMaterialParameterGatherer +{ +public: + explicit SyncMaterialParameterGatherer(const QVector<MaterialParameterGathererJobPtr> &materialParameterGathererJobs, + Renderer *renderer, + FrameGraphNode *leafNode) + : m_materialParameterGathererJobs(materialParameterGathererJobs) + , m_renderer(renderer) + , m_leafNode(leafNode) + { + } + + void operator()() + { + QMutexLocker lock(m_renderer->cache()->mutex()); + RendererCache::LeafNodeData &dataCacheForLeaf = m_renderer->cache()->leafNodeCache[m_leafNode]; + dataCacheForLeaf.materialParameterGatherer.clear(); + + for (const auto &materialGatherer : qAsConst(m_materialParameterGathererJobs)) + dataCacheForLeaf.materialParameterGatherer.unite(materialGatherer->materialToPassAndParameter()); + } + +private: + QVector<MaterialParameterGathererJobPtr> m_materialParameterGathererJobs; + Renderer *m_renderer; + FrameGraphNode *m_leafNode; +}; + +} // anonymous + +RenderViewBuilder::RenderViewBuilder(Render::FrameGraphNode *leafNode, int renderViewIndex, Renderer *renderer) + : m_leafNode(leafNode) + , m_renderViewIndex(renderViewIndex) + , m_renderer(renderer) + , m_layerCacheNeedsToBeRebuilt(false) + , m_materialGathererCacheNeedsToBeRebuilt(false) + , m_renderViewJob(RenderViewInitializerJobPtr::create()) + , m_filterEntityByLayerJob() + , m_lightGathererJob(Render::LightGathererPtr::create()) + , m_renderableEntityFilterJob(RenderableEntityFilterPtr::create()) + , m_computableEntityFilterJob(ComputableEntityFilterPtr::create()) + , m_frustumCullingJob(new Render::FrustumCullingJob()) + , m_syncFrustumCullingJob(SynchronizerJobPtr::create(SyncFrustumCulling(m_renderViewJob, m_frustumCullingJob), JobTypes::SyncFrustumCulling)) + , m_setClearDrawBufferIndexJob(SynchronizerJobPtr::create(SetClearDrawBufferIndex(m_renderViewJob), JobTypes::ClearBufferDrawIndex)) + , m_syncFilterEntityByLayerJob() + , m_filterProximityJob(Render::FilterProximityDistanceJobPtr::create()) +{ +} + +RenderViewInitializerJobPtr RenderViewBuilder::renderViewJob() const +{ + return m_renderViewJob; +} + +FilterLayerEntityJobPtr RenderViewBuilder::filterEntityByLayerJob() const +{ + return m_filterEntityByLayerJob; +} + +LightGathererPtr RenderViewBuilder::lightGathererJob() const +{ + return m_lightGathererJob; +} + +RenderableEntityFilterPtr RenderViewBuilder::renderableEntityFilterJob() const +{ + return m_renderableEntityFilterJob; +} + +ComputableEntityFilterPtr RenderViewBuilder::computableEntityFilterJob() const +{ + return m_computableEntityFilterJob; +} + +FrustumCullingJobPtr RenderViewBuilder::frustumCullingJob() const +{ + return m_frustumCullingJob; +} + +QVector<RenderViewBuilderJobPtr> RenderViewBuilder::renderViewBuilderJobs() const +{ + return m_renderViewBuilderJobs; +} + +QVector<MaterialParameterGathererJobPtr> RenderViewBuilder::materialGathererJobs() const +{ + return m_materialGathererJobs; +} + +SynchronizerJobPtr RenderViewBuilder::syncRenderViewInitializationJob() const +{ + return m_syncRenderViewInitializationJob; +} + +SynchronizerJobPtr RenderViewBuilder::syncFrustumCullingJob() const +{ + return m_syncFrustumCullingJob; +} + +SynchronizerJobPtr RenderViewBuilder::syncRenderCommandBuildingJob() const +{ + return m_syncRenderCommandBuildingJob; +} + +SynchronizerJobPtr RenderViewBuilder::syncRenderViewCommandBuildersJob() const +{ + return m_syncRenderViewCommandBuildersJob; +} + +SynchronizerJobPtr RenderViewBuilder::setClearDrawBufferIndexJob() const +{ + return m_setClearDrawBufferIndexJob; +} + +SynchronizerJobPtr RenderViewBuilder::syncFilterEntityByLayerJob() const +{ + return m_syncFilterEntityByLayerJob; +} + +SynchronizerJobPtr RenderViewBuilder::syncMaterialGathererJob() const +{ + return m_syncMaterialGathererJob; +} + +FilterProximityDistanceJobPtr RenderViewBuilder::filterProximityJob() const +{ + return m_filterProximityJob; +} + +void RenderViewBuilder::prepareJobs() +{ + // Init what we can here + EntityManager *entityManager = m_renderer->nodeManagers()->renderNodesManager(); + m_filterProximityJob->setManager(m_renderer->nodeManagers()); + m_renderableEntityFilterJob->setManager(entityManager); + m_computableEntityFilterJob->setManager(entityManager); + m_frustumCullingJob->setRoot(m_renderer->sceneRoot()); + m_lightGathererJob->setManager(entityManager); + m_renderViewJob->setRenderer(m_renderer); + m_renderViewJob->setFrameGraphLeafNode(m_leafNode); + m_renderViewJob->setSubmitOrderIndex(m_renderViewIndex); + + // RenderCommand building is the most consuming task -> split it + // Estimate the number of jobs to create based on the number of entities + m_renderViewBuilderJobs.reserve(RenderViewBuilder::m_optimalParallelJobCount); + for (auto i = 0; i < RenderViewBuilder::m_optimalParallelJobCount; ++i) { + auto renderViewCommandBuilder = Render::RenderViewBuilderJobPtr::create(); + renderViewCommandBuilder->setIndex(m_renderViewIndex); + renderViewCommandBuilder->setRenderer(m_renderer); + m_renderViewBuilderJobs.push_back(renderViewCommandBuilder); + } + + if (m_materialGathererCacheNeedsToBeRebuilt) { + // Since Material gathering is an heavy task, we split it + const QVector<HMaterial> materialHandles = m_renderer->nodeManagers()->materialManager()->activeHandles(); + const int elementsPerJob = materialHandles.size() / RenderViewBuilder::m_optimalParallelJobCount; + const int lastRemaingElements = materialHandles.size() % RenderViewBuilder::m_optimalParallelJobCount; + m_materialGathererJobs.reserve(RenderViewBuilder::m_optimalParallelJobCount); + for (auto i = 0; i < RenderViewBuilder::m_optimalParallelJobCount; ++i) { + auto materialGatherer = Render::MaterialParameterGathererJobPtr::create(); + materialGatherer->setNodeManagers(m_renderer->nodeManagers()); + materialGatherer->setRenderer(m_renderer); + if (i == RenderViewBuilder::m_optimalParallelJobCount - 1) + materialGatherer->setHandles(materialHandles.mid(i * elementsPerJob, elementsPerJob + lastRemaingElements)); + else + materialGatherer->setHandles(materialHandles.mid(i * elementsPerJob, elementsPerJob)); + m_materialGathererJobs.push_back(materialGatherer); + } + m_syncMaterialGathererJob = SynchronizerJobPtr::create(SyncMaterialParameterGatherer(m_materialGathererJobs, + m_renderer, + m_leafNode), + JobTypes::SyncMaterialGatherer); + } + + if (m_layerCacheNeedsToBeRebuilt) { + m_filterEntityByLayerJob = Render::FilterLayerEntityJobPtr::create(); + m_filterEntityByLayerJob->setManager(m_renderer->nodeManagers()); + m_syncFilterEntityByLayerJob = SynchronizerJobPtr::create(SyncFilterEntityByLayer(m_filterEntityByLayerJob, + m_renderer, + m_leafNode), + JobTypes::SyncFilterEntityByLayer); + } + + m_syncRenderCommandBuildingJob = SynchronizerJobPtr::create(SyncRenderCommandBuilding(m_renderViewJob, + m_frustumCullingJob, + m_filterProximityJob, + m_lightGathererJob, + m_renderableEntityFilterJob, + m_computableEntityFilterJob, + m_materialGathererJobs, + m_renderViewBuilderJobs, + m_renderer, + m_leafNode), + JobTypes::SyncRenderViewCommandBuilding); + + m_syncRenderViewCommandBuildersJob = SynchronizerJobPtr::create(SyncRenderViewCommandBuilders(m_renderViewJob, + m_renderViewBuilderJobs, + m_renderer), + JobTypes::SyncRenderViewCommandBuilder); + + m_syncRenderViewInitializationJob = SynchronizerJobPtr::create(SyncRenderViewInitialization(m_renderViewJob, + m_frustumCullingJob, + m_filterEntityByLayerJob, + m_filterProximityJob, + m_materialGathererJobs, + m_renderViewBuilderJobs), + JobTypes::SyncRenderViewInitialization); + +} + +QVector<Qt3DCore::QAspectJobPtr> RenderViewBuilder::buildJobHierachy() const +{ + QVector<Qt3DCore::QAspectJobPtr> jobs; + + jobs.reserve(m_materialGathererJobs.size() + m_renderViewBuilderJobs.size() + 11); + + // Set dependencies + + // Finish the skinning palette job before processing renderviews + // TODO: Maybe only update skinning palettes for non-culled entities + m_renderViewJob->addDependency(m_renderer->updateSkinningPaletteJob()); + + m_syncFrustumCullingJob->addDependency(m_renderer->updateWorldTransformJob()); + m_syncFrustumCullingJob->addDependency(m_renderer->updateShaderDataTransformJob()); + m_syncFrustumCullingJob->addDependency(m_syncRenderViewInitializationJob); + + m_frustumCullingJob->addDependency(m_renderer->expandBoundingVolumeJob()); + m_frustumCullingJob->addDependency(m_syncFrustumCullingJob); + + m_setClearDrawBufferIndexJob->addDependency(m_syncRenderViewInitializationJob); + + m_syncRenderViewInitializationJob->addDependency(m_renderViewJob); + + m_filterProximityJob->addDependency(m_renderer->expandBoundingVolumeJob()); + m_filterProximityJob->addDependency(m_syncRenderViewInitializationJob); + + m_syncRenderCommandBuildingJob->addDependency(m_syncRenderViewInitializationJob); + m_syncRenderCommandBuildingJob->addDependency(m_renderableEntityFilterJob); + m_syncRenderCommandBuildingJob->addDependency(m_computableEntityFilterJob); + m_syncRenderCommandBuildingJob->addDependency(m_filterProximityJob); + m_syncRenderCommandBuildingJob->addDependency(m_lightGathererJob); + m_syncRenderCommandBuildingJob->addDependency(m_frustumCullingJob); + + // Ensure the RenderThread won't be able to process dirtyResources + // before they have been completely gathered + m_syncRenderCommandBuildingJob->addDependency(m_renderer->introspectShadersJob()); + m_syncRenderCommandBuildingJob->addDependency(m_renderer->bufferGathererJob()); + m_syncRenderCommandBuildingJob->addDependency(m_renderer->textureGathererJob()); + + for (const auto &renderViewCommandBuilder : qAsConst(m_renderViewBuilderJobs)) { + renderViewCommandBuilder->addDependency(m_syncRenderCommandBuildingJob); + m_syncRenderViewCommandBuildersJob->addDependency(renderViewCommandBuilder); + } + + m_renderer->frameCleanupJob()->addDependency(m_syncRenderViewCommandBuildersJob); + m_renderer->frameCleanupJob()->addDependency(m_setClearDrawBufferIndexJob); + + // Add jobs + jobs.push_back(m_renderViewJob); // Step 1 + jobs.push_back(m_renderableEntityFilterJob); // Step 1 + jobs.push_back(m_lightGathererJob); // Step 1 + + // Note: do it only if OpenGL 4.3+ available + jobs.push_back(m_computableEntityFilterJob); // Step 1 + + jobs.push_back(m_syncRenderViewInitializationJob); // Step 2 + + if (m_layerCacheNeedsToBeRebuilt) { + m_filterEntityByLayerJob->addDependency(m_syncRenderViewInitializationJob); + m_filterEntityByLayerJob->addDependency(m_renderer->updateTreeEnabledJob()); + + m_syncFilterEntityByLayerJob->addDependency(m_filterEntityByLayerJob); + m_syncRenderCommandBuildingJob->addDependency(m_syncFilterEntityByLayerJob); + + jobs.push_back(m_filterEntityByLayerJob); // Step 3 + jobs.push_back(m_syncFilterEntityByLayerJob); // Step 4 + } + jobs.push_back(m_syncFrustumCullingJob); // Step 3 + jobs.push_back(m_filterProximityJob); // Step 3 + jobs.push_back(m_setClearDrawBufferIndexJob); // Step 3 + + if (m_materialGathererCacheNeedsToBeRebuilt) { + for (const auto &materialGatherer : qAsConst(m_materialGathererJobs)) { + materialGatherer->addDependency(m_syncRenderViewInitializationJob); + materialGatherer->addDependency(m_renderer->introspectShadersJob()); + materialGatherer->addDependency(m_renderer->filterCompatibleTechniqueJob()); + jobs.push_back(materialGatherer); // Step3 + m_syncMaterialGathererJob->addDependency(materialGatherer); + } + m_syncRenderCommandBuildingJob->addDependency(m_syncMaterialGathererJob); + + jobs.push_back(m_syncMaterialGathererJob); // Step 3 + } + + jobs.push_back(m_frustumCullingJob); // Step 4 + jobs.push_back(m_syncRenderCommandBuildingJob); // Step 5 + + for (const auto &renderViewCommandBuilder : qAsConst(m_renderViewBuilderJobs)) // Step 6 + jobs.push_back(renderViewCommandBuilder); + + jobs.push_back(m_syncRenderViewCommandBuildersJob); // Step 7 + + return jobs; +} + +Renderer *RenderViewBuilder::renderer() const +{ + return m_renderer; +} + +int RenderViewBuilder::renderViewIndex() const +{ + return m_renderViewIndex; +} + +void RenderViewBuilder::setLayerCacheNeedsToBeRebuilt(bool needsToBeRebuilt) +{ + m_layerCacheNeedsToBeRebuilt = needsToBeRebuilt; +} + +bool RenderViewBuilder::layerCacheNeedsToBeRebuilt() const +{ + return m_layerCacheNeedsToBeRebuilt; +} + +void RenderViewBuilder::setMaterialGathererCacheNeedsToBeRebuilt(bool needsToBeRebuilt) +{ + m_materialGathererCacheNeedsToBeRebuilt = needsToBeRebuilt; +} + +bool RenderViewBuilder::materialGathererCacheNeedsToBeRebuilt() const +{ + return m_materialGathererCacheNeedsToBeRebuilt; +} + +int RenderViewBuilder::optimalJobCount() +{ + return RenderViewBuilder::m_optimalParallelJobCount; +} + +QVector<Entity *> RenderViewBuilder::entitiesInSubset(const QVector<Entity *> &entities, const QVector<Entity *> &subset) +{ + QVector<Entity *> intersection; + intersection.reserve(qMin(entities.size(), subset.size())); + std::set_intersection(entities.begin(), entities.end(), + subset.begin(), subset.end(), + std::back_inserter(intersection)); + + return intersection; +} + +} // Render + +} // Qt3DRender + +QT_END_NAMESPACE diff --git a/src/render/renderers/opengl/renderer/renderviewbuilder_p.h b/src/render/renderers/opengl/renderer/renderviewbuilder_p.h new file mode 100644 index 000000000..818313500 --- /dev/null +++ b/src/render/renderers/opengl/renderer/renderviewbuilder_p.h @@ -0,0 +1,149 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Klaralvdalens Datakonsult AB (KDAB). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT3DRENDER_RENDER_RENDERVIEWBUILDER_H +#define QT3DRENDER_RENDER_RENDERVIEWBUILDER_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 <functional> +#include <Qt3DCore/qaspectjob.h> +#include <Qt3DRender/private/filterentitybycomponentjob_p.h> +#include <Qt3DRender/private/filterlayerentityjob_p.h> +#include <Qt3DRender/private/genericlambdajob_p.h> +#include <Qt3DRender/private/materialparametergathererjob_p.h> +#include <Qt3DRender/private/nodemanagers_p.h> +#include <Qt3DRender/private/renderviewbuilderjob_p.h> +#include <Qt3DRender/private/renderview_p.h> +#include <Qt3DRender/private/frustumcullingjob_p.h> +#include <Qt3DRender/private/lightgatherer_p.h> +#include <Qt3DRender/private/filterproximitydistancejob_p.h> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { + +namespace Render { + +class Renderer; + +using SynchronizerJobPtr = GenericLambdaJobPtr<std::function<void()>>; +using ComputableEntityFilterPtr = FilterEntityByComponentJobPtr<Render::ComputeCommand, Render::Material>; +using RenderableEntityFilterPtr = FilterEntityByComponentJobPtr<Render::GeometryRenderer, Render::Material>; + +class Q_AUTOTEST_EXPORT RenderViewBuilder +{ +public: + explicit RenderViewBuilder(Render::FrameGraphNode *leafNode, int renderViewIndex, Renderer *renderer); + + RenderViewInitializerJobPtr renderViewJob() const; + FilterLayerEntityJobPtr filterEntityByLayerJob() const; + LightGathererPtr lightGathererJob() const; + RenderableEntityFilterPtr renderableEntityFilterJob() const; + ComputableEntityFilterPtr computableEntityFilterJob() const; + FrustumCullingJobPtr frustumCullingJob() const; + QVector<RenderViewBuilderJobPtr> renderViewBuilderJobs() const; + QVector<MaterialParameterGathererJobPtr> materialGathererJobs() const; + SynchronizerJobPtr syncRenderViewInitializationJob() const; + SynchronizerJobPtr syncFrustumCullingJob() const; + SynchronizerJobPtr syncRenderCommandBuildingJob() const; + SynchronizerJobPtr syncRenderViewCommandBuildersJob() const; + SynchronizerJobPtr setClearDrawBufferIndexJob() const; + SynchronizerJobPtr syncFilterEntityByLayerJob() const; + FilterProximityDistanceJobPtr filterProximityJob() const; + SynchronizerJobPtr syncMaterialGathererJob() const; + + void prepareJobs(); + QVector<Qt3DCore::QAspectJobPtr> buildJobHierachy() const; + + Renderer *renderer() const; + int renderViewIndex() const; + + void setLayerCacheNeedsToBeRebuilt(bool needsToBeRebuilt); + bool layerCacheNeedsToBeRebuilt() const; + void setMaterialGathererCacheNeedsToBeRebuilt(bool needsToBeRebuilt); + bool materialGathererCacheNeedsToBeRebuilt() const; + + static int optimalJobCount(); + static QVector<Entity *> entitiesInSubset(const QVector<Entity *> &entities, const QVector<Entity *> &subset); + +private: + Render::FrameGraphNode *m_leafNode; + const int m_renderViewIndex; + Renderer *m_renderer; + bool m_layerCacheNeedsToBeRebuilt; + bool m_materialGathererCacheNeedsToBeRebuilt; + + RenderViewInitializerJobPtr m_renderViewJob; + FilterLayerEntityJobPtr m_filterEntityByLayerJob; + LightGathererPtr m_lightGathererJob; + RenderableEntityFilterPtr m_renderableEntityFilterJob; + ComputableEntityFilterPtr m_computableEntityFilterJob; + FrustumCullingJobPtr m_frustumCullingJob; + QVector<RenderViewBuilderJobPtr> m_renderViewBuilderJobs; + QVector<MaterialParameterGathererJobPtr> m_materialGathererJobs; + + SynchronizerJobPtr m_syncRenderViewInitializationJob; + SynchronizerJobPtr m_syncFrustumCullingJob; + SynchronizerJobPtr m_syncRenderCommandBuildingJob; + SynchronizerJobPtr m_syncRenderViewCommandBuildersJob; + SynchronizerJobPtr m_setClearDrawBufferIndexJob; + SynchronizerJobPtr m_syncFilterEntityByLayerJob; + SynchronizerJobPtr m_syncMaterialGathererJob; + FilterProximityDistanceJobPtr m_filterProximityJob; + + static const int m_optimalParallelJobCount; +}; + +} // Render + +} // Qt3DRender + +QT_END_NAMESPACE + +#endif // QT3DRENDER_RENDER_RENDERVIEWBUILDER_H diff --git a/src/render/renderers/opengl/renderer/shaderparameterpack.cpp b/src/render/renderers/opengl/renderer/shaderparameterpack.cpp new file mode 100644 index 000000000..f78e45a5e --- /dev/null +++ b/src/render/renderers/opengl/renderer/shaderparameterpack.cpp @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "shaderparameterpack_p.h" + +#include <Qt3DRender/private/graphicscontext_p.h> +#include <Qt3DRender/private/texture_p.h> + +#include <Qt3DCore/private/qframeallocator_p.h> + +#include <QOpenGLShaderProgram> +#include <QDebug> +#include <QColor> +#include <QQuaternion> +#include <Qt3DRender/private/renderlogging_p.h> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { +namespace Render { + +ShaderParameterPack::~ShaderParameterPack() +{ + m_uniforms.clear(); +} + +void ShaderParameterPack::setUniform(const int glslNameId, const UniformValue &val) +{ + m_uniforms.insert(glslNameId, val); +} + +void ShaderParameterPack::setTexture(const int glslNameId, int uniformArrayIndex, Qt3DCore::QNodeId texId) +{ + for (int t=0; t<m_textures.size(); ++t) { + if (m_textures[t].glslNameId != glslNameId || m_textures[t].uniformArrayIndex != uniformArrayIndex) + continue; + + m_textures[t].texId = texId; + return; + } + + m_textures.append(NamedTexture(glslNameId, texId, uniformArrayIndex)); +} + +// Contains Uniform Block Index and QNodeId of the ShaderData (UBO) +void ShaderParameterPack::setUniformBuffer(BlockToUBO blockToUBO) +{ + m_uniformBuffers.append(std::move(blockToUBO)); +} + +void ShaderParameterPack::setShaderStorageBuffer(BlockToSSBO blockToSSBO) +{ + m_shaderStorageBuffers.push_back(std::move(blockToSSBO)); +} + +void ShaderParameterPack::setSubmissionUniform(const ShaderUniform &uniform) +{ + m_submissionUniforms.push_back(uniform); +} + +} // namespace Render +} // namespace Qt3DRender + +QT_END_NAMESPACE diff --git a/src/render/renderers/opengl/renderer/shaderparameterpack_p.h b/src/render/renderers/opengl/renderer/shaderparameterpack_p.h new file mode 100644 index 000000000..5703bb17b --- /dev/null +++ b/src/render/renderers/opengl/renderer/shaderparameterpack_p.h @@ -0,0 +1,145 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT3DRENDER_RENDER_SHADERPARAMETERPACK_P_H +#define QT3DRENDER_RENDER_SHADERPARAMETERPACK_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 <QVariant> +#include <QByteArray> +#include <QVector> +#include <QOpenGLShaderProgram> +#include <Qt3DCore/qnodeid.h> +#include <Qt3DRender/private/renderlogging_p.h> +#include <Qt3DRender/private/shadervariables_p.h> +#include <Qt3DRender/private/uniform_p.h> + +QT_BEGIN_NAMESPACE + +class QOpenGLShaderProgram; + +namespace Qt3DCore { +class QFrameAllocator; +} + +namespace Qt3DRender { +namespace Render { + +class GraphicsContext; + +struct BlockToUBO { + int m_blockIndex; + Qt3DCore::QNodeId m_bufferID; + bool m_needsUpdate; + QHash<QString, QVariant> m_updatedProperties; +}; +QT3D_DECLARE_TYPEINFO_2(Qt3DRender, Render, BlockToUBO, Q_MOVABLE_TYPE) + +struct BlockToSSBO { + int m_blockIndex; + Qt3DCore::QNodeId m_bufferID; +}; +QT3D_DECLARE_TYPEINFO_2(Qt3DRender, Render, BlockToSSBO, Q_PRIMITIVE_TYPE) + + +typedef QHash<int, UniformValue> PackUniformHash; + +class Q_AUTOTEST_EXPORT ShaderParameterPack +{ +public: + ~ShaderParameterPack(); + + void setUniform(const int glslNameId, const UniformValue &val); + void setTexture(const int glslNameId, int uniformArrayIndex, Qt3DCore::QNodeId id); + void setUniformBuffer(BlockToUBO blockToUBO); + void setShaderStorageBuffer(BlockToSSBO blockToSSBO); + void setSubmissionUniform(const ShaderUniform &uniform); + + inline PackUniformHash &uniforms() { return m_uniforms; } + inline const PackUniformHash &uniforms() const { return m_uniforms; } + UniformValue uniform(const int glslNameId) const { return m_uniforms.value(glslNameId); } + + struct NamedTexture + { + NamedTexture() {} + NamedTexture(const int glslNameId, Qt3DCore::QNodeId texId, int uniformArrayIndex) + : glslNameId(glslNameId) + , texId(texId) + , uniformArrayIndex(uniformArrayIndex) + { } + + int glslNameId; + Qt3DCore::QNodeId texId; + int uniformArrayIndex; + }; + + inline QVector<NamedTexture> textures() const { return m_textures; } + inline QVector<BlockToUBO> uniformBuffers() const { return m_uniformBuffers; } + inline QVector<BlockToSSBO> shaderStorageBuffers() const { return m_shaderStorageBuffers; } + inline QVector<ShaderUniform> submissionUniforms() const { return m_submissionUniforms; } +private: + PackUniformHash m_uniforms; + + QVector<NamedTexture> m_textures; + QVector<BlockToUBO> m_uniformBuffers; + QVector<BlockToSSBO> m_shaderStorageBuffers; + QVector<ShaderUniform> m_submissionUniforms; + + friend class RenderView; +}; +QT3D_DECLARE_TYPEINFO_2(Qt3DRender, Render, ShaderParameterPack::NamedTexture, Q_PRIMITIVE_TYPE) + +} // namespace Render +} // namespace Qt3DRender + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(Qt3DRender::Render::ShaderParameterPack) + +#endif // QT3DRENDER_RENDER_SHADERPARAMETERPACK_P_H diff --git a/src/render/renderers/opengl/renderer/shadervariables_p.h b/src/render/renderers/opengl/renderer/shadervariables_p.h new file mode 100644 index 000000000..e0fa07dff --- /dev/null +++ b/src/render/renderers/opengl/renderer/shadervariables_p.h @@ -0,0 +1,152 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT3DRENDER_RENDER_SHADERVARIABLES_P_H +#define QT3DRENDER_RENDER_SHADERVARIABLES_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 <Qt3DRender/qt3drender_global.h> +#include <QOpenGLContext> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { + +namespace Render { + +struct ShaderAttribute +{ + ShaderAttribute() + : m_nameId(-1) + , m_type(0) + , m_size(0) + , m_location(-1) + {} + + QString m_name; + int m_nameId; + GLenum m_type; + int m_size; + int m_location; +}; +QT3D_DECLARE_TYPEINFO_2(Qt3DRender, Render, ShaderAttribute, Q_MOVABLE_TYPE) + +struct ShaderUniform +{ + ShaderUniform() + : m_nameId(-1) + , m_type(GL_NONE) + , m_size(0) + , m_offset(-1) + , m_location(-1) + , m_blockIndex(-1) + , m_arrayStride(-1) + , m_matrixStride(-1) + , m_rawByteSize(0) + {} + + QString m_name; + int m_nameId; + GLenum m_type; + int m_size; + int m_offset; // -1 default, >= 0 if uniform defined in uniform block + int m_location; // -1 if uniform defined in a uniform block + int m_blockIndex; // -1 is the default, >= 0 if uniform defined in uniform block + int m_arrayStride; // -1 is the default, >= 0 if uniform defined in uniform block and if it's an array + int m_matrixStride; // -1 is the default, >= 0 uniform defined in uniform block and is a matrix + uint m_rawByteSize; // contains byte size (size / type / strides) + // size, offset and strides are in bytes +}; +QT3D_DECLARE_TYPEINFO_2(Qt3DRender, Render, ShaderUniform, Q_MOVABLE_TYPE) + +struct ShaderUniformBlock +{ + ShaderUniformBlock() + : m_nameId(-1) + , m_index(-1) + , m_binding(-1) + , m_activeUniformsCount(0) + , m_size(0) + {} + + QString m_name; + int m_nameId; + int m_index; + int m_binding; + int m_activeUniformsCount; + int m_size; +}; +QT3D_DECLARE_TYPEINFO_2(Qt3DRender, Render, ShaderUniformBlock, Q_MOVABLE_TYPE) + +struct ShaderStorageBlock +{ + ShaderStorageBlock() + : m_nameId(-1) + , m_index(-1) + , m_binding(-1) + , m_size(0) + , m_activeVariablesCount(0) + {} + + QString m_name; + int m_nameId; + int m_index; + int m_binding; + int m_size; + int m_activeVariablesCount; +}; +QT3D_DECLARE_TYPEINFO_2(Qt3DRender, Render, ShaderStorageBlock, Q_MOVABLE_TYPE) + +} // namespace Render + +} // namespace Qt3DRender + +QT_END_NAMESPACE + +#endif // QT3DRENDER_RENDER_SHADERVARIABLES_P_H |