diff options
Diffstat (limited to 'src/plugins/renderers')
80 files changed, 15480 insertions, 309 deletions
diff --git a/src/plugins/renderers/opengl/debug/imguirenderer.cpp b/src/plugins/renderers/opengl/debug/imguirenderer.cpp index 8dd9b98fe..0acee945a 100644 --- a/src/plugins/renderers/opengl/debug/imguirenderer.cpp +++ b/src/plugins/renderers/opengl/debug/imguirenderer.cpp @@ -283,13 +283,21 @@ void ImGuiRenderer::renderDebugOverlay(const QVector<RenderView *> &renderViews, QMetaObject::invokeMethod(m_renderer->services()->systemInformation(), "dumpCommand", Qt::QueuedConnection, Q_ARG(QString, QLatin1String("render framegraph"))); ImGui::SameLine(); - if (ImGui::Button("FrameGraph Paths##1")) + if (ImGui::Button("Render Views##1")) QMetaObject::invokeMethod(m_renderer->services()->systemInformation(), "dumpCommand", Qt::QueuedConnection, Q_ARG(QString, QLatin1String("render framepaths"))); + + ImGui::AlignTextToFramePadding(); + ImGui::Text(" "); + ImGui::SameLine(); + if (ImGui::Button("Filter State##1")) + QMetaObject::invokeMethod(m_renderer->services()->systemInformation(), "dumpCommand", + Qt::QueuedConnection, Q_ARG(QString, QLatin1String("render filterstates"))); ImGui::SameLine(); if (ImGui::Button("JobsGraph##1")) QMetaObject::invokeMethod(m_renderer->services()->systemInformation(), "dumpCommand", Qt::QueuedConnection, Q_ARG(QString, QLatin1String("dump jobs"))); + ImGui::End(); if (m_showGLInfoWindow) diff --git a/src/plugins/renderers/opengl/graphicshelpers/submissioncontext.cpp b/src/plugins/renderers/opengl/graphicshelpers/submissioncontext.cpp index 519f2ae98..d4d3457ab 100644 --- a/src/plugins/renderers/opengl/graphicshelpers/submissioncontext.cpp +++ b/src/plugins/renderers/opengl/graphicshelpers/submissioncontext.cpp @@ -1166,7 +1166,7 @@ void SubmissionContext::setUpdatedTexture(const Qt3DCore::QNodeIdVector &updated // It will be easier if the QGraphicContext applies the QUniformPack // than the other way around -bool SubmissionContext::setParameters(ShaderParameterPack ¶meterPack) +bool SubmissionContext::setParameters(ShaderParameterPack ¶meterPack, GLShader *shader) { static const int irradianceId = StringToInt::lookupId(QLatin1String("envLight.irradiance")); static const int specularId = StringToInt::lookupId(QLatin1String("envLight.specular")); @@ -1231,7 +1231,7 @@ bool SubmissionContext::setParameters(ShaderParameterPack ¶meterPack) } } - QOpenGLShaderProgram *shader = activeShader(); + QOpenGLShaderProgram *glShader = activeShader(); // TO DO: We could cache the binding points somehow and only do the binding when necessary // for SSBO and UBO @@ -1245,7 +1245,7 @@ bool SubmissionContext::setParameters(ShaderParameterPack ¶meterPack) // This is currently not required as we are introspecting the bindingIndex // value from the shaders and not replacing them, making such a call useless // bindShaderStorageBlock(shader->programId(), b.m_blockIndex, b.m_bindingIndex); - bindShaderStorageBlock(shader->programId(), b.m_blockIndex, b.m_bindingIndex); + bindShaderStorageBlock(glShader->programId(), b.m_blockIndex, b.m_bindingIndex); // Needed to avoid conflict where the buffer would already // be bound as a VertexArray bindGLBuffer(ssbo, GLBuffer::ShaderStorageBuffer); @@ -1260,7 +1260,7 @@ bool SubmissionContext::setParameters(ShaderParameterPack ¶meterPack) for (const BlockToUBO &b : blockToUBOs) { Buffer *cpuBuffer = m_renderer->nodeManagers()->bufferManager()->lookupResource(b.m_bufferID); GLBuffer *ubo = glBufferForRenderBuffer(cpuBuffer); - bindUniformBlock(shader->programId(), b.m_blockIndex, uboIndex); + bindUniformBlock(glShader->programId(), b.m_blockIndex, uboIndex); // Needed to avoid conflict where the buffer would already // be bound as a VertexArray bindGLBuffer(ubo, GLBuffer::UniformBuffer); @@ -1270,11 +1270,11 @@ bool SubmissionContext::setParameters(ShaderParameterPack ¶meterPack) // Update uniforms in the Default Uniform Block const PackUniformHash values = parameterPack.uniforms(); - const QVector<ShaderUniform> activeUniforms = parameterPack.submissionUniforms(); + const QVector<int> &activeUniformsIndices = parameterPack.submissionUniformIndices(); + const QVector<ShaderUniform> &shaderUniforms = shader->uniforms(); - for (const ShaderUniform &uniform : activeUniforms) { - // We can use [] as we are sure the the uniform wouldn't - // be un activeUniforms if there wasn't a matching value + for (const int shaderUniformIndex : activeUniformsIndices) { + const ShaderUniform &uniform = shaderUniforms[shaderUniformIndex]; const UniformValue &v = values.value(uniform.m_nameId); // skip invalid textures/images diff --git a/src/plugins/renderers/opengl/graphicshelpers/submissioncontext_p.h b/src/plugins/renderers/opengl/graphicshelpers/submissioncontext_p.h index 59b2b78f3..4c895013c 100644 --- a/src/plugins/renderers/opengl/graphicshelpers/submissioncontext_p.h +++ b/src/plugins/renderers/opengl/graphicshelpers/submissioncontext_p.h @@ -137,7 +137,7 @@ public: GLBuffer *glBufferForRenderBuffer(Buffer *buf); // Parameters - bool setParameters(ShaderParameterPack ¶meterPack); + bool setParameters(ShaderParameterPack ¶meterPack, GLShader *shader); // RenderState void setCurrentStateSet(RenderStateSet* ss); diff --git a/src/plugins/renderers/opengl/jobs/materialparametergathererjob.cpp b/src/plugins/renderers/opengl/jobs/materialparametergathererjob.cpp index 1f51ceba3..1dd26b847 100644 --- a/src/plugins/renderers/opengl/jobs/materialparametergathererjob.cpp +++ b/src/plugins/renderers/opengl/jobs/materialparametergathererjob.cpp @@ -59,8 +59,32 @@ const int likelyNumberOfParameters = 24; } // anonymous +class MaterialParameterGathererJobPrivate : public Qt3DCore::QAspectJobPrivate +{ +public: + MaterialParameterGathererJobPrivate(MaterialParameterGathererJob *q) : q_ptr(q) { } + ~MaterialParameterGathererJobPrivate() override = default; + + bool isRequired() const override; + void postFrame(Qt3DCore::QAspectManager *manager) override; + + MaterialParameterGathererJob *q_ptr; + Q_DECLARE_PUBLIC(MaterialParameterGathererJob) +}; + +bool MaterialParameterGathererJobPrivate::isRequired() const +{ + return !q_ptr->m_handles.isEmpty(); +} + +void MaterialParameterGathererJobPrivate::postFrame(Qt3DCore::QAspectManager *manager) +{ + Q_UNUSED(manager) + materialParameterGathererCounter = 0; +} + MaterialParameterGathererJob::MaterialParameterGathererJob() - : Qt3DCore::QAspectJob() + : Qt3DCore::QAspectJob(*new MaterialParameterGathererJobPrivate(this)) , m_manager(nullptr) , m_techniqueFilter(nullptr) , m_renderPassFilter(nullptr) diff --git a/src/plugins/renderers/opengl/jobs/materialparametergathererjob_p.h b/src/plugins/renderers/opengl/jobs/materialparametergathererjob_p.h index cd5af8124..8c9997827 100644 --- a/src/plugins/renderers/opengl/jobs/materialparametergathererjob_p.h +++ b/src/plugins/renderers/opengl/jobs/materialparametergathererjob_p.h @@ -71,6 +71,7 @@ namespace OpenGL { class Renderer; // TO be executed for each FrameGraph branch with a given RenderPassFilter/TechniqueFilter +class MaterialParameterGathererJobPrivate; class Q_AUTOTEST_EXPORT MaterialParameterGathererJob : public Qt3DCore::QAspectJob { @@ -96,6 +97,8 @@ private: // Material id to array of RenderPasse with parameters MaterialParameterGathererData m_parameters; QVector<HMaterial> m_handles; + + Q_DECLARE_PRIVATE(MaterialParameterGathererJob) }; typedef QSharedPointer<MaterialParameterGathererJob> MaterialParameterGathererJobPtr; diff --git a/src/plugins/renderers/opengl/jobs/renderviewcommandbuilderjob.cpp b/src/plugins/renderers/opengl/jobs/renderviewcommandbuilderjob.cpp index d8a33b693..d5e17e0bf 100644 --- a/src/plugins/renderers/opengl/jobs/renderviewcommandbuilderjob.cpp +++ b/src/plugins/renderers/opengl/jobs/renderviewcommandbuilderjob.cpp @@ -53,8 +53,32 @@ namespace { int renderViewInstanceCounter = 0; } // anonymous +class RenderViewCommandBuilderJobPrivate : public Qt3DCore::QAspectJobPrivate +{ +public: + RenderViewCommandBuilderJobPrivate(RenderViewCommandBuilderJob *q) : q_ptr(q) { } + ~RenderViewCommandBuilderJobPrivate() override = default; + + bool isRequired() const override; + void postFrame(Qt3DCore::QAspectManager *manager) override; + + RenderViewCommandBuilderJob *q_ptr; + Q_DECLARE_PUBLIC(RenderViewCommandBuilderJob) +}; + +bool RenderViewCommandBuilderJobPrivate::isRequired() const +{ + return q_ptr->m_renderView && !q_ptr->m_renderView->noDraw() && q_ptr->m_count > 0; +} + +void RenderViewCommandBuilderJobPrivate::postFrame(Qt3DCore::QAspectManager *manager) +{ + Q_UNUSED(manager) + renderViewInstanceCounter = 0; +} + RenderViewCommandBuilderJob::RenderViewCommandBuilderJob() - : Qt3DCore::QAspectJob() + : Qt3DCore::QAspectJob(*new RenderViewCommandBuilderJobPrivate(this)) , m_offset(0) , m_count(0) , m_renderView(nullptr) @@ -64,18 +88,14 @@ RenderViewCommandBuilderJob::RenderViewCommandBuilderJob() void RenderViewCommandBuilderJob::run() { - if (!m_renderView->noDraw()) { - if (m_count == 0) - return; - - const bool isDraw = !m_renderView->isCompute(); - if (isDraw) - m_commandData = m_renderView->buildDrawRenderCommands(m_entities, m_offset, m_count); - else - m_commandData = m_renderView->buildComputeRenderCommands(m_entities, m_offset, m_count); - } + const bool isDraw = !m_renderView->isCompute(); + if (isDraw) + m_commandData = m_renderView->buildDrawRenderCommands(m_entities, m_offset, m_count); + else + m_commandData = m_renderView->buildComputeRenderCommands(m_entities, m_offset, m_count); } + } // OpenGL } // Render diff --git a/src/plugins/renderers/opengl/jobs/renderviewcommandbuilderjob_p.h b/src/plugins/renderers/opengl/jobs/renderviewcommandbuilderjob_p.h index e9f8bb10a..52c055285 100644 --- a/src/plugins/renderers/opengl/jobs/renderviewcommandbuilderjob_p.h +++ b/src/plugins/renderers/opengl/jobs/renderviewcommandbuilderjob_p.h @@ -63,6 +63,8 @@ namespace Render { namespace OpenGL { +class RenderViewCommandBuilderJobPrivate; + class Q_AUTOTEST_EXPORT RenderViewCommandBuilderJob : public Qt3DCore::QAspectJob { public: @@ -85,6 +87,8 @@ private: RenderView *m_renderView; QVector<Entity *> m_entities; EntityRenderCommandData m_commandData; + + Q_DECLARE_PRIVATE(RenderViewCommandBuilderJob) }; typedef QSharedPointer<RenderViewCommandBuilderJob> RenderViewCommandBuilderJobPtr; diff --git a/src/plugins/renderers/opengl/jobs/renderviewcommandupdaterjob.cpp b/src/plugins/renderers/opengl/jobs/renderviewcommandupdaterjob.cpp index ed0854ecf..9bbdf50fd 100644 --- a/src/plugins/renderers/opengl/jobs/renderviewcommandupdaterjob.cpp +++ b/src/plugins/renderers/opengl/jobs/renderviewcommandupdaterjob.cpp @@ -60,17 +60,16 @@ public: RenderViewCommandUpdaterJobPrivate(RenderViewCommandUpdaterJob *q) : q_ptr(q) { } ~RenderViewCommandUpdaterJobPrivate() override = default; - bool isRequired() override; + bool isRequired() const override; void postFrame(Qt3DCore::QAspectManager *manager) override; RenderViewCommandUpdaterJob *q_ptr; Q_DECLARE_PUBLIC(RenderViewCommandUpdaterJob) }; -bool RenderViewCommandUpdaterJobPrivate::isRequired() +bool RenderViewCommandUpdaterJobPrivate::isRequired() const { - Q_Q(RenderViewCommandUpdaterJob); - + Q_Q(const RenderViewCommandUpdaterJob); return q->m_renderView && !q->m_renderView->noDraw() && q->m_count > 0; } diff --git a/src/plugins/renderers/opengl/jobs/renderviewjobutils.cpp b/src/plugins/renderers/opengl/jobs/renderviewjobutils.cpp index d4835054b..8774ac368 100644 --- a/src/plugins/renderers/opengl/jobs/renderviewjobutils.cpp +++ b/src/plugins/renderers/opengl/jobs/renderviewjobutils.cpp @@ -473,7 +473,10 @@ UniformBlockValueBuilder::~UniformBlockValueBuilder() { } -void UniformBlockValueBuilder::buildActiveUniformNameValueMapHelper(ShaderData *currentShaderData, const QString &blockName, const QString &qmlPropertyName, const QVariant &value) +void UniformBlockValueBuilder::buildActiveUniformNameValueMapHelper(const ShaderData *currentShaderData, + const QString &blockName, + const QString &qmlPropertyName, + const QVariant &value) { // In the end, values are either scalar or a scalar array // Composed elements (structs, structs array) are simplified into simple scalars @@ -534,7 +537,9 @@ void UniformBlockValueBuilder::buildActiveUniformNameValueMapHelper(ShaderData * } } -void UniformBlockValueBuilder::buildActiveUniformNameValueMapStructHelper(ShaderData *rShaderData, const QString &blockName, const QString &qmlPropertyName) +void UniformBlockValueBuilder::buildActiveUniformNameValueMapStructHelper(const ShaderData *rShaderData, + const QString &blockName, + const QString &qmlPropertyName) { const QHash<QString, ShaderData::PropertyValue> &properties = rShaderData->properties(); auto it = properties.begin(); diff --git a/src/plugins/renderers/opengl/jobs/renderviewjobutils_p.h b/src/plugins/renderers/opengl/jobs/renderviewjobutils_p.h index 7b5ba2bfd..bc5bfd8aa 100644 --- a/src/plugins/renderers/opengl/jobs/renderviewjobutils_p.h +++ b/src/plugins/renderers/opengl/jobs/renderviewjobutils_p.h @@ -162,11 +162,11 @@ struct Q_AUTOTEST_EXPORT UniformBlockValueBuilder QT3D_ALIGNED_MALLOC_AND_FREE() - void buildActiveUniformNameValueMapHelper(ShaderData *currentShaderData, + void buildActiveUniformNameValueMapHelper(const ShaderData *currentShaderData, const QString &blockName, const QString &qmlPropertyName, const QVariant &value); - void buildActiveUniformNameValueMapStructHelper(ShaderData *rShaderData, + void buildActiveUniformNameValueMapStructHelper(const ShaderData *rShaderData, const QString &blockName, const QString &qmlPropertyName = QString()); diff --git a/src/plugins/renderers/opengl/opengl.pro b/src/plugins/renderers/opengl/opengl.pro index ebd4ff5d1..f098513e8 100644 --- a/src/plugins/renderers/opengl/opengl.pro +++ b/src/plugins/renderers/opengl/opengl.pro @@ -1,5 +1,8 @@ TARGET = openglrenderer +# We use QT_AUTOTEST_EXPORT to test the plug-ins, which needs QT_BUILDING_QT +DEFINES += QT_BUILDING_QT + include(opengl.pri) SOURCES += \ diff --git a/src/plugins/renderers/opengl/renderer/gllights.cpp b/src/plugins/renderers/opengl/renderer/gllights.cpp new file mode 100644 index 000000000..c87fd766b --- /dev/null +++ b/src/plugins/renderers/opengl/renderer/gllights.cpp @@ -0,0 +1,345 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 <Qt3DRender/private/stringtoint_p.h> +#include "gllights_p.h" + +#define LIGHT_POSITION_NAME QLatin1String(".position") +#define LIGHT_TYPE_NAME QLatin1String(".type") +#define LIGHT_COLOR_NAME QLatin1String(".color") +#define LIGHT_INTENSITY_NAME QLatin1String(".intensity") +#define LIGHT_DIRECTION_NAME QLatin1String(".direction") +#define LIGHT_LINEAR_ATTENUATION_NAME QLatin1String(".linearAttenuation") +#define LIGHT_QUADRATIC_ATTENUATION_NAME QLatin1String(".quadraticAttenuation") +#define LIGHT_CONSTANT_ATTENUATION_NAME QLatin1String(".constantAttenuation") +#define LIGHT_CUT_OFF_ANGLE_NAME QLatin1String(".cutOffAngle") + +#define DECLARE_LIGHT_STRUCT_NAME(idx)\ + QLatin1String("lights[") + QLatin1Char(char('0' + idx)) + QLatin1Char(']') + +#define DECLARE_LIGHT_POSITION_NAME(idx)\ + StringToInt::lookupId(LIGHT_STRUCT_NAMES[idx] + LIGHT_POSITION_NAME) + +#define DECLARE_LIGHT_TYPE_NAME(idx)\ + StringToInt::lookupId(LIGHT_STRUCT_NAMES[idx] + LIGHT_TYPE_NAME) + +#define DECLARE_LIGHT_COLOR_NAME(idx)\ + StringToInt::lookupId(LIGHT_STRUCT_NAMES[idx] + LIGHT_COLOR_NAME) + +#define DECLARE_LIGHT_INTENSITY_NAME(idx)\ + StringToInt::lookupId(LIGHT_STRUCT_NAMES[idx] + LIGHT_INTENSITY_NAME) + +#define DECLARE_LIGHT_DIRECTION_NAME(idx)\ + StringToInt::lookupId(LIGHT_STRUCT_NAMES[idx] + LIGHT_DIRECTION_NAME) + +#define DECLARE_LIGHT_LINEAR_ATTENUATION_NAME(idx)\ + StringToInt::lookupId(LIGHT_STRUCT_NAMES[idx] + LIGHT_LINEAR_ATTENUATION_NAME) + +#define DECLARE_LIGHT_QUADRATIC_ATTENUATION_NAME(idx)\ + StringToInt::lookupId(LIGHT_STRUCT_NAMES[idx] + LIGHT_QUADRATIC_ATTENUATION_NAME) + +#define DECLARE_LIGHT_CONSTANT_ATTENUATION_NAME(idx)\ + StringToInt::lookupId(LIGHT_STRUCT_NAMES[idx] + LIGHT_CONSTANT_ATTENUATION_NAME) + +#define DECLARE_LIGHT_CUT_OFF_ANGLE_NAME(idx)\ + StringToInt::lookupId(LIGHT_STRUCT_NAMES[idx] + LIGHT_CUT_OFF_ANGLE_NAME) + +#define DECLARE_LIGHT_STRUCT_UNROLL_NAME(idx)\ + QLatin1String("light_") + QLatin1Char(char('0' + idx)) + +#define DECLARE_LIGHT_POSITION_UNROLL_NAME(idx)\ + StringToInt::lookupId(LIGHT_STRUCT_UNROLL_NAMES[idx] + LIGHT_POSITION_NAME) + +#define DECLARE_LIGHT_TYPE_UNROLL_NAME(idx)\ + StringToInt::lookupId(LIGHT_STRUCT_UNROLL_NAMES[idx] + LIGHT_TYPE_NAME) + +#define DECLARE_LIGHT_COLOR_UNROLL_NAME(idx)\ + StringToInt::lookupId(LIGHT_STRUCT_UNROLL_NAMES[idx] + LIGHT_COLOR_NAME) + +#define DECLARE_LIGHT_INTENSITY_UNROLL_NAME(idx)\ + StringToInt::lookupId(LIGHT_STRUCT_UNROLL_NAMES[idx] + LIGHT_INTENSITY_NAME) + +#define DECLARE_LIGHT_DIRECTION_UNROLL_NAME(idx)\ + StringToInt::lookupId(LIGHT_STRUCT_UNROLL_NAMES[idx] + LIGHT_DIRECTION_NAME) + +#define DECLARE_LIGHT_LINEAR_ATTENUATION_UNROLL_NAME(idx)\ + StringToInt::lookupId(LIGHT_STRUCT_UNROLL_NAMES[idx] + LIGHT_LINEAR_ATTENUATION_NAME) + +#define DECLARE_LIGHT_QUADRATIC_ATTENUATION_UNROLL_NAME(idx)\ + StringToInt::lookupId(LIGHT_STRUCT_UNROLL_NAMES[idx] + LIGHT_QUADRATIC_ATTENUATION_NAME) + +#define DECLARE_LIGHT_CONSTANT_ATTENUATION_UNROLL_NAME(idx)\ + StringToInt::lookupId(LIGHT_STRUCT_UNROLL_NAMES[idx] + LIGHT_CONSTANT_ATTENUATION_NAME) + +#define DECLARE_LIGHT_CUT_OFF_ANGLE_UNROLL_NAME(idx)\ + StringToInt::lookupId(LIGHT_STRUCT_UNROLL_NAMES[idx] + LIGHT_CUT_OFF_ANGLE_NAME) + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { +namespace Render { +namespace OpenGL { + +int GLLights::LIGHT_COUNT_NAME_ID = StringToInt::lookupId(QLatin1String("lightCount")); + +QString GLLights::LIGHT_STRUCT_NAMES[MAX_LIGHTS] = { + DECLARE_LIGHT_STRUCT_NAME(0), + DECLARE_LIGHT_STRUCT_NAME(1), + DECLARE_LIGHT_STRUCT_NAME(2), + DECLARE_LIGHT_STRUCT_NAME(3), + DECLARE_LIGHT_STRUCT_NAME(4), + DECLARE_LIGHT_STRUCT_NAME(5), + DECLARE_LIGHT_STRUCT_NAME(6), + DECLARE_LIGHT_STRUCT_NAME(7) +}; + +int GLLights::LIGHT_POSITION_NAMES[MAX_LIGHTS] = { + DECLARE_LIGHT_POSITION_NAME(0), + DECLARE_LIGHT_POSITION_NAME(1), + DECLARE_LIGHT_POSITION_NAME(2), + DECLARE_LIGHT_POSITION_NAME(3), + DECLARE_LIGHT_POSITION_NAME(4), + DECLARE_LIGHT_POSITION_NAME(5), + DECLARE_LIGHT_POSITION_NAME(6), + DECLARE_LIGHT_POSITION_NAME(7) +}; + +int GLLights::LIGHT_TYPE_NAMES[MAX_LIGHTS] = { + DECLARE_LIGHT_TYPE_NAME(0), + DECLARE_LIGHT_TYPE_NAME(1), + DECLARE_LIGHT_TYPE_NAME(2), + DECLARE_LIGHT_TYPE_NAME(3), + DECLARE_LIGHT_TYPE_NAME(4), + DECLARE_LIGHT_TYPE_NAME(5), + DECLARE_LIGHT_TYPE_NAME(6), + DECLARE_LIGHT_TYPE_NAME(7) +}; + +int GLLights::LIGHT_COLOR_NAMES[MAX_LIGHTS] = { + DECLARE_LIGHT_COLOR_NAME(0), + DECLARE_LIGHT_COLOR_NAME(1), + DECLARE_LIGHT_COLOR_NAME(2), + DECLARE_LIGHT_COLOR_NAME(3), + DECLARE_LIGHT_COLOR_NAME(4), + DECLARE_LIGHT_COLOR_NAME(5), + DECLARE_LIGHT_COLOR_NAME(6), + DECLARE_LIGHT_COLOR_NAME(7) +}; + +int GLLights::LIGHT_INTENSITY_NAMES[MAX_LIGHTS] = { + DECLARE_LIGHT_INTENSITY_NAME(0), + DECLARE_LIGHT_INTENSITY_NAME(1), + DECLARE_LIGHT_INTENSITY_NAME(2), + DECLARE_LIGHT_INTENSITY_NAME(3), + DECLARE_LIGHT_INTENSITY_NAME(4), + DECLARE_LIGHT_INTENSITY_NAME(5), + DECLARE_LIGHT_INTENSITY_NAME(6), + DECLARE_LIGHT_INTENSITY_NAME(7) +}; + +int GLLights::LIGHT_DIRECTION_NAMES[MAX_LIGHTS] = { + DECLARE_LIGHT_DIRECTION_NAME(0), + DECLARE_LIGHT_DIRECTION_NAME(1), + DECLARE_LIGHT_DIRECTION_NAME(2), + DECLARE_LIGHT_DIRECTION_NAME(3), + DECLARE_LIGHT_DIRECTION_NAME(4), + DECLARE_LIGHT_DIRECTION_NAME(5), + DECLARE_LIGHT_DIRECTION_NAME(6), + DECLARE_LIGHT_DIRECTION_NAME(7) +}; + +int GLLights::LIGHT_LINEAR_ATTENUATION_NAMES[MAX_LIGHTS] = { + DECLARE_LIGHT_LINEAR_ATTENUATION_NAME(0), + DECLARE_LIGHT_LINEAR_ATTENUATION_NAME(1), + DECLARE_LIGHT_LINEAR_ATTENUATION_NAME(2), + DECLARE_LIGHT_LINEAR_ATTENUATION_NAME(3), + DECLARE_LIGHT_LINEAR_ATTENUATION_NAME(4), + DECLARE_LIGHT_LINEAR_ATTENUATION_NAME(5), + DECLARE_LIGHT_LINEAR_ATTENUATION_NAME(6), + DECLARE_LIGHT_LINEAR_ATTENUATION_NAME(7) +}; + +int GLLights::LIGHT_QUADRATIC_ATTENUATION_NAMES[MAX_LIGHTS] = { + DECLARE_LIGHT_QUADRATIC_ATTENUATION_NAME(0), + DECLARE_LIGHT_QUADRATIC_ATTENUATION_NAME(1), + DECLARE_LIGHT_QUADRATIC_ATTENUATION_NAME(2), + DECLARE_LIGHT_QUADRATIC_ATTENUATION_NAME(3), + DECLARE_LIGHT_QUADRATIC_ATTENUATION_NAME(4), + DECLARE_LIGHT_QUADRATIC_ATTENUATION_NAME(5), + DECLARE_LIGHT_QUADRATIC_ATTENUATION_NAME(6), + DECLARE_LIGHT_QUADRATIC_ATTENUATION_NAME(7) +}; + +int GLLights::LIGHT_CONSTANT_ATTENUATION_NAMES[MAX_LIGHTS] = { + DECLARE_LIGHT_CONSTANT_ATTENUATION_NAME(0), + DECLARE_LIGHT_CONSTANT_ATTENUATION_NAME(1), + DECLARE_LIGHT_CONSTANT_ATTENUATION_NAME(2), + DECLARE_LIGHT_CONSTANT_ATTENUATION_NAME(3), + DECLARE_LIGHT_CONSTANT_ATTENUATION_NAME(4), + DECLARE_LIGHT_CONSTANT_ATTENUATION_NAME(5), + DECLARE_LIGHT_CONSTANT_ATTENUATION_NAME(6), + DECLARE_LIGHT_CONSTANT_ATTENUATION_NAME(7) +}; + +int GLLights::LIGHT_CUT_OFF_ANGLE_NAMES[MAX_LIGHTS] = { + DECLARE_LIGHT_CUT_OFF_ANGLE_NAME(0), + DECLARE_LIGHT_CUT_OFF_ANGLE_NAME(1), + DECLARE_LIGHT_CUT_OFF_ANGLE_NAME(2), + DECLARE_LIGHT_CUT_OFF_ANGLE_NAME(3), + DECLARE_LIGHT_CUT_OFF_ANGLE_NAME(4), + DECLARE_LIGHT_CUT_OFF_ANGLE_NAME(5), + DECLARE_LIGHT_CUT_OFF_ANGLE_NAME(6), + DECLARE_LIGHT_CUT_OFF_ANGLE_NAME(7) +}; + +QString GLLights::LIGHT_STRUCT_UNROLL_NAMES[MAX_LIGHTS] = { + DECLARE_LIGHT_STRUCT_UNROLL_NAME(0), + DECLARE_LIGHT_STRUCT_UNROLL_NAME(1), + DECLARE_LIGHT_STRUCT_UNROLL_NAME(2), + DECLARE_LIGHT_STRUCT_UNROLL_NAME(3), + DECLARE_LIGHT_STRUCT_UNROLL_NAME(4), + DECLARE_LIGHT_STRUCT_UNROLL_NAME(5), + DECLARE_LIGHT_STRUCT_UNROLL_NAME(6), + DECLARE_LIGHT_STRUCT_UNROLL_NAME(7) +}; + +int GLLights::LIGHT_POSITION_UNROLL_NAMES[MAX_LIGHTS] = { + DECLARE_LIGHT_POSITION_UNROLL_NAME(0), + DECLARE_LIGHT_POSITION_UNROLL_NAME(1), + DECLARE_LIGHT_POSITION_UNROLL_NAME(2), + DECLARE_LIGHT_POSITION_UNROLL_NAME(3), + DECLARE_LIGHT_POSITION_UNROLL_NAME(4), + DECLARE_LIGHT_POSITION_UNROLL_NAME(5), + DECLARE_LIGHT_POSITION_UNROLL_NAME(6), + DECLARE_LIGHT_POSITION_UNROLL_NAME(7) +}; + +int GLLights::LIGHT_TYPE_UNROLL_NAMES[MAX_LIGHTS] = { + DECLARE_LIGHT_TYPE_UNROLL_NAME(0), + DECLARE_LIGHT_TYPE_UNROLL_NAME(1), + DECLARE_LIGHT_TYPE_UNROLL_NAME(2), + DECLARE_LIGHT_TYPE_UNROLL_NAME(3), + DECLARE_LIGHT_TYPE_UNROLL_NAME(4), + DECLARE_LIGHT_TYPE_UNROLL_NAME(5), + DECLARE_LIGHT_TYPE_UNROLL_NAME(6), + DECLARE_LIGHT_TYPE_UNROLL_NAME(7) +}; + +int GLLights::LIGHT_COLOR_UNROLL_NAMES[MAX_LIGHTS] = { + DECLARE_LIGHT_COLOR_UNROLL_NAME(0), + DECLARE_LIGHT_COLOR_UNROLL_NAME(1), + DECLARE_LIGHT_COLOR_UNROLL_NAME(2), + DECLARE_LIGHT_COLOR_UNROLL_NAME(3), + DECLARE_LIGHT_COLOR_UNROLL_NAME(4), + DECLARE_LIGHT_COLOR_UNROLL_NAME(5), + DECLARE_LIGHT_COLOR_UNROLL_NAME(6), + DECLARE_LIGHT_COLOR_UNROLL_NAME(7) +}; + +int GLLights::LIGHT_INTENSITY_UNROLL_NAMES[MAX_LIGHTS] = { + DECLARE_LIGHT_INTENSITY_UNROLL_NAME(0), + DECLARE_LIGHT_INTENSITY_UNROLL_NAME(1), + DECLARE_LIGHT_INTENSITY_UNROLL_NAME(2), + DECLARE_LIGHT_INTENSITY_UNROLL_NAME(3), + DECLARE_LIGHT_INTENSITY_UNROLL_NAME(4), + DECLARE_LIGHT_INTENSITY_UNROLL_NAME(5), + DECLARE_LIGHT_INTENSITY_UNROLL_NAME(6), + DECLARE_LIGHT_INTENSITY_UNROLL_NAME(7) +}; + +int GLLights::LIGHT_DIRECTION_UNROLL_NAMES[MAX_LIGHTS] = { + DECLARE_LIGHT_DIRECTION_UNROLL_NAME(0), + DECLARE_LIGHT_DIRECTION_UNROLL_NAME(1), + DECLARE_LIGHT_DIRECTION_UNROLL_NAME(2), + DECLARE_LIGHT_DIRECTION_UNROLL_NAME(3), + DECLARE_LIGHT_DIRECTION_UNROLL_NAME(4), + DECLARE_LIGHT_DIRECTION_UNROLL_NAME(5), + DECLARE_LIGHT_DIRECTION_UNROLL_NAME(6), + DECLARE_LIGHT_DIRECTION_UNROLL_NAME(7) +}; + +int GLLights::LIGHT_LINEAR_ATTENUATION_UNROLL_NAMES[MAX_LIGHTS] = { + DECLARE_LIGHT_LINEAR_ATTENUATION_UNROLL_NAME(0), + DECLARE_LIGHT_LINEAR_ATTENUATION_UNROLL_NAME(1), + DECLARE_LIGHT_LINEAR_ATTENUATION_UNROLL_NAME(2), + DECLARE_LIGHT_LINEAR_ATTENUATION_UNROLL_NAME(3), + DECLARE_LIGHT_LINEAR_ATTENUATION_UNROLL_NAME(4), + DECLARE_LIGHT_LINEAR_ATTENUATION_UNROLL_NAME(5), + DECLARE_LIGHT_LINEAR_ATTENUATION_UNROLL_NAME(6), + DECLARE_LIGHT_LINEAR_ATTENUATION_UNROLL_NAME(7) +}; + +int GLLights::LIGHT_QUADRATIC_ATTENUATION_UNROLL_NAMES[MAX_LIGHTS] = { + DECLARE_LIGHT_QUADRATIC_ATTENUATION_UNROLL_NAME(0), + DECLARE_LIGHT_QUADRATIC_ATTENUATION_UNROLL_NAME(1), + DECLARE_LIGHT_QUADRATIC_ATTENUATION_UNROLL_NAME(2), + DECLARE_LIGHT_QUADRATIC_ATTENUATION_UNROLL_NAME(3), + DECLARE_LIGHT_QUADRATIC_ATTENUATION_UNROLL_NAME(4), + DECLARE_LIGHT_QUADRATIC_ATTENUATION_UNROLL_NAME(5), + DECLARE_LIGHT_QUADRATIC_ATTENUATION_UNROLL_NAME(6), + DECLARE_LIGHT_QUADRATIC_ATTENUATION_UNROLL_NAME(7) +}; + +int GLLights::LIGHT_CONSTANT_ATTENUATION_UNROLL_NAMES[MAX_LIGHTS] = { + DECLARE_LIGHT_CONSTANT_ATTENUATION_UNROLL_NAME(0), + DECLARE_LIGHT_CONSTANT_ATTENUATION_UNROLL_NAME(1), + DECLARE_LIGHT_CONSTANT_ATTENUATION_UNROLL_NAME(2), + DECLARE_LIGHT_CONSTANT_ATTENUATION_UNROLL_NAME(3), + DECLARE_LIGHT_CONSTANT_ATTENUATION_UNROLL_NAME(4), + DECLARE_LIGHT_CONSTANT_ATTENUATION_UNROLL_NAME(5), + DECLARE_LIGHT_CONSTANT_ATTENUATION_UNROLL_NAME(6), + DECLARE_LIGHT_CONSTANT_ATTENUATION_UNROLL_NAME(7) +}; + +int GLLights::LIGHT_CUT_OFF_ANGLE_UNROLL_NAMES[MAX_LIGHTS] = { + DECLARE_LIGHT_CUT_OFF_ANGLE_UNROLL_NAME(0), + DECLARE_LIGHT_CUT_OFF_ANGLE_UNROLL_NAME(1), + DECLARE_LIGHT_CUT_OFF_ANGLE_UNROLL_NAME(2), + DECLARE_LIGHT_CUT_OFF_ANGLE_UNROLL_NAME(3), + DECLARE_LIGHT_CUT_OFF_ANGLE_UNROLL_NAME(4), + DECLARE_LIGHT_CUT_OFF_ANGLE_UNROLL_NAME(5), + DECLARE_LIGHT_CUT_OFF_ANGLE_UNROLL_NAME(6), + DECLARE_LIGHT_CUT_OFF_ANGLE_UNROLL_NAME(7) +}; + +} // namespace OpenGL +} // namespace Render +} // namespace Qt3DRender + +QT_END_NAMESPACE diff --git a/src/plugins/renderers/opengl/renderer/gllights_p.h b/src/plugins/renderers/opengl/renderer/gllights_p.h new file mode 100644 index 000000000..90fa91588 --- /dev/null +++ b/src/plugins/renderers/opengl/renderer/gllights_p.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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_OPENGL_GLLIGHTS_P_H +#define QT3DRENDER_RENDER_OPENGL_GLLIGHTS_P_H + +#include <QString> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { +namespace Render { +namespace OpenGL { + +#define MAX_LIGHTS 8 + +static_assert (MAX_LIGHTS < 10, "GL_Lights can't use the QChar trick anymore"); + +struct GLLights +{ + static int LIGHT_COUNT_NAME_ID; + + static QString LIGHT_STRUCT_NAMES[MAX_LIGHTS]; + static int LIGHT_POSITION_NAMES[MAX_LIGHTS]; + static int LIGHT_TYPE_NAMES[MAX_LIGHTS]; + static int LIGHT_COLOR_NAMES[MAX_LIGHTS]; + static int LIGHT_INTENSITY_NAMES[MAX_LIGHTS]; + static int LIGHT_DIRECTION_NAMES[MAX_LIGHTS]; + static int LIGHT_LINEAR_ATTENUATION_NAMES[MAX_LIGHTS]; + static int LIGHT_QUADRATIC_ATTENUATION_NAMES[MAX_LIGHTS]; + static int LIGHT_CONSTANT_ATTENUATION_NAMES[MAX_LIGHTS]; + static int LIGHT_CUT_OFF_ANGLE_NAMES[MAX_LIGHTS]; + + static QString LIGHT_STRUCT_UNROLL_NAMES[MAX_LIGHTS]; + static int LIGHT_POSITION_UNROLL_NAMES[MAX_LIGHTS]; + static int LIGHT_TYPE_UNROLL_NAMES[MAX_LIGHTS]; + static int LIGHT_COLOR_UNROLL_NAMES[MAX_LIGHTS]; + static int LIGHT_INTENSITY_UNROLL_NAMES[MAX_LIGHTS]; + static int LIGHT_DIRECTION_UNROLL_NAMES[MAX_LIGHTS]; + static int LIGHT_LINEAR_ATTENUATION_UNROLL_NAMES[MAX_LIGHTS]; + static int LIGHT_QUADRATIC_ATTENUATION_UNROLL_NAMES[MAX_LIGHTS]; + static int LIGHT_CONSTANT_ATTENUATION_UNROLL_NAMES[MAX_LIGHTS]; + static int LIGHT_CUT_OFF_ANGLE_UNROLL_NAMES[MAX_LIGHTS]; +}; + +} // namespace OpenGL +} // namespace Render +} // namespace Qt3DRender + +QT_END_NAMESPACE + +#endif // QT3DRENDER_RENDER_OPENGL_GLLIGHTS_P_H diff --git a/src/plugins/renderers/opengl/renderer/glshader.cpp b/src/plugins/renderers/opengl/renderer/glshader.cpp index 5e92d84c2..564e78a8e 100644 --- a/src/plugins/renderers/opengl/renderer/glshader.cpp +++ b/src/plugins/renderers/opengl/renderer/glshader.cpp @@ -42,6 +42,7 @@ #include <Qt3DRender/private/stringtoint_p.h> #include <graphicscontext_p.h> #include <logging_p.h> +#include <gllights_p.h> QT_BEGIN_NAMESPACE @@ -51,9 +52,51 @@ namespace Render { namespace OpenGL { +namespace { + +QVector<int> getLightUniformNameIds() +{ + QVector<int> names; + names.reserve(MAX_LIGHTS * 18 + 1); + + names << GLLights::LIGHT_COUNT_NAME_ID; + for (int i = 0; i < MAX_LIGHTS; ++i) { + names << GLLights::LIGHT_TYPE_NAMES[i] + << GLLights::LIGHT_COLOR_NAMES[i] + << GLLights::LIGHT_POSITION_NAMES[i] + << GLLights::LIGHT_INTENSITY_NAMES[i] + << GLLights::LIGHT_DIRECTION_NAMES[i] + << GLLights::LIGHT_LINEAR_ATTENUATION_NAMES[i] + << GLLights::LIGHT_QUADRATIC_ATTENUATION_NAMES[i] + << GLLights::LIGHT_CONSTANT_ATTENUATION_NAMES[i] + << GLLights::LIGHT_CUT_OFF_ANGLE_NAMES[i] + << GLLights::LIGHT_TYPE_UNROLL_NAMES[i] + << GLLights::LIGHT_COLOR_UNROLL_NAMES[i] + << GLLights::LIGHT_POSITION_UNROLL_NAMES[i] + << GLLights::LIGHT_INTENSITY_UNROLL_NAMES[i] + << GLLights::LIGHT_DIRECTION_UNROLL_NAMES[i] + << GLLights::LIGHT_LINEAR_ATTENUATION_UNROLL_NAMES[i] + << GLLights::LIGHT_QUADRATIC_ATTENUATION_UNROLL_NAMES[i] + << GLLights::LIGHT_CONSTANT_ATTENUATION_UNROLL_NAMES[i] + << GLLights::LIGHT_CUT_OFF_ANGLE_UNROLL_NAMES[i]; + } + + return names; +} + +template<typename Vector> +bool fastContains(const Vector &v, int value) +{ + return std::binary_search(v.cbegin(), v.cend(), value); +} + +} + GLShader::GLShader() : m_isLoaded(false) , m_graphicsContext(nullptr) + , m_parameterPackSize(0) + , m_hasActiveVariables(false) { m_shaderCode.resize(static_cast<int>(QShaderProgram::Compute) + 1); } @@ -106,7 +149,7 @@ QHash<QString, ShaderUniform> GLShader::activeUniformsForUniformBlock(int blockI return m_uniformBlockIndexToShaderUniforms.value(blockIndex); } -ShaderUniformBlock GLShader::uniformBlockForBlockIndex(int blockIndex) +ShaderUniformBlock GLShader::uniformBlockForBlockIndex(int blockIndex) const noexcept { for (int i = 0, m = m_uniformBlocks.size(); i < m; ++i) { if (m_uniformBlocks[i].m_index == blockIndex) { @@ -116,7 +159,7 @@ ShaderUniformBlock GLShader::uniformBlockForBlockIndex(int blockIndex) return ShaderUniformBlock(); } -ShaderUniformBlock GLShader::uniformBlockForBlockNameId(int blockNameId) +ShaderUniformBlock GLShader::uniformBlockForBlockNameId(int blockNameId) const noexcept { for (int i = 0, m = m_uniformBlocks.size(); i < m; ++i) { if (m_uniformBlocks[i].m_nameId == blockNameId) { @@ -126,7 +169,7 @@ ShaderUniformBlock GLShader::uniformBlockForBlockNameId(int blockNameId) return ShaderUniformBlock(); } -ShaderUniformBlock GLShader::uniformBlockForBlockName(const QString &blockName) +ShaderUniformBlock GLShader::uniformBlockForBlockName(const QString &blockName) const noexcept { for (int i = 0, m = m_uniformBlocks.size(); i < m; ++i) { if (m_uniformBlocks[i].m_name == blockName) { @@ -136,7 +179,7 @@ ShaderUniformBlock GLShader::uniformBlockForBlockName(const QString &blockName) return ShaderUniformBlock(); } -ShaderStorageBlock GLShader::storageBlockForBlockIndex(int blockIndex) +ShaderStorageBlock GLShader::storageBlockForBlockIndex(int blockIndex) const noexcept { for (int i = 0, m = m_shaderStorageBlockNames.size(); i < m; ++i) { if (m_shaderStorageBlocks[i].m_index == blockIndex) @@ -145,7 +188,7 @@ ShaderStorageBlock GLShader::storageBlockForBlockIndex(int blockIndex) return ShaderStorageBlock(); } -ShaderStorageBlock GLShader::storageBlockForBlockNameId(int blockNameId) +ShaderStorageBlock GLShader::storageBlockForBlockNameId(int blockNameId) const noexcept { for (int i = 0, m = m_shaderStorageBlockNames.size(); i < m; ++i) { if (m_shaderStorageBlocks[i].m_nameId == blockNameId) @@ -154,7 +197,7 @@ ShaderStorageBlock GLShader::storageBlockForBlockNameId(int blockNameId) return ShaderStorageBlock(); } -ShaderStorageBlock GLShader::storageBlockForBlockName(const QString &blockName) +ShaderStorageBlock GLShader::storageBlockForBlockName(const QString &blockName) const noexcept { for (int i = 0, m = m_shaderStorageBlockNames.size(); i < m; ++i) { if (m_shaderStorageBlocks[i].m_name == blockName) @@ -163,6 +206,22 @@ ShaderStorageBlock GLShader::storageBlockForBlockName(const QString &blockName) return ShaderStorageBlock(); } +GLShader::ParameterKind GLShader::categorizeVariable(int nameId) const noexcept +{ + if (fastContains(m_uniformsNamesIds, nameId)) + return ParameterKind::Uniform; + if (fastContains(m_uniformBlockNamesIds, nameId)) + return ParameterKind::UBO; + if (fastContains(m_shaderStorageBlockNamesIds, nameId)) + return ParameterKind::SSBO; + return ParameterKind::Struct; +} + +bool GLShader::hasUniform(int nameId) const noexcept +{ + return m_uniformsNamesIds.contains(nameId); +} + void GLShader::prepareUniforms(ShaderParameterPack &pack) { const PackUniformHash &values = pack.uniforms(); @@ -170,14 +229,20 @@ void GLShader::prepareUniforms(ShaderParameterPack &pack) auto it = values.keys.cbegin(); const auto end = values.keys.cend(); + const int shaderUniformsCount = m_uniforms.size(); + const auto uIt = m_uniforms.cbegin(); + while (it != end) { // Find if there's a uniform with the same name id - for (const ShaderUniform &uniform : qAsConst(m_uniforms)) { - if (uniform.m_nameId == *it) { - pack.setSubmissionUniform(uniform); - break; - } - } + + int i = 0; + const int targetNameId = *it; + while (i < shaderUniformsCount && (uIt + i)->m_nameId < targetNameId) + ++i; + + if (i < shaderUniformsCount && (uIt + i)->m_nameId == targetNameId) + pack.setSubmissionUniformIndex(i); + ++it; } } @@ -188,7 +253,6 @@ void GLShader::setFragOutputs(const QHash<QString, int> &fragOutputs) QMutexLocker lock(&m_mutex); m_fragOutputs = fragOutputs; } -// updateDNA(); } const QHash<QString, int> GLShader::fragOutputs() const @@ -203,6 +267,7 @@ void GLShader::initializeUniforms(const QVector<ShaderUniform> &uniformsDescript m_uniformsNames.resize(uniformsDescription.size()); m_uniformsNamesIds.reserve(uniformsDescription.size()); m_standardUniformNamesIds.reserve(5); + m_lightUniformsNamesIds.reserve(MAX_LIGHTS * 8 + 1); QHash<QString, ShaderUniform> activeUniformsInDefaultBlock; static const QVector<int> standardUniformNameIds = { @@ -231,14 +296,18 @@ void GLShader::initializeUniforms(const QVector<ShaderUniform> &uniformsDescript Shader::skinningPaletteNameId, }; + static const QVector<int> lightUniformNameIds = getLightUniformNameIds(); + for (int i = 0, m = uniformsDescription.size(); i < m; i++) { m_uniformsNames[i] = m_uniforms[i].m_name; const int nameId = StringToInt::lookupId(m_uniformsNames[i]); m_uniforms[i].m_nameId = nameId; - // Is the uniform a Qt3D "Standard" uniform or a user defined one? + // Is the uniform a Qt3D "Standard" uniform, a light uniform or a user defined one? if (standardUniformNameIds.contains(nameId)) m_standardUniformNamesIds.push_back(nameId); + else if (lightUniformNameIds.contains(nameId)) + m_lightUniformsNamesIds.push_back(nameId); else m_uniformsNamesIds.push_back(nameId); @@ -248,6 +317,18 @@ void GLShader::initializeUniforms(const QVector<ShaderUniform> &uniformsDescript } } m_uniformBlockIndexToShaderUniforms.insert(-1, activeUniformsInDefaultBlock); + + m_parameterPackSize += m_standardUniformNamesIds.size() + m_lightUniformsNamesIds.size() + m_uniformsNamesIds.size(); + m_hasActiveVariables |= (m_parameterPackSize > 0); + + // Sort by ascending order to make contains check faster + std::sort(m_uniformsNamesIds.begin(), m_uniformsNamesIds.end()); + std::sort(m_lightUniformsNamesIds.begin(), m_lightUniformsNamesIds.end()); + std::sort(m_standardUniformNamesIds.begin(), m_standardUniformNamesIds.end()); + std::sort(m_uniforms.begin(), m_uniforms.end(), + [] (const ShaderUniform &a, const ShaderUniform &b) { + return a.m_nameId < b.m_nameId; + }); } void GLShader::initializeAttributes(const QVector<ShaderAttribute> &attributesDescription) @@ -261,6 +342,7 @@ void GLShader::initializeAttributes(const QVector<ShaderAttribute> &attributesDe m_attributeNamesIds[i] = m_attributes[i].m_nameId; qCDebug(Shaders) << "Active Attribute " << attributesDescription[i].m_name; } + m_hasActiveVariables |= (m_attributeNamesIds.size() > 0); } void GLShader::initializeUniformBlocks(const QVector<ShaderUniformBlock> &uniformBlockDescription) @@ -296,6 +378,12 @@ void GLShader::initializeUniformBlocks(const QVector<ShaderUniformBlock> &unifor } m_uniformBlockIndexToShaderUniforms.insert(uniformBlockDescription[i].m_index, activeUniformsInBlock); } + + m_parameterPackSize += m_uniformsNamesIds.size(); + m_hasActiveVariables |= (m_parameterPackSize > 0); + + // Sort by ascending order to make contains check faster + std::sort(m_uniformBlockNamesIds.begin(), m_uniformBlockNamesIds.end()); } void GLShader::initializeShaderStorageBlocks(const QVector<ShaderStorageBlock> &shaderStorageBlockDescription) @@ -310,6 +398,12 @@ void GLShader::initializeShaderStorageBlocks(const QVector<ShaderStorageBlock> & m_shaderStorageBlocks[i].m_nameId =m_shaderStorageBlockNamesIds[i]; qCDebug(Shaders) << "Initializing Shader Storage Block {" << m_shaderStorageBlockNames[i] << "}"; } + + m_parameterPackSize += m_shaderStorageBlockNamesIds.size(); + m_hasActiveVariables |= (m_parameterPackSize > 0); + + // Sort by ascending order to make contains check faster + std::sort(m_shaderStorageBlockNamesIds.begin(), m_shaderStorageBlockNamesIds.end()); } } // OpenGL diff --git a/src/plugins/renderers/opengl/renderer/glshader_p.h b/src/plugins/renderers/opengl/renderer/glshader_p.h index 6bd5400af..ae447cd18 100644 --- a/src/plugins/renderers/opengl/renderer/glshader_p.h +++ b/src/plugins/renderers/opengl/renderer/glshader_p.h @@ -57,6 +57,9 @@ #include <Qt3DRender/qshaderprogram.h> #include <QMutex> +#ifdef QT_BUILD_INTERNAL + class tst_BenchShaderParameterPack; +#endif QT_BEGIN_NAMESPACE @@ -84,31 +87,44 @@ public: void setFragOutputs(const QHash<QString, int> &fragOutputs); const QHash<QString, int> fragOutputs() const; - inline QVector<int> uniformsNamesIds() const { return m_uniformsNamesIds; } - inline QVector<int> standardUniformNameIds() const { return m_standardUniformNamesIds; } - inline QVector<int> uniformBlockNamesIds() const { return m_uniformBlockNamesIds; } - inline QVector<int> storageBlockNamesIds() const { return m_shaderStorageBlockNamesIds; } - inline QVector<int> attributeNamesIds() const { return m_attributeNamesIds; } + inline const QVector<int> &uniformsNamesIds() const { return m_uniformsNamesIds; } + inline const QVector<int> &lightUniformsNamesIds() const { return m_lightUniformsNamesIds; } + inline const QVector<int> &standardUniformNameIds() const { return m_standardUniformNamesIds; } + inline const QVector<int> &uniformBlockNamesIds() const { return m_uniformBlockNamesIds; } + inline const QVector<int> &storageBlockNamesIds() const { return m_shaderStorageBlockNamesIds; } + inline const QVector<int> &attributeNamesIds() const { return m_attributeNamesIds; } QVector<QString> uniformsNames() const; QVector<QString> attributesNames() const; QVector<QString> uniformBlockNames() const; QVector<QString> storageBlockNames() const; - inline QVector<ShaderUniform> uniforms() const { return m_uniforms; } - inline QVector<ShaderAttribute> attributes() const { return m_attributes; } - inline QVector<ShaderUniformBlock> uniformBlocks() const { return m_uniformBlocks; } - inline QVector<ShaderStorageBlock> storageBlocks() const { return m_shaderStorageBlocks; } + inline const QVector<ShaderUniform> &uniforms() const { return m_uniforms; } + inline const QVector<ShaderAttribute> &attributes() const { return m_attributes; } + inline const QVector<ShaderUniformBlock> &uniformBlocks() const { return m_uniformBlocks; } + inline const QVector<ShaderStorageBlock> &storageBlocks() const { return m_shaderStorageBlocks; } QHash<QString, ShaderUniform> activeUniformsForUniformBlock(int blockIndex) const; - ShaderUniformBlock uniformBlockForBlockIndex(int blockNameId); - ShaderUniformBlock uniformBlockForBlockNameId(int blockIndex); - ShaderUniformBlock uniformBlockForBlockName(const QString &blockName); + ShaderUniformBlock uniformBlockForBlockIndex(int blockNameId) const noexcept; + ShaderUniformBlock uniformBlockForBlockNameId(int blockIndex) const noexcept; + ShaderUniformBlock uniformBlockForBlockName(const QString &blockName) const noexcept; - ShaderStorageBlock storageBlockForBlockIndex(int blockIndex); - ShaderStorageBlock storageBlockForBlockNameId(int blockNameId); - ShaderStorageBlock storageBlockForBlockName(const QString &blockName); + ShaderStorageBlock storageBlockForBlockIndex(int blockIndex) const noexcept; + ShaderStorageBlock storageBlockForBlockNameId(int blockNameId) const noexcept; + ShaderStorageBlock storageBlockForBlockName(const QString &blockName) const noexcept; + + enum ParameterKind { + Uniform, + UBO, + SSBO, + Struct + }; + ParameterKind categorizeVariable(int nameId) const noexcept; + + bool hasUniform(int nameId) const noexcept; + inline bool hasActiveVariables() const noexcept { return m_hasActiveVariables; } + inline int parameterPackSize() const noexcept { return m_parameterPackSize; } QOpenGLShaderProgram *shaderProgram() { return &m_shader; } @@ -122,6 +138,7 @@ private: QVector<QString> m_uniformsNames; QVector<int> m_uniformsNamesIds; + QVector<int> m_lightUniformsNamesIds; QVector<int> m_standardUniformNamesIds; QVector<ShaderUniform> m_uniforms; @@ -141,6 +158,9 @@ private: QHash<QString, int> m_fragOutputs; QVector<QByteArray> m_shaderCode; + int m_parameterPackSize; + int m_hasActiveVariables; + // Private so that only GraphicContext can call it void initializeUniforms(const QVector<ShaderUniform> &uniformsDescription); void initializeAttributes(const QVector<ShaderAttribute> &attributesDescription); @@ -148,6 +168,9 @@ private: void initializeShaderStorageBlocks(const QVector<ShaderStorageBlock> &shaderStorageBlockDescription); friend class GraphicsContext; +#ifdef QT_BUILD_INTERNAL + friend class ::tst_BenchShaderParameterPack; +#endif mutable QMutex m_mutex; QMetaObject::Connection m_contextConnection; diff --git a/src/plugins/renderers/opengl/renderer/renderer.cpp b/src/plugins/renderers/opengl/renderer/renderer.cpp index 3ad9c2818..57ee5ec88 100644 --- a/src/plugins/renderers/opengl/renderer/renderer.cpp +++ b/src/plugins/renderers/opengl/renderer/renderer.cpp @@ -995,16 +995,9 @@ void Renderer::prepareCommandsSubmission(const QVector<RenderView *> &renderView // 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); - } else if (command.m_type == RenderCommand::Compute) { GLShader *shader = command.m_glShader; Q_ASSERT(shader); - - // Prepare the ShaderParameterPack based on the active uniforms of the shader - shader->prepareUniforms(command.m_parameterPack); } } } @@ -1172,6 +1165,10 @@ void Renderer::sendShaderChangesToFrontend(Qt3DCore::QAspectManager *manager) Shader *s = m_nodesManager->shaderManager()->data(handle); if (s->requiresFrontendSync()) { QShaderProgram *frontend = static_cast<decltype(frontend)>(manager->lookupNode(s->peerId())); + // Could happen as a backend shader might live beyong the frontend + // the time needed to destroy the GLShader assoicated with it. + if (!frontend) + continue; QShaderProgramPrivate *dFrontend = static_cast<decltype(dFrontend)>(QNodePrivate::get(frontend)); s->unsetRequiresFrontendSync(); dFrontend->setStatus(s->status()); @@ -1917,10 +1914,31 @@ QVector<Qt3DCore::QAspectJobPtr> Renderer::renderBinJobs() m_updatedDisableSubtreeEnablers.push_back(node->peerId()); } + int idealThreadCount = QThread::idealThreadCount(); + const QByteArray maxThreadCount = qgetenv("QT3D_MAX_THREAD_COUNT"); + if (!maxThreadCount.isEmpty()) { + bool conversionOK = false; + const int maxThreadCountValue = maxThreadCount.toInt(&conversionOK); + if (conversionOK) + idealThreadCount = maxThreadCountValue; + } + const int fgBranchCount = m_frameGraphLeaves.size(); + if (fgBranchCount > 1) { + int workBranches = fgBranchCount; + for (auto leaf: qAsConst(m_frameGraphLeaves)) + if (leaf->nodeType() == FrameGraphNode::NoDraw) + --workBranches; + + if (idealThreadCount > 4 && workBranches && maxThreadCount.isEmpty()) + idealThreadCount = qMax(4, idealThreadCount / workBranches); + } + for (int i = 0; i < fgBranchCount; ++i) { FrameGraphNode *leaf = m_frameGraphLeaves.at(i); RenderViewBuilder builder(leaf, i, this); + builder.setOptimalJobCount(leaf->nodeType() == FrameGraphNode::NoDraw ? 1 : idealThreadCount); + // If we have a new RV (wasn't in the cache before, then it contains no cached data) const bool isNewRV = !m_cache.leafNodeCache.contains(leaf); builder.setLayerCacheNeedsToBeRebuilt(layersCacheNeedsToBeRebuilt || isNewRV); @@ -2046,7 +2064,7 @@ void Renderer::performCompute(const RenderView *, RenderCommand *command) } { Profiling::GLTimeRecorder recorder(Profiling::UniformUpdate, activeProfiler()); - m_submissionContext->setParameters(command->m_parameterPack); + m_submissionContext->setParameters(command->m_parameterPack, command->m_glShader); } { Profiling::GLTimeRecorder recorder(Profiling::DispatchCompute, activeProfiler()); @@ -2140,7 +2158,7 @@ bool Renderer::executeCommandsSubmission(const RenderView *rv) { Profiling::GLTimeRecorder recorder(Profiling::UniformUpdate, activeProfiler()); //// Update program uniforms - if (!m_submissionContext->setParameters(command.m_parameterPack)) { + if (!m_submissionContext->setParameters(command.m_parameterPack, command.m_glShader)) { allCommandsIssued = false; // If we have failed to set uniform (e.g unable to bind a texture) // we won't perform the draw call which could show invalid content diff --git a/src/plugins/renderers/opengl/renderer/renderer.pri b/src/plugins/renderers/opengl/renderer/renderer.pri index 3e3f83c86..1a0240e77 100644 --- a/src/plugins/renderers/opengl/renderer/renderer.pri +++ b/src/plugins/renderers/opengl/renderer/renderer.pri @@ -1,6 +1,7 @@ INCLUDEPATH += $$PWD SOURCES += \ + $$PWD/gllights.cpp \ $$PWD/openglvertexarrayobject.cpp \ $$PWD/rendercommand.cpp \ $$PWD/renderer.cpp \ @@ -13,6 +14,7 @@ SOURCES += \ $$PWD/commandexecuter.cpp HEADERS += \ + $$PWD/gllights_p.h \ $$PWD/openglvertexarrayobject_p.h \ $$PWD/renderercache_p.h \ $$PWD/rendercommand_p.h \ diff --git a/src/plugins/renderers/opengl/renderer/renderer_p.h b/src/plugins/renderers/opengl/renderer/renderer_p.h index 8cf610efb..d4980464a 100644 --- a/src/plugins/renderers/opengl/renderer/renderer_p.h +++ b/src/plugins/renderers/opengl/renderer/renderer_p.h @@ -173,7 +173,7 @@ public: ~Renderer(); void dumpInfo() const override; - API api() const override { return AbstractRenderer::OpenGL; } + API api() const override { return Qt3DRender::API::OpenGL; } qint64 time() const override; void setTime(qint64 time) override; @@ -270,7 +270,7 @@ public: QSharedPointer<RenderBackendResourceAccessor> resourceAccessor() const override; - const GraphicsApiFilterData *contextInfo() const; + const GraphicsApiFilterData *contextInfo() const override; SubmissionContext *submissionContext() const; inline RenderStateSet *defaultRenderState() const { return m_defaultRenderStateSet; } diff --git a/src/plugins/renderers/opengl/renderer/renderview.cpp b/src/plugins/renderers/opengl/renderer/renderview.cpp index 99ac88a94..a3a00782e 100644 --- a/src/plugins/renderers/opengl/renderer/renderview.cpp +++ b/src/plugins/renderers/opengl/renderer/renderview.cpp @@ -75,7 +75,8 @@ #include <Qt3DCore/qentity.h> #include <QtGui/qsurface.h> #include <algorithm> - +#include <atomic> +#include <gllights_p.h> #include <QDebug> #if defined(QT3D_RENDER_VIEW_JOB_TIMINGS) #include <QElapsedTimer> @@ -87,33 +88,12 @@ namespace Qt3DRender { namespace Render { namespace OpenGL { - 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]; - -int LIGHT_POSITION_UNROLL_NAMES[MAX_LIGHTS]; -int LIGHT_TYPE_UNROLL_NAMES[MAX_LIGHTS]; -int LIGHT_COLOR_UNROLL_NAMES[MAX_LIGHTS]; -int LIGHT_INTENSITY_UNROLL_NAMES[MAX_LIGHTS]; -QString LIGHT_STRUCT_UNROLL_NAMES[MAX_LIGHTS]; - -bool wasInitialized = false; +std::atomic_bool wasInitialized{}; } // anonymous namespace @@ -168,9 +148,10 @@ static Matrix4x4 getProjectionMatrix(const CameraLens *lens) } UniformValue RenderView::standardUniformValue(RenderView::StandardUniform standardUniformType, - Entity *entity, - const Matrix4x4 &model) const + const Entity *entity) const { + const Matrix4x4 &model = *(entity->worldTransform()); + switch (standardUniformType) { case ModelMatrix: return UniformValue(model); @@ -263,26 +244,11 @@ RenderView::RenderView() m_workGroups[1] = 1; m_workGroups[2] = 1; - if (Q_UNLIKELY(!wasInitialized)) { + if (Q_UNLIKELY(!wasInitialized.exchange(true))) { // 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); - - LIGHT_STRUCT_UNROLL_NAMES[i] = QLatin1String("light_") + QLatin1Char(char('0' + i)); - LIGHT_POSITION_UNROLL_NAMES[i] = StringToInt::lookupId(LIGHT_STRUCT_UNROLL_NAMES[i] + LIGHT_POSITION_NAME); - LIGHT_TYPE_UNROLL_NAMES[i] = StringToInt::lookupId(LIGHT_STRUCT_UNROLL_NAMES[i] + LIGHT_TYPE_NAME); - LIGHT_COLOR_UNROLL_NAMES[i] = StringToInt::lookupId(LIGHT_STRUCT_UNROLL_NAMES[i] + LIGHT_COLOR_NAME); - LIGHT_INTENSITY_UNROLL_NAMES[i] = StringToInt::lookupId(LIGHT_STRUCT_UNROLL_NAMES[i] + LIGHT_INTENSITY_NAME); - } } } @@ -437,6 +403,7 @@ struct SubRangeSorter<QSortPolicy::Texture> { static void sortSubRange(CommandIt begin, const CommandIt end) { +#ifndef Q_OS_WIN std::stable_sort(begin, end, [] (const RenderCommand &a, const RenderCommand &b) { QVector<ShaderParameterPack::NamedResource> texturesA = a.m_parameterPack.textures(); QVector<ShaderParameterPack::NamedResource> texturesB = b.m_parameterPack.textures(); @@ -455,6 +422,7 @@ struct SubRangeSorter<QSortPolicy::Texture> return identicalTextureCount < originalTextureASize; }); +#endif } }; @@ -958,21 +926,16 @@ void RenderView::setUniformValue(ShaderParameterPack &uniformPack, int nameId, c } void RenderView::setStandardUniformValue(ShaderParameterPack &uniformPack, - int glslNameId, int nameId, - Entity *entity, - const Matrix4x4 &worldTransform) const + const Entity *entity) const { - uniformPack.setUniform(glslNameId, standardUniformValue(ms_standardUniformSetters[nameId], entity, worldTransform)); + uniformPack.setUniform(nameId, standardUniformValue(ms_standardUniformSetters[nameId], entity)); } void RenderView::setUniformBlockValue(ShaderParameterPack &uniformPack, - GLShader *shader, const ShaderUniformBlock &block, const UniformValue &value) const { - Q_UNUSED(shader) - if (value.valueType() == UniformValue::NodeId) { Buffer *buffer = nullptr; @@ -988,11 +951,9 @@ void RenderView::setUniformBlockValue(ShaderParameterPack &uniformPack, } void RenderView::setShaderStorageValue(ShaderParameterPack &uniformPack, - GLShader *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) { @@ -1006,7 +967,10 @@ void RenderView::setShaderStorageValue(ShaderParameterPack &uniformPack, } } -void RenderView::setDefaultUniformBlockShaderDataValue(ShaderParameterPack &uniformPack, GLShader *shader, ShaderData *shaderData, const QString &structName) const +void RenderView::setDefaultUniformBlockShaderDataValue(ShaderParameterPack &uniformPack, + const GLShader *shader, + const ShaderData *shaderData, + const QString &structName) const { UniformBlockValueBuilder *builder = m_localData.localData(); builder->activeUniformNamesToValue.clear(); @@ -1030,6 +994,42 @@ void RenderView::setDefaultUniformBlockShaderDataValue(ShaderParameterPack &unif } } +void RenderView::applyParameter(const Parameter *param, + RenderCommand *command, + const GLShader *shader) const noexcept +{ + const int nameId = param->nameId(); + const UniformValue &uniformValue = param->uniformValue(); + const GLShader::ParameterKind kind = shader->categorizeVariable(nameId); + + switch (kind) { + case GLShader::Uniform: { + setUniformValue(command->m_parameterPack, nameId, uniformValue); + break; + } + case GLShader::UBO: { + setUniformBlockValue(command->m_parameterPack, shader->uniformBlockForBlockNameId(nameId), uniformValue); + break; + } + case GLShader::SSBO: { + setShaderStorageValue(command->m_parameterPack, shader->storageBlockForBlockNameId(nameId), uniformValue); + break; + } + case GLShader::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(nameId)); + } + break; + } + default: + break; + } +} + + void RenderView::setShaderAndUniforms(RenderCommand *command, ParameterInfoList ¶meters, Entity *entity, @@ -1052,11 +1052,6 @@ void RenderView::setShaderAndUniforms(RenderCommand *command, // 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> standardUniformNamesIds = shader->standardUniformNameIds(); - 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 @@ -1073,113 +1068,95 @@ void RenderView::setShaderAndUniforms(RenderCommand *command, shader->setFragOutputs(fragOutputs); } - if (!uniformNamesIds.isEmpty() || !standardUniformNamesIds.isEmpty() || - !attributeNamesIds.isEmpty() || - !shaderStorageBlockNamesIds.isEmpty() || !attributeNamesIds.isEmpty()) { + // Set default attributes + command->m_activeAttributes = shader->attributeNamesIds(); - // Set default standard uniforms without bindings - const Matrix4x4 worldTransform = *(entity->worldTransform()); + // At this point we know whether the command is a valid draw command or not + // We still need to process the uniforms as the command could be a compute command + command->m_isValid = !command->m_activeAttributes.empty(); - for (const int uniformNameId : standardUniformNamesIds) - setStandardUniformValue(command->m_parameterPack, uniformNameId, uniformNameId, entity, worldTransform); - - // Set default attributes - command->m_activeAttributes = attributeNamesIds; + if (shader->hasActiveVariables()) { - // At this point we know whether the command is a valid draw command or not - // We still need to process the uniforms as the command could be a compute command - command->m_isValid = !command->m_activeAttributes.empty(); + // Reserve amount of uniforms we are going to need + command->m_parameterPack.reserve(shader->parameterPackSize()); - // Parameters remaining could be - // -> uniform scalar / vector - // -> uniform struct / arrays - // -> uniform block / array (4.3) - // -> ssbo block / array (4.3) + const QVector<int> &standardUniformNamesIds = shader->standardUniformNameIds(); + for (const int uniformNameId : standardUniformNamesIds) + setStandardUniformValue(command->m_parameterPack, uniformNameId, entity); 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 - } + const Parameter *param = m_manager->data<Parameter, ParameterManager>(it->handle); + applyParameter(param, command, shader); ++it; } // Lights - - int lightIdx = 0; - for (const LightSource &lightSource : activeLightSources) { - if (lightIdx == MAX_LIGHTS) - break; - Entity *lightEntity = lightSource.entity; - const Matrix4x4 lightWorldTransform = *(lightEntity->worldTransform()); - const Vector3D worldPos = lightWorldTransform * Vector3D(0.0f, 0.0f, 0.0f); - for (Light *light : lightSource.lights) { - if (!light->isEnabled()) - continue; - - ShaderData *shaderData = m_manager->shaderDataManager()->lookupResource(light->shaderData()); - if (!shaderData) - continue; - + const QVector<int> &lightUniformNamesIds = shader->lightUniformsNamesIds(); + if (!lightUniformNamesIds.empty()) { + int lightIdx = 0; + for (const LightSource &lightSource : activeLightSources) { if (lightIdx == MAX_LIGHTS) break; + Entity *lightEntity = lightSource.entity; + const Matrix4x4 lightWorldTransform = *(lightEntity->worldTransform()); + const Vector3D worldPos = lightWorldTransform * Vector3D(0.0f, 0.0f, 0.0f); + 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); - - setUniformValue(command->m_parameterPack, LIGHT_POSITION_UNROLL_NAMES[lightIdx], worldPos); - setUniformValue(command->m_parameterPack, LIGHT_TYPE_UNROLL_NAMES[lightIdx], int(QAbstractLight::PointLight)); - setUniformValue(command->m_parameterPack, LIGHT_COLOR_UNROLL_NAMES[lightIdx], Vector3D(1.0f, 1.0f, 1.0f)); - setUniformValue(command->m_parameterPack, LIGHT_INTENSITY_UNROLL_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]); - setDefaultUniformBlockShaderDataValue(command->m_parameterPack, shader, shaderData, LIGHT_STRUCT_UNROLL_NAMES[lightIdx]); - ++lightIdx; + // Note: implicit conversion of values to UniformValue + if (lightUniformNamesIds.contains(GLLights::LIGHT_TYPE_NAMES[lightIdx])) { + setUniformValue(command->m_parameterPack, GLLights::LIGHT_POSITION_NAMES[lightIdx], worldPos); + setUniformValue(command->m_parameterPack, GLLights::LIGHT_TYPE_NAMES[lightIdx], int(QAbstractLight::PointLight)); + setUniformValue(command->m_parameterPack, GLLights::LIGHT_COLOR_NAMES[lightIdx], Vector3D(1.0f, 1.0f, 1.0f)); + setUniformValue(command->m_parameterPack, GLLights::LIGHT_INTENSITY_NAMES[lightIdx], 0.5f); + } else if (lightUniformNamesIds.contains(GLLights::LIGHT_TYPE_UNROLL_NAMES[lightIdx])) { + setUniformValue(command->m_parameterPack, GLLights::LIGHT_POSITION_UNROLL_NAMES[lightIdx], worldPos); + setUniformValue(command->m_parameterPack, GLLights::LIGHT_TYPE_UNROLL_NAMES[lightIdx], int(QAbstractLight::PointLight)); + setUniformValue(command->m_parameterPack, GLLights::LIGHT_COLOR_UNROLL_NAMES[lightIdx], Vector3D(1.0f, 1.0f, 1.0f)); + setUniformValue(command->m_parameterPack, GLLights::LIGHT_INTENSITY_UNROLL_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, GLLights::LIGHT_STRUCT_NAMES[lightIdx]); + setDefaultUniformBlockShaderDataValue(command->m_parameterPack, shader, shaderData, GLLights::LIGHT_STRUCT_UNROLL_NAMES[lightIdx]); + ++lightIdx; + } } - } - if (uniformNamesIds.contains(LIGHT_COUNT_NAME_ID)) - setUniformValue(command->m_parameterPack, LIGHT_COUNT_NAME_ID, UniformValue(qMax((environmentLight ? 0 : 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); - - setUniformValue(command->m_parameterPack, LIGHT_POSITION_UNROLL_NAMES[0], Vector3D(10.0f, 10.0f, 0.0f)); - setUniformValue(command->m_parameterPack, LIGHT_TYPE_UNROLL_NAMES[0], int(QAbstractLight::PointLight)); - setUniformValue(command->m_parameterPack, LIGHT_COLOR_UNROLL_NAMES[0], Vector3D(1.0f, 1.0f, 1.0f)); - setUniformValue(command->m_parameterPack, LIGHT_INTENSITY_UNROLL_NAMES[0], 0.5f); + setUniformValue(command->m_parameterPack, GLLights::LIGHT_COUNT_NAME_ID, UniformValue(qMax((environmentLight ? 0 : 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 + if (lightUniformNamesIds.contains(GLLights::LIGHT_TYPE_NAMES[0])) { + setUniformValue(command->m_parameterPack, GLLights::LIGHT_POSITION_NAMES[0], Vector3D(10.0f, 10.0f, 0.0f)); + setUniformValue(command->m_parameterPack, GLLights::LIGHT_TYPE_NAMES[0], int(QAbstractLight::PointLight)); + setUniformValue(command->m_parameterPack, GLLights::LIGHT_COLOR_NAMES[0], Vector3D(1.0f, 1.0f, 1.0f)); + setUniformValue(command->m_parameterPack, GLLights::LIGHT_INTENSITY_NAMES[0], 0.5f); + } else if (lightUniformNamesIds.contains(GLLights::LIGHT_TYPE_UNROLL_NAMES[lightIdx])) { + setUniformValue(command->m_parameterPack, GLLights::LIGHT_POSITION_UNROLL_NAMES[0], Vector3D(10.0f, 10.0f, 0.0f)); + setUniformValue(command->m_parameterPack, GLLights::LIGHT_TYPE_UNROLL_NAMES[0], int(QAbstractLight::PointLight)); + setUniformValue(command->m_parameterPack, GLLights::LIGHT_COLOR_UNROLL_NAMES[0], Vector3D(1.0f, 1.0f, 1.0f)); + setUniformValue(command->m_parameterPack, GLLights::LIGHT_INTENSITY_UNROLL_NAMES[0], 0.5f); + } + } } // Environment Light @@ -1200,6 +1177,9 @@ void RenderView::setShaderAndUniforms(RenderCommand *command, } setUniformValue(command->m_parameterPack, StringToInt::lookupId(QStringLiteral("envLightCount")), envLightCount); } + + // Prepare the ShaderParameterPack based on the active uniforms of the shader + shader->prepareUniforms(command->m_parameterPack); } } @@ -1233,6 +1213,9 @@ bool RenderView::shouldSkipSubmission() const if (m_clearBuffer != QClearBuffers::None) return false; + if (!m_renderCaptureNodeId.isNull()) + return false; + return true; } diff --git a/src/plugins/renderers/opengl/renderer/renderview_p.h b/src/plugins/renderers/opengl/renderer/renderview_p.h index adab30f4e..6c41ce500 100644 --- a/src/plugins/renderers/opengl/renderer/renderview_p.h +++ b/src/plugins/renderers/opengl/renderer/renderview_p.h @@ -370,27 +370,25 @@ private: static StandardUniformsNameToTypeHash initializeStandardUniformSetters(); UniformValue standardUniformValue(StandardUniform standardUniformType, - Entity *entity, - const Matrix4x4 &model) const; + const Entity *entity) 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; + const Entity *entity) const; void setUniformBlockValue(ShaderParameterPack &uniformPack, - GLShader *shader, const ShaderUniformBlock &block, const UniformValue &value) const; void setShaderStorageValue(ShaderParameterPack &uniformPack, - GLShader *shader, const ShaderStorageBlock &block, const UniformValue &value) const; void setDefaultUniformBlockShaderDataValue(ShaderParameterPack &uniformPack, - GLShader *shader, - ShaderData *shaderData, + const GLShader *shader, + const ShaderData *shaderData, const QString &structName) const; + void applyParameter(const Parameter *param, + RenderCommand *command, + const GLShader *shader) const noexcept; }; } // namespace OpenGL diff --git a/src/plugins/renderers/opengl/renderer/renderviewbuilder.cpp b/src/plugins/renderers/opengl/renderer/renderviewbuilder.cpp index 8ed32ff10..b0ac76199 100644 --- a/src/plugins/renderers/opengl/renderer/renderviewbuilder.cpp +++ b/src/plugins/renderers/opengl/renderer/renderviewbuilder.cpp @@ -49,18 +49,13 @@ namespace Qt3DRender { namespace Render { namespace OpenGL { -// In some cases having less jobs is better (especially on fast cpus where -// splitting just adds more overhead). Ideally, we should try to set the value -// depending on the platform/CPU/nbr of cores -const int RenderViewBuilder::m_optimalParallelJobCount = QThread::idealThreadCount(); - namespace { -int findIdealNumberOfWorkers(int elementCount, int packetSize = 100) +int findIdealNumberOfWorkers(int elementCount, int packetSize = 100, int maxJobCount = 1) { if (elementCount == 0 || packetSize == 0) return 0; - return std::min(std::max(elementCount / packetSize, 1), RenderViewBuilder::optimalJobCount()); + return std::min(std::max(elementCount / packetSize, 1), maxJobCount); } @@ -93,9 +88,10 @@ public: lock.unlock(); // Split among the ideal number of command builders - const int idealPacketSize = std::min(std::max(100, entities.size() / RenderViewBuilder::optimalJobCount()), entities.size()); + const int jobCount = m_renderViewCommandBuilderJobs.size(); + const int idealPacketSize = std::min(std::max(10, entities.size() / jobCount), entities.size()); // Try to split work into an ideal number of workers - const int m = findIdealNumberOfWorkers(entities.size(), idealPacketSize); + const int m = findIdealNumberOfWorkers(entities.size(), idealPacketSize, jobCount); for (int i = 0; i < m; ++i) { const RenderViewCommandBuilderJobPtr renderViewCommandBuilder = m_renderViewCommandBuilderJobs.at(i); @@ -352,9 +348,9 @@ public: } // Split among the number of command builders - // The idealPacketSize is at least 100 entities per worker - const int idealPacketSize = std::min(std::max(100, filteredCommandData->size() / RenderViewBuilder::optimalJobCount()), filteredCommandData->size()); - const int m = findIdealNumberOfWorkers(filteredCommandData->size(), idealPacketSize); + const int jobCount = m_renderViewCommandUpdaterJobs.size(); + const int idealPacketSize = std::min(std::max(10, filteredCommandData->size() / jobCount), filteredCommandData->size()); + const int m = findIdealNumberOfWorkers(filteredCommandData->size(), idealPacketSize, jobCount); for (int i = 0; i < m; ++i) { const RenderViewCommandUpdaterJobPtr renderViewCommandBuilder = m_renderViewCommandUpdaterJobs.at(i); @@ -474,6 +470,10 @@ RenderViewBuilder::RenderViewBuilder(Render::FrameGraphNode *leafNode, int rende , m_syncFilterEntityByLayerJob() , m_filterProximityJob(Render::FilterProximityDistanceJobPtr::create()) { + // In some cases having less jobs is better (especially on fast cpus where + // splitting just adds more overhead). Ideally, we should try to set the value + // depending on the platform/CPU/nbr of cores + m_optimalParallelJobCount = QThread::idealThreadCount(); } RenderViewInitializerJobPtr RenderViewBuilder::renderViewJob() const @@ -558,17 +558,16 @@ void RenderViewBuilder::prepareJobs() m_frustumCullingJob->setRoot(m_renderer->sceneRoot()); if (m_renderCommandCacheNeedsToBeRebuilt) { - - m_renderViewCommandBuilderJobs.reserve(RenderViewBuilder::m_optimalParallelJobCount); - for (auto i = 0; i < RenderViewBuilder::m_optimalParallelJobCount; ++i) { + m_renderViewCommandBuilderJobs.reserve(m_optimalParallelJobCount); + for (auto i = 0; i < m_optimalParallelJobCount; ++i) { auto renderViewCommandBuilder = Render::OpenGL::RenderViewCommandBuilderJobPtr::create(); m_renderViewCommandBuilderJobs.push_back(renderViewCommandBuilder); } m_syncRenderViewPreCommandBuildingJob = CreateSynchronizerJobPtr(SyncPreCommandBuilding(m_renderViewJob, - m_renderViewCommandBuilderJobs, - m_renderer, - m_leafNode), - JobTypes::SyncRenderViewPreCommandBuilding); + m_renderViewCommandBuilderJobs, + m_renderer, + m_leafNode), + JobTypes::SyncRenderViewPreCommandBuilding); } m_renderViewJob->setRenderer(m_renderer); @@ -577,8 +576,8 @@ void RenderViewBuilder::prepareJobs() // RenderCommand building is the most consuming task -> split it // Estimate the number of jobs to create based on the number of entities - m_renderViewCommandUpdaterJobs.reserve(RenderViewBuilder::m_optimalParallelJobCount); - for (auto i = 0; i < RenderViewBuilder::m_optimalParallelJobCount; ++i) { + m_renderViewCommandUpdaterJobs.reserve(m_optimalParallelJobCount); + for (auto i = 0; i < m_optimalParallelJobCount; ++i) { auto renderViewCommandUpdater = Render::OpenGL::RenderViewCommandUpdaterJobPtr::create(); renderViewCommandUpdater->setRenderer(m_renderer); m_renderViewCommandUpdaterJobs.push_back(renderViewCommandUpdater); @@ -587,22 +586,23 @@ void RenderViewBuilder::prepareJobs() 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 = MaterialParameterGathererJobPtr::create(); - materialGatherer->setNodeManagers(m_renderer->nodeManagers()); - 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); + if (materialHandles.count()) { + const int elementsPerJob = qMax(materialHandles.size() / m_optimalParallelJobCount, 1); + m_materialGathererJobs.reserve(m_optimalParallelJobCount); + int elementCount = 0; + while (elementCount < materialHandles.size()) { + auto materialGatherer = MaterialParameterGathererJobPtr::create(); + materialGatherer->setNodeManagers(m_renderer->nodeManagers()); + materialGatherer->setHandles(materialHandles.mid(elementCount, elementsPerJob)); + m_materialGathererJobs.push_back(materialGatherer); + + elementCount += elementsPerJob; + } } m_syncMaterialGathererJob = CreateSynchronizerJobPtr(SyncMaterialParameterGatherer(m_materialGathererJobs, - m_renderer, - m_leafNode), - JobTypes::SyncMaterialGatherer); + m_renderer, + m_leafNode), + JobTypes::SyncMaterialGatherer); } if (m_layerCacheNeedsToBeRebuilt) { @@ -615,29 +615,29 @@ void RenderViewBuilder::prepareJobs() } m_syncRenderViewPreCommandUpdateJob = CreateSynchronizerJobPtr(SyncRenderViewPreCommandUpdate(m_renderViewJob, - m_frustumCullingJob, - m_filterProximityJob, - m_materialGathererJobs, - m_renderViewCommandUpdaterJobs, - m_renderViewCommandBuilderJobs, - m_renderer, - m_leafNode, - m_renderCommandCacheNeedsToBeRebuilt), - JobTypes::SyncRenderViewPreCommandUpdate); + m_frustumCullingJob, + m_filterProximityJob, + m_materialGathererJobs, + m_renderViewCommandUpdaterJobs, + m_renderViewCommandBuilderJobs, + m_renderer, + m_leafNode, + m_renderCommandCacheNeedsToBeRebuilt), + JobTypes::SyncRenderViewPreCommandUpdate); m_syncRenderViewPostCommandUpdateJob = CreateSynchronizerJobPtr(SyncRenderViewPostCommandUpdate(m_renderViewJob, - m_renderViewCommandUpdaterJobs, - m_renderer), - JobTypes::SyncRenderViewPostCommandUpdate); + m_renderViewCommandUpdaterJobs, + m_renderer), + JobTypes::SyncRenderViewPostCommandUpdate); m_syncRenderViewPostInitializationJob = CreateSynchronizerJobPtr(SyncRenderViewPostInitialization(m_renderViewJob, - m_frustumCullingJob, - m_filterEntityByLayerJob, - m_filterProximityJob, - m_materialGathererJobs, - m_renderViewCommandUpdaterJobs, - m_renderViewCommandBuilderJobs), - JobTypes::SyncRenderViewInitialization); + m_frustumCullingJob, + m_filterEntityByLayerJob, + m_filterProximityJob, + m_materialGathererJobs, + m_renderViewCommandUpdaterJobs, + m_renderViewCommandBuilderJobs), + JobTypes::SyncRenderViewInitialization); } QVector<Qt3DCore::QAspectJobPtr> RenderViewBuilder::buildJobHierachy() const @@ -793,9 +793,34 @@ bool RenderViewBuilder::renderCommandCacheNeedsToBeRebuilt() const return m_renderCommandCacheNeedsToBeRebuilt; } -int RenderViewBuilder::optimalJobCount() +int RenderViewBuilder::defaultJobCount() +{ + static int jobCount = 0; + if (jobCount) + return jobCount; + + const QByteArray maxThreadCount = qgetenv("QT3D_MAX_THREAD_COUNT"); + if (!maxThreadCount.isEmpty()) { + bool conversionOK = false; + const int maxThreadCountValue = maxThreadCount.toInt(&conversionOK); + if (conversionOK) { + jobCount = maxThreadCountValue; + return jobCount; + } + } + + jobCount = QThread::idealThreadCount(); + return jobCount; +} + +int RenderViewBuilder::optimalJobCount() const +{ + return m_optimalParallelJobCount; +} + +void RenderViewBuilder::setOptimalJobCount(int v) { - return RenderViewBuilder::m_optimalParallelJobCount; + m_optimalParallelJobCount = v; } QVector<Entity *> RenderViewBuilder::entitiesInSubset(const QVector<Entity *> &entities, const QVector<Entity *> &subset) diff --git a/src/plugins/renderers/opengl/renderer/renderviewbuilder_p.h b/src/plugins/renderers/opengl/renderer/renderviewbuilder_p.h index 54fc98352..17e5fa744 100644 --- a/src/plugins/renderers/opengl/renderer/renderviewbuilder_p.h +++ b/src/plugins/renderers/opengl/renderer/renderviewbuilder_p.h @@ -61,7 +61,6 @@ #include <renderviewcommandbuilderjob_p.h> #include <renderviewcommandupdaterjob_p.h> #include <materialparametergathererjob_p.h> -#include <renderviewbuilderjob_p.h> #include <renderview_p.h> QT_BEGIN_NAMESPACE @@ -112,7 +111,10 @@ public: void setRenderCommandCacheNeedsToBeRebuilt(bool needsToBeRebuilt); bool renderCommandCacheNeedsToBeRebuilt() const; - static int optimalJobCount(); + static int defaultJobCount(); + int optimalJobCount() const; + void setOptimalJobCount(int v); + static QVector<Entity *> entitiesInSubset(const QVector<Entity *> &entities, const QVector<Entity *> &subset); private: @@ -140,7 +142,7 @@ private: SynchronizerJobPtr m_syncMaterialGathererJob; FilterProximityDistanceJobPtr m_filterProximityJob; - static const int m_optimalParallelJobCount; + int m_optimalParallelJobCount; }; } // OpenGL diff --git a/src/plugins/renderers/opengl/renderer/shaderparameterpack.cpp b/src/plugins/renderers/opengl/renderer/shaderparameterpack.cpp index c51595bb7..bc9e9434b 100644 --- a/src/plugins/renderers/opengl/renderer/shaderparameterpack.cpp +++ b/src/plugins/renderers/opengl/renderer/shaderparameterpack.cpp @@ -58,6 +58,12 @@ ShaderParameterPack::~ShaderParameterPack() { } +void ShaderParameterPack::reserve(int uniformCount) +{ + m_uniforms.reserve(uniformCount); + m_submissionUniformIndices.reserve(uniformCount); +} + void ShaderParameterPack::setUniform(const int glslNameId, const UniformValue &val) { m_uniforms.insert(glslNameId, val); @@ -100,9 +106,9 @@ void ShaderParameterPack::setShaderStorageBuffer(BlockToSSBO blockToSSBO) m_shaderStorageBuffers.push_back(std::move(blockToSSBO)); } -void ShaderParameterPack::setSubmissionUniform(const ShaderUniform &uniform) +void ShaderParameterPack::setSubmissionUniformIndex(const int uniformIdx) { - m_submissionUniforms.push_back(uniform); + m_submissionUniformIndices.push_back(uniformIdx); } } // namespace OpenGL diff --git a/src/plugins/renderers/opengl/renderer/shaderparameterpack_p.h b/src/plugins/renderers/opengl/renderer/shaderparameterpack_p.h index 31ef4f7ea..bb6bb0dc6 100644 --- a/src/plugins/renderers/opengl/renderer/shaderparameterpack_p.h +++ b/src/plugins/renderers/opengl/renderer/shaderparameterpack_p.h @@ -93,8 +93,12 @@ struct PackUniformHash PackUniformHash() { - keys.reserve(10); - values.reserve(10); + } + + void reserve(int count) + { + keys.reserve(count); + values.reserve(count); } void insert(int key, const UniformValue &value) @@ -142,13 +146,14 @@ class Q_AUTOTEST_EXPORT ShaderParameterPack public: ~ShaderParameterPack(); + void reserve(int uniformCount); void setUniform(const int glslNameId, const UniformValue &val); void setTexture(const int glslNameId, int uniformArrayIndex, Qt3DCore::QNodeId id); void setImage(const int glslNameId, int uniformArrayIndex, Qt3DCore::QNodeId id); void setUniformBuffer(BlockToUBO blockToUBO); void setShaderStorageBuffer(BlockToSSBO blockToSSBO); - void setSubmissionUniform(const ShaderUniform &uniform); + void setSubmissionUniformIndex(const int shaderUniformIndex); inline PackUniformHash &uniforms() { return m_uniforms; } inline const PackUniformHash &uniforms() const { return m_uniforms; } @@ -194,7 +199,7 @@ public: inline QVector<NamedResource> images() const { return m_images; } inline QVector<BlockToUBO> uniformBuffers() const { return m_uniformBuffers; } inline QVector<BlockToSSBO> shaderStorageBuffers() const { return m_shaderStorageBuffers; } - inline QVector<ShaderUniform> submissionUniforms() const { return m_submissionUniforms; } + inline QVector<int> submissionUniformIndices() const { return m_submissionUniformIndices; } private: PackUniformHash m_uniforms; @@ -202,7 +207,7 @@ private: QVector<NamedResource> m_images; QVector<BlockToUBO> m_uniformBuffers; QVector<BlockToSSBO> m_shaderStorageBuffers; - QVector<ShaderUniform> m_submissionUniforms; + QVector<int> m_submissionUniformIndices; friend class RenderView; }; diff --git a/src/plugins/renderers/renderers.pro b/src/plugins/renderers/renderers.pro index dc58bf7fc..f5399ce84 100644 --- a/src/plugins/renderers/renderers.pro +++ b/src/plugins/renderers/renderers.pro @@ -7,3 +7,7 @@ QT_FOR_CONFIG += 3drender-private #SUBDIRS += dummy qtConfig(qt3d-opengl-renderer): SUBDIRS += opengl + +qtConfig(qt3d-rhi-renderer): { + qtHaveModule(shadertools): SUBDIRS += rhi +} diff --git a/src/plugins/renderers/rhi/graphicshelpers/graphicshelpers.pri b/src/plugins/renderers/rhi/graphicshelpers/graphicshelpers.pri new file mode 100644 index 000000000..e156d3ce5 --- /dev/null +++ b/src/plugins/renderers/rhi/graphicshelpers/graphicshelpers.pri @@ -0,0 +1,9 @@ +#DEFINES += QT3D_RENDER_ASPECT_OPENGL_DEBUG + +INCLUDEPATH += $$PWD + +HEADERS += \ + $$PWD/submissioncontext_p.h + +SOURCES += \ + $$PWD/submissioncontext.cpp diff --git a/src/plugins/renderers/rhi/graphicshelpers/submissioncontext.cpp b/src/plugins/renderers/rhi/graphicshelpers/submissioncontext.cpp new file mode 100644 index 000000000..5b217929c --- /dev/null +++ b/src/plugins/renderers/rhi/graphicshelpers/submissioncontext.cpp @@ -0,0 +1,1871 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 "submissioncontext_p.h" + +#include <Qt3DRender/qgraphicsapifilter.h> +#include <Qt3DRender/qparameter.h> +#include <Qt3DRender/qcullface.h> +#include <Qt3DRender/qfrontface.h> +#include <Qt3DRender/qdepthtest.h> +#include <Qt3DRender/qblendequation.h> +#include <Qt3DRender/qblendequationarguments.h> +#include <Qt3DRender/qstenciloperationarguments.h> +#include <Qt3DRender/qstenciltestarguments.h> +#include <Qt3DRender/private/renderlogging_p.h> +#include <Qt3DRender/private/shader_p.h> +#include <Qt3DRender/private/material_p.h> +#include <Qt3DRender/private/buffer_p.h> +#include <Qt3DRender/private/attribute_p.h> +#include <Qt3DRender/private/renderstates_p.h> +#include <Qt3DRender/private/renderstateset_p.h> +#include <Qt3DRender/private/rendertarget_p.h> +#include <Qt3DRender/private/nodemanagers_p.h> +#include <Qt3DRender/private/buffermanager_p.h> +#include <Qt3DRender/private/managers_p.h> +#include <Qt3DRender/private/attachmentpack_p.h> +#include <Qt3DRender/private/qbuffer_p.h> +#include <Qt3DRender/private/stringtoint_p.h> +#include <Qt3DRender/private/vulkaninstance_p.h> +#include <QGuiApplication> +#include <texture_p.h> +#include <rendercommand_p.h> +#include <renderer_p.h> +#include <rhiresourcemanagers_p.h> +#include <renderbuffer_p.h> +#include <rhishader_p.h> +#include <QOpenGLShaderProgram> + +#include <private/qdebug_p.h> +#include <QSurface> +#include <QWindow> +#include <QShaderBaker> + +#ifdef Q_OS_WIN +#include <QtGui/private/qrhid3d11_p.h> +#endif + +#if defined(Q_OS_MACOS) || defined(Q_OS_IOS) +#include <QtGui/private/qrhimetal_p.h> +#endif + +#ifndef QT_NO_OPENGL +#include <QtGui/private/qrhigles2_p.h> +#endif + +#if QT_CONFIG(vulkan) +#include <QtGui/private/qrhivulkan_p.h> +#endif +#include <bitset> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { +namespace Render { +namespace Rhi { + +static QHash<unsigned int, SubmissionContext *> static_contexts; + +unsigned int nextFreeContextId() noexcept +{ + for (unsigned int i = 0; i < 0xffff; ++i) { + if (!static_contexts.contains(i)) + return i; + } + + qFatal("Couldn't find free context ID"); + return 0; +} + +namespace { + +RHIBuffer::Type attributeTypeToGLBufferType(QAttribute::AttributeType type) noexcept +{ + switch (type) { + case QAttribute::VertexAttribute: + return RHIBuffer::ArrayBuffer; + case QAttribute::IndexAttribute: + return RHIBuffer::IndexBuffer; + case QAttribute::DrawIndirectAttribute: + return RHIBuffer::DrawIndirectBuffer; + default: + Q_UNREACHABLE(); + } +} + +void copyGLFramebufferDataToImage(QImage &img, const uchar *srcData, uint stride, uint width, + uint height, QAbstractTexture::TextureFormat format) noexcept +{ + switch (format) { + case QAbstractTexture::RGBA32F: { + uchar *srcScanline = const_cast<uchar *>(srcData) + stride * (height - 1); + for (uint i = 0; i < height; ++i) { + uchar *dstScanline = img.scanLine(i); + float *pSrc = reinterpret_cast<float *>(srcScanline); + for (uint j = 0; j < width; j++) { + *dstScanline++ = (uchar)(255.0f * qBound(0.0f, pSrc[4 * j + 2], 1.0f)); + *dstScanline++ = (uchar)(255.0f * qBound(0.0f, pSrc[4 * j + 1], 1.0f)); + *dstScanline++ = (uchar)(255.0f * qBound(0.0f, pSrc[4 * j + 0], 1.0f)); + *dstScanline++ = (uchar)(255.0f * qBound(0.0f, pSrc[4 * j + 3], 1.0f)); + } + srcScanline -= stride; + } + } break; + default: { + uchar *srcScanline = (uchar *)srcData + stride * (height - 1); + for (uint i = 0; i < height; ++i) { + memcpy(img.scanLine(i), srcScanline, stride); + srcScanline -= stride; + } + } break; + } +} + +// Render States Helpers + +template<typename GenericState> +void applyStateHelper(const GenericState *state, QRhiGraphicsPipeline *gp) noexcept +{ + Q_UNUSED(state); + Q_UNUSED(gp); + qWarning() << "RHI Unhandled render state" << typeid(GenericState).name(); +} + +void applyStateHelper(const BlendEquationArguments *state, QRhiGraphicsPipeline *gp) noexcept +{ + const auto values = state->values(); + + // We assume a single color attachment + QRhiGraphicsPipeline::TargetBlend targetBlend {}; + + const bool hasTargetBlend = gp->cbeginTargetBlends() != gp->cendTargetBlends(); + if (hasTargetBlend) + targetBlend = *(gp->cbeginTargetBlends()); + + auto getRHIBlendFactor = [](int arg) { + switch (arg) { + case QBlendEquationArguments::Zero: + return QRhiGraphicsPipeline::Zero; + case QBlendEquationArguments::One: + return QRhiGraphicsPipeline::One; + case QBlendEquationArguments::SourceColor: + return QRhiGraphicsPipeline::SrcColor; + case QBlendEquationArguments::SourceAlpha: + return QRhiGraphicsPipeline::SrcAlpha; + // ### Qt 6 Fix values + // case QBlendEquationArguments::Source1Alpha: + // return QRhiGraphicsPipeline::Src1Alpha; + // case QBlendEquationArguments::Source1Color: + // return QRhiGraphicsPipeline::Src1Color; + case QBlendEquationArguments::DestinationColor: + return QRhiGraphicsPipeline::DstColor; + case QBlendEquationArguments::DestinationAlpha: + return QRhiGraphicsPipeline::DstAlpha; + case QBlendEquationArguments::SourceAlphaSaturate: + return QRhiGraphicsPipeline::SrcAlphaSaturate; + case QBlendEquationArguments::ConstantColor: + return QRhiGraphicsPipeline::ConstantColor; + case QBlendEquationArguments::ConstantAlpha: + return QRhiGraphicsPipeline::ConstantAlpha; + case QBlendEquationArguments::OneMinusSourceColor: + return QRhiGraphicsPipeline::OneMinusSrcColor; + case QBlendEquationArguments::OneMinusSourceAlpha: + return QRhiGraphicsPipeline::OneMinusSrcAlpha; + case QBlendEquationArguments::OneMinusDestinationAlpha: + return QRhiGraphicsPipeline::OneMinusDstAlpha; + case QBlendEquationArguments::OneMinusDestinationColor: + return QRhiGraphicsPipeline::OneMinusDstColor; + case QBlendEquationArguments::OneMinusConstantColor: + return QRhiGraphicsPipeline::OneMinusConstantColor; + case QBlendEquationArguments::OneMinusConstantAlpha: + return QRhiGraphicsPipeline::OneMinusConstantAlpha; + case QBlendEquationArguments::OneMinusSource1Alpha: + return QRhiGraphicsPipeline::OneMinusSrc1Alpha; + case QBlendEquationArguments::OneMinusSource1Color: + return QRhiGraphicsPipeline::OneMinusSrc1Color; + default: + qDebug() << "Unhandled blend equation argument" << arg; + return QRhiGraphicsPipeline::Zero; + } + }; + + targetBlend.srcAlpha = getRHIBlendFactor(std::get<2>(values)); + targetBlend.dstAlpha = getRHIBlendFactor(std::get<3>(values)); + targetBlend.srcColor = getRHIBlendFactor(std::get<0>(values)); + targetBlend.dstColor = getRHIBlendFactor(std::get<1>(values)); + gp->setTargetBlends({ targetBlend }); +} + +void applyStateHelper(const BlendEquation *state, QRhiGraphicsPipeline *gp) noexcept +{ + const auto values = state->values(); + const QBlendEquation::BlendFunction equation = + static_cast<QBlendEquation::BlendFunction>(std::get<0>(values)); + + // We assume a single color attachment + QRhiGraphicsPipeline::TargetBlend targetBlend; + + const bool hasTargetBlend = gp->cbeginTargetBlends() != gp->cendTargetBlends(); + if (hasTargetBlend) + targetBlend = *(gp->cbeginTargetBlends()); + + auto getRHIBlendOp = [](QBlendEquation::BlendFunction equation) { + switch (equation) { + case QBlendEquation::Add: + return QRhiGraphicsPipeline::Add; + case QBlendEquation::Subtract: + return QRhiGraphicsPipeline::Subtract; + case QBlendEquation::ReverseSubtract: + return QRhiGraphicsPipeline::ReverseSubtract; + case QBlendEquation::Min: + return QRhiGraphicsPipeline::Min; + case QBlendEquation::Max: + return QRhiGraphicsPipeline::Max; + } + }; + + targetBlend.enable = true; + targetBlend.opAlpha = getRHIBlendOp(equation); + gp->setTargetBlends({ targetBlend }); +} + +void applyStateHelper(const MSAAEnabled *state, QRhiGraphicsPipeline *gp, + const QSurfaceFormat &format) noexcept +{ + gp->setSampleCount(format.samples()); +} + +void applyStateHelper(const DepthTest *state, QRhiGraphicsPipeline *gp) noexcept +{ + gp->setDepthTest(true); + const QDepthTest::DepthFunction depthFunc = + static_cast<QDepthTest::DepthFunction>(std::get<0>(state->values())); + switch (depthFunc) { + case QDepthTest::Never: + gp->setDepthOp(QRhiGraphicsPipeline::Never); + break; + case QDepthTest::Always: + gp->setDepthOp(QRhiGraphicsPipeline::Always); + break; + case QDepthTest::Less: + gp->setDepthOp(QRhiGraphicsPipeline::Less); + break; + case QDepthTest::LessOrEqual: + gp->setDepthOp(QRhiGraphicsPipeline::LessOrEqual); + break; + case QDepthTest::Equal: + gp->setDepthOp(QRhiGraphicsPipeline::Equal); + break; + case QDepthTest::GreaterOrEqual: + gp->setDepthOp(QRhiGraphicsPipeline::GreaterOrEqual); + break; + case QDepthTest::Greater: + gp->setDepthOp(QRhiGraphicsPipeline::Greater); + break; + case QDepthTest::NotEqual: + gp->setDepthOp(QRhiGraphicsPipeline::NotEqual); + break; + } +} + +void applyStateHelper(const NoDepthMask *state, QRhiGraphicsPipeline *gp) noexcept +{ + const auto values = state->values(); + gp->setDepthWrite(std::get<0>(values)); +} + +void applyStateHelper(const CullFace *state, QRhiGraphicsPipeline *gp) noexcept +{ + const auto values = state->values(); + const QCullFace::CullingMode cullingMode = + static_cast<QCullFace::CullingMode>(std::get<0>(values)); + switch (cullingMode) { + case QCullFace::NoCulling: + gp->setCullMode(QRhiGraphicsPipeline::None); + break; + case QCullFace::Front: + gp->setCullMode(QRhiGraphicsPipeline::Front); + break; + case QCullFace::Back: + gp->setCullMode(QRhiGraphicsPipeline::Back); + break; + case QCullFace::FrontAndBack: + qWarning() << "RHI doesn't handle FrontAndBack CullFace"; + break; + } +} + +void applyStateHelper(const FrontFace *state, QRhiGraphicsPipeline *gp) noexcept +{ + const auto values = state->values(); + const QFrontFace::WindingDirection cullingMode = + static_cast<QFrontFace::WindingDirection>(std::get<0>(values)); + + switch (cullingMode) { + case QFrontFace::ClockWise: + gp->setFrontFace(QRhiGraphicsPipeline::CW); + break; + case QFrontFace::CounterClockWise: + gp->setFrontFace(QRhiGraphicsPipeline::CCW); + break; + } +} + +void applyStateHelper(const StencilTest *state, QRhiGraphicsPipeline *gp) noexcept +{ + const auto values = state->values(); + gp->setStencilTest(true); + + auto getCompareOp = [](int compareOp) { + switch (compareOp) { + case QStencilTestArguments::Never: + return QRhiGraphicsPipeline::Never; + case QStencilTestArguments::Always: + return QRhiGraphicsPipeline::Always; + case QStencilTestArguments::Less: + return QRhiGraphicsPipeline::Less; + case QStencilTestArguments::LessOrEqual: + return QRhiGraphicsPipeline::LessOrEqual; + case QStencilTestArguments::Equal: + return QRhiGraphicsPipeline::Equal; + case QStencilTestArguments::GreaterOrEqual: + return QRhiGraphicsPipeline::GreaterOrEqual; + case QStencilTestArguments::Greater: + return QRhiGraphicsPipeline::Greater; + case QStencilTestArguments::NotEqual: + return QRhiGraphicsPipeline::NotEqual; + default: + qDebug() << "Unhandled stencil test argument"; + return QRhiGraphicsPipeline::Never; + } + }; + + QRhiGraphicsPipeline::StencilOpState frontCompare = gp->stencilFront(); + frontCompare.compareOp = getCompareOp(std::get<0>(values)); + gp->setStencilFront(frontCompare); + + QRhiGraphicsPipeline::StencilOpState backCompare = gp->stencilBack(); + backCompare.compareOp = getCompareOp(std::get<3>(values)); + gp->setStencilBack(backCompare); +} + +void applyStateHelper(const ColorMask *state, QRhiGraphicsPipeline *gp) noexcept +{ + const auto values = state->values(); + + // We assume a single color attachment + QRhiGraphicsPipeline::TargetBlend targetBlend; + + const bool hasTargetBlend = gp->cbeginTargetBlends() != gp->cendTargetBlends(); + if (hasTargetBlend) + targetBlend = *(gp->cbeginTargetBlends()); + + const bool redEnabled = std::get<0>(values); + const bool greenEnabled = std::get<1>(values); + const bool blueEnabled = std::get<2>(values); + const bool alphaEnabled = std::get<3>(values); + + QRhiGraphicsPipeline::ColorMask colorMask; + if (redEnabled) + colorMask |= QRhiGraphicsPipeline::R; + if (greenEnabled) + colorMask |= QRhiGraphicsPipeline::G; + if (blueEnabled) + colorMask |= QRhiGraphicsPipeline::B; + if (alphaEnabled) + colorMask |= QRhiGraphicsPipeline::A; + + targetBlend.colorWrite = colorMask; + gp->setTargetBlends({ targetBlend }); +} + +void applyStateHelper(const StencilOp *state, QRhiGraphicsPipeline *gp) noexcept +{ + const auto values = state->values(); + auto getRHIStencilOp = [](int op) { + switch (op) { + case QStencilOperationArguments::Zero: + return QRhiGraphicsPipeline::StencilZero; + case QStencilOperationArguments::Keep: + return QRhiGraphicsPipeline::Keep; + case QStencilOperationArguments::Replace: + return QRhiGraphicsPipeline::Replace; + case QStencilOperationArguments::Increment: + return QRhiGraphicsPipeline::IncrementAndClamp; + case QStencilOperationArguments::Decrement: + return QRhiGraphicsPipeline::DecrementAndClamp; + case QStencilOperationArguments::IncrementWrap: + return QRhiGraphicsPipeline::IncrementAndWrap; + case QStencilOperationArguments::DecrementWrap: + return QRhiGraphicsPipeline::DecrementAndWrap; + case QStencilOperationArguments::Invert: + return QRhiGraphicsPipeline::Invert; + default: + qDebug() << "Unhandled stencil operation argument"; + return QRhiGraphicsPipeline::StencilZero; + } + }; + + QRhiGraphicsPipeline::StencilOpState frontCompare = gp->stencilFront(); + frontCompare.depthFailOp = getRHIStencilOp(std::get<1>(values)); + frontCompare.failOp = getRHIStencilOp(std::get<0>(values)); + frontCompare.passOp = getRHIStencilOp(std::get<2>(values)); + gp->setStencilFront(frontCompare); + + QRhiGraphicsPipeline::StencilOpState backCompare = gp->stencilBack(); + backCompare.depthFailOp = getRHIStencilOp(std::get<4>(values)); + backCompare.failOp = getRHIStencilOp(std::get<3>(values)); + backCompare.passOp = getRHIStencilOp(std::get<5>(values)); + gp->setStencilBack(backCompare); +} + +void applyStateHelper(const StencilMask *state, QRhiGraphicsPipeline *gp) noexcept +{ + const auto values = state->values(); + gp->setStencilWriteMask(std::get<0>(values)); + gp->setStencilReadMask(std::get<1>(values)); +} + +static QShader::Stage rhiShaderStage(QShaderProgram::ShaderType type) noexcept +{ + switch (type) { + case QShaderProgram::Vertex: + return QShader::VertexStage; + case QShaderProgram::Fragment: + return QShader::FragmentStage; + case QShaderProgram::TessellationControl: + return QShader::TessellationControlStage; + case QShaderProgram::TessellationEvaluation: + return QShader::TessellationEvaluationStage; + case QShaderProgram::Geometry: + return QShader::GeometryStage; + case QShaderProgram::Compute: + return QShader::ComputeStage; + default: + std::abort(); + } +} + +} // anonymous + +SubmissionContext::SubmissionContext() + : m_ownCurrent(true), + m_id(nextFreeContextId()), + m_surface(nullptr), + m_activeShader(nullptr), + m_renderTargetFormat(QAbstractTexture::NoFormat), + m_material(nullptr), + m_activeFBO(0), + m_renderer(nullptr), + m_uboTempArray(QByteArray(1024, 0)), + m_initialized(false), + m_maxTextureUnits(0), + m_defaultFBO(0), + m_rhi(nullptr), + m_currentSwapChain(nullptr), + m_currentRenderPassDescriptor(nullptr) +#ifndef QT_NO_OPENGL + , + m_fallbackSurface(nullptr) +#endif +{ + static_contexts[m_id] = this; + m_contextInfo.m_api = QGraphicsApiFilter::RHI; + + // We set those version numbers because QShaderGenerator wants major > 0 + m_contextInfo.m_major = 1; + m_contextInfo.m_minor = 0; +} + +SubmissionContext::~SubmissionContext() +{ + releaseResources(); + + Q_ASSERT(static_contexts[m_id] == this); + static_contexts.remove(m_id); +} + +void SubmissionContext::initialize() +{ + m_initialized = true; + // m_textureContext.initialize(this); + + Qt3DRender::API requestedApi = Qt3DRender::API::OpenGL; + const auto userRequestedApi = qgetenv("QT3D_RHI_DEFAULT_API").toLower(); + if (!userRequestedApi.isEmpty()) { + if (userRequestedApi == QByteArrayLiteral("opengl")) { + requestedApi = Qt3DRender::API::OpenGL; + } else if (userRequestedApi == QByteArrayLiteral("vulkan")) { + requestedApi = Qt3DRender::API::Vulkan; + } else if (userRequestedApi == QByteArrayLiteral("metal")) { + requestedApi = Qt3DRender::API::Metal; + } else if (userRequestedApi == QByteArrayLiteral("d3d11")) { + requestedApi = Qt3DRender::API::DirectX; + } else if (userRequestedApi == QByteArrayLiteral("null")) { + requestedApi = Qt3DRender::API::Null; + } + } + + QRhi::Flags rhiFlags = QRhi::EnableDebugMarkers; + +#if QT_CONFIG(vulkan) + if (requestedApi == Qt3DRender::API::Vulkan) { + QRhiVulkanInitParams params; + params.inst = &Qt3DRender::staticVulkanInstance(); + m_rhi = QRhi::create(QRhi::Vulkan, ¶ms, rhiFlags); + } +#endif + +#ifdef Q_OS_WIN + if (requestedApi == Qt3DRender::API::DirectX) { + QRhiD3D11InitParams params; + params.enableDebugLayer = true; + m_rhi = QRhi::create(QRhi::D3D11, ¶ms, rhiFlags); + } +#endif + +#if defined(Q_OS_MACOS) || defined(Q_OS_IOS) + if (requestedApi == Qt3DRender::API::Metal) { + QRhiMetalInitParams params; + m_rhi = QRhi::create(QRhi::Metal, ¶ms, rhiFlags); + } +#endif + if (requestedApi == Qt3DRender::API::Null) { + QRhiInitParams params; + m_rhi = QRhi::create(QRhi::Null, ¶ms, rhiFlags); + } + + if (requestedApi != Qt3DRender::API::OpenGL && m_rhi == nullptr) { + qWarning() << "RHI: Unable to use requested RHI Api, trying to fall back on OpenGL"; + requestedApi = Qt3DRender::API::OpenGL; + } + + if (requestedApi == Qt3DRender::API::OpenGL) { +#ifndef QT_NO_OPENGL + m_fallbackSurface = QRhiGles2InitParams::newFallbackSurface(); + QRhiGles2InitParams params; + params.format = QSurfaceFormat::defaultFormat(); + params.fallbackSurface = m_fallbackSurface; + m_rhi = QRhi::create(QRhi::OpenGLES2, ¶ms, rhiFlags); +#else + qWarning() << "RHI: OpenGL not supported"; +#endif + } + + Q_ASSERT(m_rhi != nullptr); +} + +void SubmissionContext::resolveRenderTargetFormat() +{ + RHI_UNIMPLEMENTED; + + //* const QSurfaceFormat format = m_gl->format(); + //* const uint a = (format.alphaBufferSize() == -1) ? 0 : format.alphaBufferSize(); + //* const uint r = format.redBufferSize(); + //* const uint g = format.greenBufferSize(); + //* const uint b = format.blueBufferSize(); + //* + //* #define RGBA_BITS(r,g,b,a) (r | (g << 6) | (b << 12) | (a << 18)) + //* + //* const uint bits = RGBA_BITS(r,g,b,a); + //* switch (bits) { + //* case RGBA_BITS(8,8,8,8): + //* m_renderTargetFormat = QAbstractTexture::RGBA8_UNorm; + //* break; + //* case RGBA_BITS(8,8,8,0): + //* m_renderTargetFormat = QAbstractTexture::RGB8_UNorm; + //* break; + //* case RGBA_BITS(5,6,5,0): + //* m_renderTargetFormat = QAbstractTexture::R5G6B5; + //* break; + //* } + //* #undef RGBA_BITS +} + +bool SubmissionContext::beginDrawing(QSurface *surface) +{ + Q_ASSERT(surface); + + m_surface = surface; + + // TO DO: Find a way to make to pause work if the window is not exposed + // if (m_surface && m_surface->surfaceClass() == QSurface::Window) { + // qDebug() << Q_FUNC_INFO << 1; + // if (!static_cast<QWindow *>(m_surface)->isExposed()) + // return false; + // qDebug() << Q_FUNC_INFO << 2; + // } + + // Makes the surface current on the OpenGLContext + // and sets the right glHelper + // m_ownCurrent = !(m_gl->surface() == m_surface); + // if (m_ownCurrent && !makeCurrent(m_surface)) + // return false; + + // TODO: cache surface format somewhere rather than doing this every time render surface changes + resolveRenderTargetFormat(); + +#if defined(QT3D_RENDER_ASPECT_RHI_DEBUG) + GLint err = m_gl->functions()->glGetError(); + if (err != 0) { + qCWarning(Backend) << Q_FUNC_INFO << "glGetError:" << err; + } +#endif + + Q_ASSERT(isInitialized()); + + // need to reset these values every frame, may get overwritten elsewhere + RHI_UNIMPLEMENTED; + + if (m_activeShader) { + m_activeShader = nullptr; + } + + // Check if we have a swapchain for the Window, if not create one + SwapChainInfo *swapChainInfo = swapChainForSurface(surface); + QRhiSwapChain *swapChain = swapChainInfo->swapChain; + + // TO DO: Check if that's required all the time + { + // Rebuild RenderPassDescriptor + // TODO -> this is not necessary, swapChain->buildOrResize already does it + // swapChainInfo->renderBuffer->setPixelSize(surface->size()); + // swapChainInfo->renderBuffer->build(); + + // Resize swapchain if needed + if (m_surface->size() != swapChain->surfacePixelSize()) { + bool couldRebuild = swapChain->buildOrResize(); + if (!couldRebuild) + return false; + } + } + + m_currentSwapChain = swapChain; + m_currentRenderPassDescriptor = swapChainInfo->renderPassDescriptor; + + // Begin Frame + const auto success = m_rhi->beginFrame(m_currentSwapChain); + + return success == QRhi::FrameOpSuccess; +} + +void SubmissionContext::endDrawing(bool swapBuffers) +{ + m_rhi->endFrame(m_currentSwapChain, {}); + + RHI_UNIMPLEMENTED; + //* if (swapBuffers) + //* m_gl->swapBuffers(m_surface); + //* if (m_ownCurrent) + //* m_gl->doneCurrent(); + // m_textureContext.endDrawing(); + //* static int i = 0; + //* if (i++ == 10) + //* std::exit(0); +} + +void SubmissionContext::activateRenderTarget(Qt3DCore::QNodeId renderTargetNodeId, + const AttachmentPack &attachments, GLuint defaultFboId) +{ + RHI_UNIMPLEMENTED; + GLuint fboId = defaultFboId; // Default FBO + if (renderTargetNodeId) { + // New RenderTarget + if (!m_renderTargets.contains(renderTargetNodeId)) { + if (m_defaultFBO && fboId == m_defaultFBO) { + // this is the default fbo that some platforms create (iOS), we just register it + // Insert FBO into hash + m_renderTargets.insert(renderTargetNodeId, fboId); + } else { + RHI_UNIMPLEMENTED; + fboId = createRenderTarget(renderTargetNodeId, attachments); + } + } else { + RHI_UNIMPLEMENTED; + fboId = updateRenderTarget(renderTargetNodeId, attachments, true); + } + } + m_activeFBO = fboId; + //* m_glHelper->bindFrameBufferObject(m_activeFBO, GraphicsHelperInterface::FBODraw); + // Set active drawBuffers + activateDrawBuffers(attachments); +} + +GLuint SubmissionContext::createRenderTarget(Qt3DCore::QNodeId renderTargetNodeId, + const AttachmentPack &attachments) +{ + RHI_UNIMPLEMENTED; + return 0; + //* const GLuint fboId = m_glHelper->createFrameBufferObject(); + //* if (fboId) { + //* // The FBO is created and its attachments are set once + //* // Insert FBO into hash + //* m_renderTargets.insert(renderTargetNodeId, fboId); + //* // Bind FBO + //* m_glHelper->bindFrameBufferObject(fboId, GraphicsHelperInterface::FBODraw); + //* bindFrameBufferAttachmentHelper(fboId, attachments); + //* } else { + //* qCritical("Failed to create FBO"); + //* } + //* return fboId; +} + +GLuint SubmissionContext::updateRenderTarget(Qt3DCore::QNodeId renderTargetNodeId, + const AttachmentPack &attachments, + bool isActiveRenderTarget) +{ + RHI_UNIMPLEMENTED; + return 0; + //* const GLuint fboId = m_renderTargets.value(renderTargetNodeId); + //* + //* // We need to check if one of the attachment was resized + //* bool needsResize = !m_renderTargetsSize.contains(fboId); // not even initialized yet? + //* if (!needsResize) { + //* // render target exists, has attachment been resized? + //* RHITextureManager *rhiTextureManager = + //m_renderer->rhiResourceManagers()->rhiTextureManager(); + //* const QSize s = m_renderTargetsSize[fboId]; + //* const auto attachments_ = attachments.attachments(); + //* for (const Attachment &attachment : attachments_) { + //* RHITexture *rTex = rhiTextureManager->lookupResource(attachment.m_textureUuid); + //* // ### TODO QTBUG-64757 this check is insufficient since the + //* // texture may have changed to another one with the same size. That + //* // case is not handled atm. + //* if (rTex) { + //* needsResize |= rTex->size() != s; + //* if (isActiveRenderTarget && attachment.m_point == + //QRenderTargetOutput::Color0) + //* m_renderTargetFormat = rTex->properties().format; + //* } + //* } + //* } + //* + //* if (needsResize) { + //* m_glHelper->bindFrameBufferObject(fboId, GraphicsHelperInterface::FBODraw); + //* bindFrameBufferAttachmentHelper(fboId, attachments); + //* } + //* + //* return fboId; +} + +QSize SubmissionContext::renderTargetSize(const QSize &surfaceSize) const +{ + RHI_UNIMPLEMENTED; + return surfaceSize; + //* QSize renderTargetSize{}; + //* if (m_activeFBO != m_defaultFBO) { + //* // For external FBOs we may not have a m_renderTargets entry. + //* if (m_renderTargetsSize.contains(m_activeFBO)) { + //* renderTargetSize = m_renderTargetsSize[m_activeFBO]; + //* } else if (surfaceSize.isValid()) { + //* renderTargetSize = surfaceSize; + //* } else { + //* // External FBO (when used with QtQuick2 Scene3D) + //* + //* // Query FBO color attachment 0 size + //* GLint attachmentObjectType = GL_NONE; + //* GLint attachment0Name = 0; + //* m_gl->functions()->glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, + //* GL_COLOR_ATTACHMENT0, + //* GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, + //* &attachmentObjectType); + //* m_gl->functions()->glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, + //* GL_COLOR_ATTACHMENT0, + //* GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, + //* &attachment0Name); + //* + //* if (attachmentObjectType == GL_RENDERBUFFER && + //m_glHelper->supportsFeature(GraphicsHelperInterface::RenderBufferDimensionRetrieval)) + //* renderTargetSize = m_glHelper->getRenderBufferDimensions(attachment0Name); + //* else if (attachmentObjectType == GL_TEXTURE && + //m_glHelper->supportsFeature(GraphicsHelperInterface::TextureDimensionRetrieval)) + //* // Assumes texture level 0 and GL_TEXTURE_2D target + //* renderTargetSize = m_glHelper->getTextureDimensions(attachment0Name, + //GL_TEXTURE_2D); + //* else + //* return renderTargetSize; + //* } + //* } else { + //* renderTargetSize = m_surface->size(); + //* if (m_surface->surfaceClass() == QSurface::Window) { + //* const float dpr = static_cast<QWindow *>(m_surface)->devicePixelRatio(); + //* renderTargetSize *= dpr; + //* } + //* } + //* return renderTargetSize; +} + +QImage SubmissionContext::readFramebuffer(const QRect &rect) +{ + RHI_UNIMPLEMENTED; + return {}; + //* QImage img; + //* const unsigned int area = rect.width() * rect.height(); + //* unsigned int bytes; + //* GLenum format, type; + //* QImage::Format imageFormat; + //* uint stride; + //* + //* /* format value should match GL internalFormat */ + //* GLenum internalFormat = m_renderTargetFormat; + //* + //* switch (m_renderTargetFormat) { + //* case QAbstractTexture::RGBAFormat: + //* case QAbstractTexture::RGBA8_SNorm: + //* case QAbstractTexture::RGBA8_UNorm: + //* case QAbstractTexture::RGBA8U: + //* case QAbstractTexture::SRGB8_Alpha8: + //*#ifdef QT_OPENGL_ES_2 + //* format = GL_RGBA; + //* imageFormat = QImage::Format_RGBA8888_Premultiplied; + //*#else + //* format = GL_BGRA; + //* imageFormat = QImage::Format_ARGB32_Premultiplied; + //* internalFormat = GL_RGBA8; + //*#endif + //* type = GL_UNSIGNED_BYTE; + //* bytes = area * 4; + //* stride = rect.width() * 4; + //* break; + //* case QAbstractTexture::SRGB8: + //* case QAbstractTexture::RGBFormat: + //* case QAbstractTexture::RGB8U: + //* case QAbstractTexture::RGB8_UNorm: + //*#ifdef QT_OPENGL_ES_2 + //* format = GL_RGBA; + //* imageFormat = QImage::Format_RGBX8888; + //*#else + //* format = GL_BGRA; + //* imageFormat = QImage::Format_RGB32; + //* internalFormat = GL_RGB8; + //*#endif + //* type = GL_UNSIGNED_BYTE; + //* bytes = area * 4; + //* stride = rect.width() * 4; + //* break; + //*#ifndef QT_OPENGL_ES_2 + //* case QAbstractTexture::RG11B10F: + //* bytes = area * 4; + //* format = GL_RGB; + //* type = GL_UNSIGNED_INT_10F_11F_11F_REV; + //* imageFormat = QImage::Format_RGB30; + //* stride = rect.width() * 4; + //* break; + //* case QAbstractTexture::RGB10A2: + //* bytes = area * 4; + //* format = GL_RGBA; + //* type = GL_UNSIGNED_INT_2_10_10_10_REV; + //* imageFormat = QImage::Format_A2BGR30_Premultiplied; + //* stride = rect.width() * 4; + //* break; + //* case QAbstractTexture::R5G6B5: + //* bytes = area * 2; + //* format = GL_RGB; + //* type = GL_UNSIGNED_SHORT; + //* internalFormat = GL_UNSIGNED_SHORT_5_6_5_REV; + //* imageFormat = QImage::Format_RGB16; + //* stride = rect.width() * 2; + //* break; + //* case QAbstractTexture::RGBA16F: + //* case QAbstractTexture::RGBA16U: + //* case QAbstractTexture::RGBA32F: + //* case QAbstractTexture::RGBA32U: + //* bytes = area * 16; + //* format = GL_RGBA; + //* type = GL_FLOAT; + //* imageFormat = QImage::Format_ARGB32_Premultiplied; + //* stride = rect.width() * 16; + //* break; + //*#endif + //* default: + //* auto warning = qWarning(); + //* warning << "Unable to convert"; + //* QtDebugUtils::formatQEnum(warning, m_renderTargetFormat); + //* warning << "render target texture format to QImage."; + //* return img; + //* } + //* + //* GLint samples = 0; + //* m_gl->functions()->glGetIntegerv(GL_SAMPLES, &samples); + //* if (samples > 0 && + //!m_glHelper->supportsFeature(GraphicsHelperInterface::BlitFramebuffer)) { + //* qCWarning(Backend) << Q_FUNC_INFO << "Unable to capture multisampled framebuffer; " + //* "Required feature BlitFramebuffer is missing."; + //* return img; + //* } + //* + //* img = QImage(rect.width(), rect.height(), imageFormat); + //* + //* QScopedArrayPointer<uchar> data(new uchar [bytes]); + //* + //* if (samples > 0) { + //* // resolve multisample-framebuffer to renderbuffer and read pixels from it + //* GLuint fbo, rb; + //* QOpenGLFunctions *gl = m_gl->functions(); + //* gl->glGenFramebuffers(1, &fbo); + //* gl->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); + //* gl->glGenRenderbuffers(1, &rb); + //* gl->glBindRenderbuffer(GL_RENDERBUFFER, rb); + //* gl->glRenderbufferStorage(GL_RENDERBUFFER, internalFormat, rect.width(), + //rect.height()); + //* gl->glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + //GL_RENDERBUFFER, rb); + //* + //* const GLenum status = gl->glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER); + //* if (status != GL_FRAMEBUFFER_COMPLETE) { + //* gl->glDeleteRenderbuffers(1, &rb); + //* gl->glDeleteFramebuffers(1, &fbo); + //* qCWarning(Backend) << Q_FUNC_INFO << "Copy-framebuffer not complete: " << status; + //* return img; + //* } + //* + //* m_glHelper->blitFramebuffer(rect.x(), rect.y(), rect.x() + rect.width(), rect.y() + + //rect.height(), + //* 0, 0, rect.width(), rect.height(), + //* GL_COLOR_BUFFER_BIT, GL_NEAREST); + //* gl->glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); + //* gl->glReadPixels(0,0,rect.width(), rect.height(), format, type, data.data()); + //* + //* copyGLFramebufferDataToImage(img, data.data(), stride, rect.width(), rect.height(), + //m_renderTargetFormat); + //* + //* gl->glBindRenderbuffer(GL_RENDERBUFFER, rb); + //* gl->glDeleteRenderbuffers(1, &rb); + //* gl->glBindFramebuffer(GL_FRAMEBUFFER, m_activeFBO); + //* gl->glDeleteFramebuffers(1, &fbo); + //* } else { + //* // read pixels directly from framebuffer + //* m_gl->functions()->glReadPixels(rect.x(), rect.y(), rect.width(), rect.height(), + //format, type, data.data()); + //* copyGLFramebufferDataToImage(img, data.data(), stride, rect.width(), rect.height(), + //m_renderTargetFormat); + //* } + //* + //* return img; +} + +void SubmissionContext::releaseResources() +{ + m_renderBufferHash.clear(); + RHI_UNIMPLEMENTED; + + // Free RHI resources + { + qCDebug(Backend) << Q_FUNC_INFO; + + // We must ensure no remaining resource before deleting m_rhi. + m_renderer->rhiResourceManagers()->releaseAllResources(); + + auto it = m_swapChains.begin(); + while (it != m_swapChains.end()) { + SwapChainInfo &swapChainInfo = it.value(); + delete swapChainInfo.renderPassDescriptor; + delete swapChainInfo.renderBuffer; + delete swapChainInfo.swapChain; + it = m_swapChains.erase(it); + } + + delete m_rhi; + m_rhi = nullptr; + +#ifndef QT_NO_OPENGL + delete m_fallbackSurface; + m_fallbackSurface = nullptr; +#endif + } + + //* // Stop and destroy the OpenGL logger + //* if (m_debugLogger) { + //* m_debugLogger->stopLogging(); + //* m_debugLogger.reset(nullptr); + //* } +} + +// Called only from RenderThread +bool SubmissionContext::activateShader(RHIShader *shader) +{ + RHI_UNIMPLEMENTED; + //* if (shader->shaderProgram() != m_activeShader) { + //* // Ensure material uniforms are re-applied + //* m_material = nullptr; + //* + //* m_activeShader = shader->shaderProgram(); + //* if (Q_LIKELY(m_activeShader != nullptr)) { + //* m_activeShader->bind(); + //* } else { + //* m_glHelper->useProgram(0); + //* qWarning() << "No shader program found"; + //* return false; + //* } + //* } + return true; +} + +void SubmissionContext::bindFrameBufferAttachmentHelper(GLuint fboId, + const AttachmentPack &attachments) +{ + RHI_UNIMPLEMENTED; + // Set FBO attachments. These are normally textures, except that on Open GL + // ES <= 3.1 we must use a renderbuffer if a combined depth+stencil is + // desired since this cannot be achieved neither with a single texture (not + // before GLES 3.2) nor with separate textures (no suitable format for + // stencil before 3.1 with the appropriate extension). + + //* QSize fboSize; + //* RHITextureManager *rhiTextureManager = + //m_renderer->rhiResourceManagers()->rhiTextureManager(); + //* const auto attachments_ = attachments.attachments(); + //* for (const Attachment &attachment : attachments_) { + //* RHITexture *rTex = rhiTextureManager->lookupResource(attachment.m_textureUuid); + //* if (!m_glHelper->frameBufferNeedsRenderBuffer(attachment)) { + //* QOpenGLTexture *glTex = rTex ? rTex->getGLTexture() : nullptr; + //* if (glTex != nullptr) { + //* // The texture can not be rendered simultaniously by another renderer + //* Q_ASSERT(!rTex->isExternalRenderingEnabled()); + //* if (fboSize.isEmpty()) + //* fboSize = QSize(glTex->width(), glTex->height()); + //* else + //* fboSize = QSize(qMin(fboSize.width(), glTex->width()), + //qMin(fboSize.height(), glTex->height())); + //* m_glHelper->bindFrameBufferAttachment(glTex, attachment); + //* } + //* } else { + //* RenderBuffer *renderBuffer = rTex ? rTex->getOrCreateRenderBuffer() : nullptr; + //* if (renderBuffer) { + //* if (fboSize.isEmpty()) + //* fboSize = QSize(renderBuffer->width(), renderBuffer->height()); + //* else + //* fboSize = QSize(qMin(fboSize.width(), renderBuffer->width()), + //qMin(fboSize.height(), renderBuffer->height())); + //* m_glHelper->bindFrameBufferAttachment(renderBuffer, attachment); + //* } + //* } + //* } + //* m_renderTargetsSize.insert(fboId, fboSize); +} + +void SubmissionContext::activateDrawBuffers(const AttachmentPack &attachments) +{ + RHI_UNIMPLEMENTED; + //* const QVector<int> activeDrawBuffers = attachments.getGlDrawBuffers(); + //* + //* if (m_glHelper->checkFrameBufferComplete()) { + //* if (activeDrawBuffers.size() > 1) {// We need MRT + //* if (m_glHelper->supportsFeature(GraphicsHelperInterface::MRT)) { + //* // Set up MRT, glDrawBuffers... + //* m_glHelper->drawBuffers(activeDrawBuffers.size(), activeDrawBuffers.data()); + //* } + //* } + //* } else { + //* qCWarning(Backend) << "FBO incomplete"; + //* } +} + +void SubmissionContext::setActiveMaterial(Material *rmat) +{ + if (m_material == rmat) + return; + + // m_textureContext.deactivateTexturesWithScope(TextureSubmissionContext::TextureScopeMaterial); + m_material = rmat; +} + +void SubmissionContext::applyState(const StateVariant &stateVariant, + QRhiGraphicsPipeline *graphicsPipeline) +{ + switch (stateVariant.type) { + + case AlphaCoverageStateMask: { + applyStateHelper(static_cast<const AlphaCoverage *>(stateVariant.constState()), + graphicsPipeline); + break; + } + case AlphaTestMask: { + applyStateHelper(static_cast<const AlphaFunc *>(stateVariant.constState()), + graphicsPipeline); + break; + } + case BlendStateMask: { + applyStateHelper(static_cast<const BlendEquation *>(stateVariant.constState()), + graphicsPipeline); + break; + } + case BlendEquationArgumentsMask: { + applyStateHelper(static_cast<const BlendEquationArguments *>(stateVariant.constState()), + graphicsPipeline); + break; + } + case MSAAEnabledStateMask: { + applyStateHelper(static_cast<const MSAAEnabled *>(stateVariant.constState()), + graphicsPipeline, m_renderer->format()); + break; + } + + case CullFaceStateMask: { + applyStateHelper(static_cast<const CullFace *>(stateVariant.constState()), + graphicsPipeline); + break; + } + + case DepthWriteStateMask: { + applyStateHelper(static_cast<const NoDepthMask *>(stateVariant.constState()), + graphicsPipeline); + break; + } + + case DepthTestStateMask: { + applyStateHelper(static_cast<const DepthTest *>(stateVariant.constState()), + graphicsPipeline); + break; + } + + case DepthRangeMask: { + applyStateHelper(static_cast<const DepthRange *>(stateVariant.constState()), + graphicsPipeline); + break; + } + + case RasterModeMask: { + applyStateHelper(static_cast<const RasterMode *>(stateVariant.constState()), + graphicsPipeline); + break; + } + + case FrontFaceStateMask: { + applyStateHelper(static_cast<const FrontFace *>(stateVariant.constState()), + graphicsPipeline); + break; + } + + case ScissorStateMask: { + applyStateHelper(static_cast<const ScissorTest *>(stateVariant.constState()), + graphicsPipeline); + break; + } + + case StencilTestStateMask: { + applyStateHelper(static_cast<const StencilTest *>(stateVariant.constState()), + graphicsPipeline); + break; + } + + case PointSizeMask: { + applyStateHelper(static_cast<const PointSize *>(stateVariant.constState()), + graphicsPipeline); + break; + } + + case PolygonOffsetStateMask: { + applyStateHelper(static_cast<const PolygonOffset *>(stateVariant.constState()), + graphicsPipeline); + break; + } + + case ColorStateMask: { + applyStateHelper(static_cast<const ColorMask *>(stateVariant.constState()), + graphicsPipeline); + break; + } + + case ClipPlaneMask: { + applyStateHelper(static_cast<const ClipPlane *>(stateVariant.constState()), + graphicsPipeline); + break; + } + + case SeamlessCubemapMask: { + applyStateHelper(static_cast<const SeamlessCubemap *>(stateVariant.constState()), + graphicsPipeline); + break; + } + + case StencilOpMask: { + applyStateHelper(static_cast<const StencilOp *>(stateVariant.constState()), + graphicsPipeline); + break; + } + + case StencilWriteStateMask: { + applyStateHelper(static_cast<const StencilMask *>(stateVariant.constState()), + graphicsPipeline); + break; + } + + case DitheringStateMask: { + applyStateHelper(static_cast<const Dithering *>(stateVariant.constState()), + graphicsPipeline); + break; + } + + case LineWidthMask: { + applyStateHelper(static_cast<const LineWidth *>(stateVariant.constState()), + graphicsPipeline); + break; + } + default: + Q_UNREACHABLE(); + } +} + +void SubmissionContext::applyStateSet(const RenderStateSet *ss, + QRhiGraphicsPipeline *graphicsPipeline) +{ + // Set default state values on graphicsPipeline + graphicsPipeline->setDepthWrite(true); + graphicsPipeline->setDepthTest(false); + graphicsPipeline->setSampleCount(format().samples()); + + const QVector<StateVariant> statesToSet = ss->states(); + for (const StateVariant &ds : statesToSet) + applyState(ds, graphicsPipeline); +} + +StateVariant *SubmissionContext::getState(RenderStateSet *ss, StateMask type) const +{ + const QVector<StateVariant> &statesToSet = ss->states(); + for (int i = 0, m = statesToSet.size(); i < m; ++i) { + const StateVariant &ds = statesToSet.at(i); + if (ds.type == type) + return ss->states().begin() + i; + } + return nullptr; +} + +SubmissionContext::SwapChainInfo *SubmissionContext::swapChainForSurface(QSurface *surface) noexcept +{ + SwapChainInfo &swapChainInfo = m_swapChains[surface]; + auto &swapChain = swapChainInfo.swapChain; + + if (swapChain == nullptr) { + swapChain = m_rhi->newSwapChain(); + Q_ASSERT(surface->surfaceClass() == QSurface::Window); + QWindow *window = static_cast<QWindow *>(surface); + Q_ASSERT(window != nullptr); + const int samples = format().samples(); + + swapChain->setWindow(window); + swapChain->setFlags(QRhiSwapChain::Flags {}); + swapChain->setSampleCount(samples); + + QRhiRenderBuffer *renderBuffer = + m_rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, QSize(), samples, + QRhiRenderBuffer::UsedWithSwapChainOnly); + swapChain->setDepthStencil(renderBuffer); + + QRhiRenderPassDescriptor *renderPassDescriptor = + swapChain->newCompatibleRenderPassDescriptor(); + swapChain->setRenderPassDescriptor(renderPassDescriptor); + + // Build swapChain the first time + if (swapChain->buildOrResize()) { + swapChainInfo.swapChain = swapChain; + swapChainInfo.renderBuffer = renderBuffer; + swapChainInfo.renderPassDescriptor = renderPassDescriptor; + } else { + swapChain->releaseAndDestroyLater(); + m_swapChains.remove(surface); + return nullptr; + } + } + return &swapChainInfo; +} + +QRhiCommandBuffer *SubmissionContext::currentFrameCommandBuffer() const +{ + return m_currentSwapChain->currentFrameCommandBuffer(); +} + +QRhiRenderTarget *SubmissionContext::currentFrameRenderTarget() const +{ + return m_currentSwapChain->currentFrameRenderTarget(); +} + +QRhiSwapChain *SubmissionContext::currentSwapChain() const +{ + return m_currentSwapChain; +} + +QRhiRenderPassDescriptor *SubmissionContext::currentRenderPassDescriptor() const +{ + return m_currentRenderPassDescriptor; +} + +QSurfaceFormat SubmissionContext::format() const noexcept +{ + if (this->m_rhi && this->m_rhi->backend() == QRhi::OpenGLES2) { + auto rhi_gl = static_cast<const QRhiGles2NativeHandles *>(this->m_rhi->nativeHandles()); + return rhi_gl->context->format(); + } + return QSurfaceFormat::defaultFormat(); +} + +// It will be easier if the QGraphicContext applies the QUniformPack +// than the other way around +bool SubmissionContext::setParameters(ShaderParameterPack ¶meterPack) +{ + static const int irradianceId = StringToInt::lookupId(QLatin1String("envLight_irradiance")); + static const int specularId = StringToInt::lookupId(QLatin1String("envLight_specular")); + // Activate textures and update TextureUniform in the pack + // with the correct textureUnit + + // Set the pinned texture of the previous material texture + // to pinable so that we should easily find an available texture unit + // m_textureContext.deactivateTexturesWithScope(TextureSubmissionContext::TextureScopeMaterial); + // Update the uniforms with the correct texture unit id's + PackUniformHash &uniformValues = parameterPack.uniforms(); + + // Fill Texture Uniform Value with proper texture units + // so that they can be applied as regular uniforms in a second step + for (int i = 0; i < parameterPack.textures().size(); ++i) { + RHI_UNIMPLEMENTED; + //* const ShaderParameterPack::NamedResource &namedTex = parameterPack.textures().at(i); + //* // Given a Texture QNodeId, we retrieve the associated shared RHITexture + //* if (uniformValues.contains(namedTex.glslNameId)) { + //* RHITexture *t = + //m_renderer->rhiResourceManagers()->rhiTextureManager()->lookupResource(namedTex.nodeId); + //* if (t != nullptr) { + //* UniformValue &texUniform = uniformValues.value(namedTex.glslNameId); + //* if (texUniform.valueType() == UniformValue::TextureValue) { + //* const int texUnit = + //m_textureContext.activateTexture(TextureSubmissionContext::TextureScopeMaterial, m_gl, t); + //* texUniform.data<int>()[namedTex.uniformArrayIndex] = texUnit; + //* if (texUnit == -1) { + //* if (namedTex.glslNameId != irradianceId && + //* namedTex.glslNameId != specularId) { + //* // Only return false if we are not dealing with env light textures + //* qCWarning(Backend) << "Unable to find suitable Texture Unit"; + //* return false; + //* } + //* } + //* } + //* } + //* } + } + + RHIShader *shader = activeShader(); + + // TO DO: We could cache the binding points somehow and only do the binding when necessary + // for SSBO and UBO + + // Bind Shader Storage block to SSBO and update SSBO + const QVector<BlockToSSBO> &blockToSSBOs = parameterPack.shaderStorageBuffers(); + for (const BlockToSSBO &b : blockToSSBOs) { + RHI_UNIMPLEMENTED; + Buffer *cpuBuffer = + m_renderer->nodeManagers()->bufferManager()->lookupResource(b.m_bufferID); + RHIBuffer *ssbo = rhiBufferForRenderBuffer(cpuBuffer); + // bindShaderStorageBlock + // This is currently not required as we are introspecting the bindingIndex + // value from the shaders and not replacing them, making such a call useless + // bindShaderStorageBlock(shader->programId(), b.m_blockIndex, b.m_bindingIndex); + // bindShaderStorageBlock(shader->programId(), b.m_blockIndex, b.m_bindingIndex); + // Needed to avoid conflict where the buffer would already + // be bound as a VertexArray + bindRHIBuffer(ssbo, RHIBuffer::ShaderStorageBuffer); + ssbo->bindBufferBase(this, b.m_bindingIndex, RHIBuffer::ShaderStorageBuffer); + // TO DO: Make sure that there's enough binding points + } + + // Bind UniformBlocks to UBO and update UBO from Buffer + // TO DO: Convert ShaderData to Buffer so that we can use that generic process + const QVector<BlockToUBO> blockToUBOs = parameterPack.uniformBuffers(); + int uboIndex = 0; + for (const BlockToUBO &b : blockToUBOs) { + RHI_UNIMPLEMENTED; + Buffer *cpuBuffer = + m_renderer->nodeManagers()->bufferManager()->lookupResource(b.m_bufferID); + RHIBuffer *ubo = rhiBufferForRenderBuffer(cpuBuffer); + // bindUniformBlock(shader->programId(), b.m_blockIndex, uboIndex); + // Needed to avoid conflict where the buffer would already + // be bound as a VertexArray + bindRHIBuffer(ubo, RHIBuffer::UniformBuffer); + ubo->bindBufferBase(this, uboIndex++, RHIBuffer::UniformBuffer); + // TO DO: Make sure that there's enough binding points + } + + // Update uniforms in the Default Uniform Block + const PackUniformHash values = parameterPack.uniforms(); + const QVector<ShaderUniform> activeUniforms = parameterPack.submissionUniforms(); + + for (const ShaderUniform &uniform : activeUniforms) { + RHI_UNIMPLEMENTED; + // We can use [] as we are sure the the uniform wouldn't + // be un activeUniforms if there wasn't a matching value + const UniformValue &v = values.value(uniform.m_nameId); + + // skip invalid textures/images + if ((v.valueType() == UniformValue::TextureValue + || v.valueType() == UniformValue::ShaderImageValue) + && *v.constData<int>() == -1) + continue; + + // applyUniform(uniform, v); + } + // if not all data is valid, the next frame will be rendered immediately + return true; +} + +void SubmissionContext::updateBuffer(Buffer *buffer) +{ + const QHash<Qt3DCore::QNodeId, HRHIBuffer>::iterator it = + m_renderBufferHash.find(buffer->peerId()); + if (it != m_renderBufferHash.end()) + uploadDataToRHIBuffer( + buffer, m_renderer->rhiResourceManagers()->rhiBufferManager()->data(it.value())); +} + +QByteArray SubmissionContext::downloadBufferContent(Buffer *buffer) +{ + const QHash<Qt3DCore::QNodeId, HRHIBuffer>::iterator it = + m_renderBufferHash.find(buffer->peerId()); + if (it != m_renderBufferHash.end()) + return downloadDataFromRHIBuffer( + buffer, m_renderer->rhiResourceManagers()->rhiBufferManager()->data(it.value())); + return QByteArray(); +} + +void SubmissionContext::releaseBuffer(Qt3DCore::QNodeId bufferId) +{ + auto it = m_renderBufferHash.find(bufferId); + if (it != m_renderBufferHash.end()) { + HRHIBuffer glBuffHandle = it.value(); + RHIBuffer *glBuff = + m_renderer->rhiResourceManagers()->rhiBufferManager()->data(glBuffHandle); + + Q_ASSERT(glBuff); + // Destroy the GPU resource + glBuff->destroy(this); + // Destroy the RHIBuffer instance + m_renderer->rhiResourceManagers()->rhiBufferManager()->releaseResource(bufferId); + // Remove Id - HRHIBuffer entry + m_renderBufferHash.erase(it); + } +} + +bool SubmissionContext::hasRHIBufferForBuffer(Buffer *buffer) +{ + const QHash<Qt3DCore::QNodeId, HRHIBuffer>::iterator it = + m_renderBufferHash.find(buffer->peerId()); + return (it != m_renderBufferHash.end()); +} + +RHIBuffer *SubmissionContext::rhiBufferForRenderBuffer(Buffer *buf) +{ + if (!m_renderBufferHash.contains(buf->peerId())) + m_renderBufferHash.insert(buf->peerId(), createRHIBufferFor(buf)); + return m_renderer->rhiResourceManagers()->rhiBufferManager()->data( + m_renderBufferHash.value(buf->peerId())); +} + +HRHIBuffer SubmissionContext::createRHIBufferFor(Buffer *buffer) +{ + m_renderer->rhiResourceManagers()->rhiBufferManager()->getOrCreateResource(buffer->peerId()); + return m_renderer->rhiResourceManagers()->rhiBufferManager()->lookupHandle(buffer->peerId()); +} + +bool SubmissionContext::bindRHIBuffer(RHIBuffer *buffer, RHIBuffer::Type type) +{ + return buffer->bind(this, type); +} + +void SubmissionContext::uploadDataToRHIBuffer(Buffer *buffer, RHIBuffer *b, bool releaseBuffer) +{ + // If the buffer is dirty (hence being called here) + // there are two possible cases + // * setData was called changing the whole data or functor (or the usage pattern) + // * partial buffer updates where received + + // Note: we are only storing the updates data CPU side at this point + // actually upload will be performed when the buffer will be bound + // as we would otherwise need to know the usage type of the buffer + QVector<Qt3DRender::QBufferUpdate> updates = std::move(buffer->pendingBufferUpdates()); + for (auto it = updates.begin(); it != updates.end(); ++it) { + auto update = it; + // We have a partial update + if (update->offset >= 0) { + // accumulate sequential updates as single one + int bufferSize = update->data.size(); + auto it2 = it + 1; + while ((it2 != updates.end()) && (it2->offset - update->offset == bufferSize)) { + bufferSize += it2->data.size(); + ++it2; + } + update->data.resize(bufferSize); + while (it + 1 != it2) { + ++it; + update->data.replace(it->offset - update->offset, it->data.size(), it->data); + it->data.clear(); + } + // TO DO: based on the number of updates .., it might make sense to + // sometime use glMapBuffer rather than glBufferSubData + b->update(this, update->data, update->offset); + } else { + // We have an update that was done by calling QBuffer::setData + // which is used to resize or entirely clear the buffer + // Note: we use the buffer data directly in that case + b->orphan(this); // orphan the buffer + b->allocate(this, buffer->data(), false); + } + } + + if (releaseBuffer) { + b->release(this); + } + qCDebug(Io) << "uploaded buffer size=" << buffer->data().size(); +} + +QByteArray SubmissionContext::downloadDataFromRHIBuffer(Buffer *buffer, RHIBuffer *b) +{ + if (!bindRHIBuffer(b, + RHIBuffer::ArrayBuffer)) // We're downloading, the type doesn't matter here + qCWarning(Io) << Q_FUNC_INFO << "buffer bind failed"; + + return b->download(this, buffer->data().size()); +} + +void SubmissionContext::blitFramebuffer(Qt3DCore::QNodeId inputRenderTargetId, + Qt3DCore::QNodeId outputRenderTargetId, QRect inputRect, + QRect outputRect, uint defaultFboId, + QRenderTargetOutput::AttachmentPoint inputAttachmentPoint, + QRenderTargetOutput::AttachmentPoint outputAttachmentPoint, + QBlitFramebuffer::InterpolationMethod interpolationMethod) +{ + RHI_UNIMPLEMENTED; + //* GLuint inputFboId = defaultFboId; + //* bool inputBufferIsDefault = true; + //* if (!inputRenderTargetId.isNull()) { + //* RenderTarget *renderTarget = + //m_renderer->nodeManagers()->renderTargetManager()->lookupResource(inputRenderTargetId); + //* if (renderTarget) { + //* AttachmentPack attachments(renderTarget, + //m_renderer->nodeManagers()->attachmentManager()); + //* if (m_renderTargets.contains(inputRenderTargetId)) + //* inputFboId = updateRenderTarget(inputRenderTargetId, attachments, false); + //* else + //* inputFboId = createRenderTarget(inputRenderTargetId, attachments); + //* } + //* inputBufferIsDefault = false; + //* } + //* + //* GLuint outputFboId = defaultFboId; + //* bool outputBufferIsDefault = true; + //* if (!outputRenderTargetId.isNull()) { + //* RenderTarget *renderTarget = + //m_renderer->nodeManagers()->renderTargetManager()->lookupResource(outputRenderTargetId); + //* if (renderTarget) { + //* AttachmentPack attachments(renderTarget, + //m_renderer->nodeManagers()->attachmentManager()); + //* if (m_renderTargets.contains(outputRenderTargetId)) + //* outputFboId = updateRenderTarget(outputRenderTargetId, attachments, false); + //* else + //* outputFboId = createRenderTarget(outputRenderTargetId, attachments); + //* } + //* outputBufferIsDefault = false; + //* } + //* + //* // Up until this point the input and output rects are normal Qt rectangles. + //* // Convert them to GL rectangles (Y at bottom). + //* const int inputFboHeight = inputFboId == defaultFboId ? m_surfaceSize.height() : + //m_renderTargetsSize[inputFboId].height(); + //* const GLint srcX0 = inputRect.left(); + //* const GLint srcY0 = inputFboHeight - (inputRect.top() + inputRect.height()); + //* const GLint srcX1 = srcX0 + inputRect.width(); + //* const GLint srcY1 = srcY0 + inputRect.height(); + //* + //* const int outputFboHeight = outputFboId == defaultFboId ? m_surfaceSize.height() : + //m_renderTargetsSize[outputFboId].height(); + //* const GLint dstX0 = outputRect.left(); + //* const GLint dstY0 = outputFboHeight - (outputRect.top() + outputRect.height()); + //* const GLint dstX1 = dstX0 + outputRect.width(); + //* const GLint dstY1 = dstY0 + outputRect.height(); + //* + //* //Get the last bounded framebuffers + //* const GLuint lastDrawFboId = boundFrameBufferObject(); + //* + //* // Activate input framebuffer for reading + //* bindFramebuffer(inputFboId, GraphicsHelperInterface::FBORead); + //* + //* // Activate output framebuffer for writing + //* bindFramebuffer(outputFboId, GraphicsHelperInterface::FBODraw); + //* + //* //Bind texture + //* if (!inputBufferIsDefault) + //* readBuffer(GL_COLOR_ATTACHMENT0 + inputAttachmentPoint); + //* + //* if (!outputBufferIsDefault) { + //* // Note that we use glDrawBuffers, not glDrawBuffer. The + //* // latter is not available with GLES. + //* const int buf = outputAttachmentPoint; + //* drawBuffers(1, &buf); + //* } + //* + //* // Blit framebuffer + //* const GLenum mode = interpolationMethod ? GL_NEAREST : GL_LINEAR; + //* m_glHelper->blitFramebuffer(srcX0, srcY0, srcX1, srcY1, + //* dstX0, dstY0, dstX1, dstY1, + //* GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT, + //* mode); + //* + //* // Reset draw buffer + //* bindFramebuffer(lastDrawFboId, GraphicsHelperInterface::FBOReadAndDraw); + //* if (outputAttachmentPoint != QRenderTargetOutput::Color0) { + //* const int buf = QRenderTargetOutput::Color0; + //* drawBuffers(1, &buf); + //* } +} + +namespace { +template<std::size_t N> +constexpr int getFirstAvailableBit(const std::bitset<N> &bits) +{ + for (std::size_t i = 0; i < N; i++) { + if (!bits.test(i)) + return i; + } + return -1; +} +// This function ensures that the shader stages all have the same bindings +void preprocessRHIShader(QVector<QByteArray> &shaderCodes) +{ + // Map the variable names to bindings + std::map<QByteArray, int> bindings; + bindings["qt3d_render_view_uniforms"] = 0; + bindings["qt3d_command_uniforms"] = 1; + std::bitset<512> assignedBindings; + assignedBindings.set(0); + assignedBindings.set(1); + + thread_local const QRegularExpression samplerRegex( + QStringLiteral("binding\\s*=\\s*([0-9]+).*\\)\\s*uniform\\s*[ui]?sampler[a-zA-Z0-9]+" + "\\s*([a-zA-Z0-9_]+)\\s*;")); + thread_local const QRegularExpression uboRegex( + QStringLiteral("(?:std140\\s*,\\s*binding\\s*=\\s*([0-9]+).*|binding\\s*=\\s*([0-9]+)" + "\\s*,\\s*std140.*)\\)\\s*uniform\\s*([a-zA-Z0-9_]+)")); + + auto replaceBinding = [&bindings, &assignedBindings]( + int &offset, QRegularExpressionMatch &match, QByteArray &code, + int indexCapture, int variableCapture) noexcept { + int index = match.captured(indexCapture).toInt(); + QByteArray variable = match.captured(variableCapture).toUtf8(); + + auto it = bindings.find(variable); + if (it == bindings.end()) { + // 1. Check if the index is already used + if (assignedBindings.test(index)) { + index = getFirstAvailableBit(assignedBindings); + if (index == -1) { + return; + } + + const int indexStartOffset = match.capturedStart(indexCapture); + const int indexEndOffset = match.capturedEnd(indexCapture); + const int indexLength = indexEndOffset - indexStartOffset; + code.replace(indexStartOffset, indexLength, QByteArray::number(index)); + } + + assignedBindings.set(index); + bindings.emplace(std::move(variable), index); + } else { + int indexToUse = it->second; + const int indexStartOffset = match.capturedStart(indexCapture); + const int indexEndOffset = match.capturedEnd(indexCapture); + const int indexLength = indexEndOffset - indexStartOffset; + code.replace(indexStartOffset, indexLength, QByteArray::number(indexToUse)); + } + // This may fail in the case where the replaced offset is an incredibly long number, + // which seems quite unlikely + offset = match.capturedEnd(0); + }; + + for (QByteArray &shaderCode : shaderCodes) { + // Regex for the sampler variables + int offset = 0; + auto match = samplerRegex.match(shaderCode, offset); + while (match.hasMatch()) { + const int indexCapture = 1; + const int variableCapture = 2; + replaceBinding(offset, match, shaderCode, indexCapture, variableCapture); + + match = samplerRegex.match(shaderCode, offset); + } + + // Regex for the UBOs + offset = 0; + match = uboRegex.match(shaderCode, offset); + while (match.hasMatch()) { + const int indexCapture = !match.capturedView(1).isEmpty() ? 1 : 2; + const int variableCapture = 3; + replaceBinding(offset, match, shaderCode, indexCapture, variableCapture); + + match = uboRegex.match(shaderCode, offset); + } + } +} + +int glslVersionForFormat(const QSurfaceFormat &format) noexcept +{ + const int major = format.majorVersion(); + const int minor = format.minorVersion(); + + static const QHash<std::pair<int, int>, int> glVersionToGLSLVersion = { + { { 4, 6 }, 460 }, { { 4, 5 }, 450 }, { { 4, 4 }, 440 }, { { 4, 3 }, 430 }, + { { 4, 2 }, 420 }, { { 4, 1 }, 410 }, { { 4, 0 }, 400 }, { { 3, 3 }, 330 }, + { { 3, 2 }, 150 }, { { 3, 2 }, 120 }, { { 3, 1 }, 120 }, + }; + + const auto it = glVersionToGLSLVersion.find({ major, minor }); + if (it == glVersionToGLSLVersion.end()) { + if (major < 3) { + return 120; + } else { + return major * 100 + minor * 10; + } + } else { + return *it; + } +} +} + +// Called by GL Command Thread +SubmissionContext::ShaderCreationInfo SubmissionContext::createShaderProgram(RHIShader *shader) +{ + // Compile shaders + const auto &shaderCode = shader->shaderCode(); + QShaderBaker b; + b.setGeneratedShaders({ + { QShader::SpirvShader, 100 }, +#ifndef QT_NO_OPENGL + { QShader::GlslShader, glslVersionForFormat(format()) }, +#endif + { QShader::HlslShader, QShaderVersion(50) }, + { QShader::MslShader, QShaderVersion(12) }, + }); + + b.setGeneratedShaderVariants({ QShader::Variant {}, +#ifndef QT_NO_OPENGL + QShader::Variant {}, +#endif + QShader::Variant {}, QShader::Variant {} }); + + // TODO handle caching as QShader does not have a built-in mechanism for that + QString logs; + bool success = true; + for (int i = QShaderProgram::Vertex; i <= QShaderProgram::Compute; ++i) { + const QShaderProgram::ShaderType type = static_cast<QShaderProgram::ShaderType>(i); + if (!shaderCode.at(i).isEmpty()) { + // Note: logs only return the error but not all the shader code + // we could append it + + const auto rhiStage = rhiShaderStage(type); + b.setSourceString(shaderCode.at(i), rhiStage); + QShader bakedShader = b.bake(); + if (b.errorMessage() != QString() || !bakedShader.isValid()) { + qDebug() << "Shader Error: " << b.errorMessage() << shaderCode.at(i).data() + << rhiStage; + logs += b.errorMessage(); + success = false; + } + shader->m_stages[rhiStage] = std::move(bakedShader); + } + } + + // Perform shader introspection + if (success) + shader->introspect(); + + return { success, logs }; +} + +// Called by Renderer::updateResources +void SubmissionContext::loadShader(Shader *shaderNode, ShaderManager *shaderManager, + RHIShaderManager *rhiShaderManager) +{ + const Qt3DCore::QNodeId shaderId = shaderNode->peerId(); + RHIShader *rhiShader = rhiShaderManager->lookupResource(shaderId); + + // We already have a shader associated with the node + if (rhiShader != nullptr) { + // We need to abandon it + rhiShaderManager->abandon(rhiShader, shaderNode); + } + + // We create or adopt an already created rhiShader + rhiShader = rhiShaderManager->createOrAdoptExisting(shaderNode); + + const QVector<Qt3DCore::QNodeId> sharedShaderIds = + rhiShaderManager->shaderIdsForProgram(rhiShader); + if (sharedShaderIds.size() == 1) { + // Shader in the cache hasn't been loaded yet + QVector<QByteArray> shaderCodes = shaderNode->shaderCode(); + preprocessRHIShader(shaderCodes); + rhiShader->setShaderCode(shaderCodes); + + const ShaderCreationInfo loadResult = createShaderProgram(rhiShader); + shaderNode->setStatus(loadResult.linkSucceeded ? QShaderProgram::Ready + : QShaderProgram::Error); + shaderNode->setLog(loadResult.logs); + // Loaded in the sense we tried to load it (and maybe it failed) + rhiShader->setLoaded(true); + } else { + // Find an already loaded shader that shares the same QShaderProgram + for (const Qt3DCore::QNodeId &sharedShaderId : sharedShaderIds) { + if (sharedShaderId != shaderNode->peerId()) { + Shader *refShader = shaderManager->lookupResource(sharedShaderId); + // We only introspect once per actual OpenGL shader program + // rather than once per ShaderNode. + shaderNode->initializeFromReference(*refShader); + break; + } + } + } + shaderNode->unsetDirty(); + // Ensure we will rebuilt material caches + shaderNode->requestCacheRebuild(); +} + +const GraphicsApiFilterData *SubmissionContext::contextInfo() const +{ + return &m_contextInfo; +} + +} // namespace Rhi +} // namespace Render +} // namespace Qt3DRender of namespace + +QT_END_NAMESPACE diff --git a/src/plugins/renderers/rhi/graphicshelpers/submissioncontext_p.h b/src/plugins/renderers/rhi/graphicshelpers/submissioncontext_p.h new file mode 100644 index 000000000..7578639e6 --- /dev/null +++ b/src/plugins/renderers/rhi/graphicshelpers/submissioncontext_p.h @@ -0,0 +1,266 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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_RHI_SUBMISSIONCONTEXT_H +#define QT3DRENDER_RENDER_RHI_SUBMISSIONCONTEXT_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 <rhibuffer_p.h> +#include <Qt3DRender/qclearbuffers.h> +#include <Qt3DRender/qattribute.h> +#include <Qt3DRender/private/handle_types_p.h> +#include <Qt3DRender/qblitframebuffer.h> +#include <Qt3DRender/private/shader_p.h> +#include <Qt3DRender/private/statemask_p.h> +#include <Qt3DRender/private/qgraphicsapifilter_p.h> +#include <shaderparameterpack_p.h> +#include <shadervariables_p.h> +#include <rhihandle_types_p.h> +#include <QSurface> +#include <QtGui/private/qrhi_p.h> +#include <QOffscreenSurface> + +QT_BEGIN_NAMESPACE + +class QAbstractOpenGLFunctions; + +namespace Qt3DRender { + +namespace Render { + +class Material; +class AttachmentPack; +class Attribute; +class Buffer; +class ShaderManager; +struct StateVariant; +class RenderTarget; +class RenderStateSet; + +namespace Rhi { + +class Renderer; +class GraphicsHelperInterface; +class RHITexture; +class RHIShader; +class RHIShaderManager; +class RenderCommand; + +typedef QPair<QString, int> NamedUniformLocation; + +class Q_AUTOTEST_EXPORT SubmissionContext +{ +public: + enum Feature { + MRT = 0, + Tessellation, + UniformBufferObject, + BindableFragmentOutputs, + PrimitiveRestart, + RenderBufferDimensionRetrieval, + TextureDimensionRetrieval, + ShaderStorageObject, + Compute, + DrawBuffersBlend, + BlitFramebuffer, + IndirectDrawing, + MapBuffer, + Fences, + ShaderImage + }; + + enum FBOBindMode { FBODraw, FBORead, FBOReadAndDraw }; + + SubmissionContext(); + ~SubmissionContext(); + + void initialize(); + int id() const; // unique, small integer ID of this context + void setRenderer(Renderer *renderer) { m_renderer = renderer; } + + bool beginDrawing(QSurface *surface); + void endDrawing(bool swapBuffers); + void releaseResources(); + void setOpenGLContext(QOpenGLContext *ctx); + bool isInitialized() const { return m_initialized; } + const GraphicsApiFilterData *contextInfo() const; + + // Shaders + struct ShaderCreationInfo + { + bool linkSucceeded = false; + QString logs; + }; + + ShaderCreationInfo createShaderProgram(RHIShader *shaderNode); + void loadShader(Shader *shader, ShaderManager *shaderManager, + RHIShaderManager *rhiShaderManager); + + GLuint defaultFBO() const { return m_defaultFBO; } + + // Shaders + bool activateShader(RHIShader *shader); + RHIShader *activeShader() const { return m_activeShader; } + + // FBO + GLuint activeFBO() const { return m_activeFBO; } + void activateRenderTarget(const Qt3DCore::QNodeId id, const AttachmentPack &attachments, + GLuint defaultFboId); + QSize renderTargetSize(const QSize &surfaceSize) const; + QImage readFramebuffer(const QRect &rect); + void blitFramebuffer(Qt3DCore::QNodeId outputRenderTargetId, + Qt3DCore::QNodeId inputRenderTargetId, QRect inputRect, QRect outputRect, + uint defaultFboId, + QRenderTargetOutput::AttachmentPoint inputAttachmentPoint, + QRenderTargetOutput::AttachmentPoint outputAttachmentPoint, + QBlitFramebuffer::InterpolationMethod interpolationMethod); + + // Attributes + void specifyAttribute(const Attribute *attribute, Buffer *buffer, + const ShaderAttribute *attributeDescription); + void specifyIndices(Buffer *buffer); + + // Buffer + void updateBuffer(Buffer *buffer); + QByteArray downloadBufferContent(Buffer *buffer); + void releaseBuffer(Qt3DCore::QNodeId bufferId); + bool hasRHIBufferForBuffer(Buffer *buffer); + RHIBuffer *rhiBufferForRenderBuffer(Buffer *buf); + + // Parameters + bool setParameters(ShaderParameterPack ¶meterPack); + + // RenderState + void applyStateSet(const RenderStateSet *ss, QRhiGraphicsPipeline *graphicsPipeline); + StateVariant *getState(RenderStateSet *ss, StateMask type) const; + + // Swap chain + + struct SwapChainInfo + { + QRhiSwapChain *swapChain = nullptr; + QRhiRenderBuffer *renderBuffer = nullptr; + QRhiRenderPassDescriptor *renderPassDescriptor = nullptr; + }; + SwapChainInfo *swapChainForSurface(QSurface *surface) noexcept; + + QRhiResourceUpdateBatch *m_currentUpdates {}; + + QRhi *rhi() const { return m_rhi; } + QRhiCommandBuffer *currentFrameCommandBuffer() const; + QRhiRenderTarget *currentFrameRenderTarget() const; + QRhiRenderPassDescriptor *currentRenderPassDescriptor() const; + QRhiSwapChain *currentSwapChain() const; + QSurfaceFormat format() const noexcept; + +private: + // Material + Material *activeMaterial() const { return m_material; } + void setActiveMaterial(Material *rmat); + + // FBO + void bindFrameBufferAttachmentHelper(GLuint fboId, const AttachmentPack &attachments); + void activateDrawBuffers(const AttachmentPack &attachments); + void resolveRenderTargetFormat(); + GLuint createRenderTarget(Qt3DCore::QNodeId renderTargetNodeId, + const AttachmentPack &attachments); + GLuint updateRenderTarget(Qt3DCore::QNodeId renderTargetNodeId, + const AttachmentPack &attachments, bool isActiveRenderTarget); + + // Buffers + HRHIBuffer createRHIBufferFor(Buffer *buffer); + void uploadDataToRHIBuffer(Buffer *buffer, RHIBuffer *b, bool releaseBuffer = false); + QByteArray downloadDataFromRHIBuffer(Buffer *buffer, RHIBuffer *b); + bool bindRHIBuffer(RHIBuffer *buffer, RHIBuffer::Type type); + + // States + void applyState(const StateVariant &state, QRhiGraphicsPipeline *graphicsPipeline); + + bool m_ownCurrent; + const unsigned int m_id; + QSurface *m_surface; + QSize m_surfaceSize; + + RHIShader *m_activeShader; + + QHash<Qt3DCore::QNodeId, HRHIBuffer> m_renderBufferHash; + QHash<Qt3DCore::QNodeId, GLuint> m_renderTargets; + QHash<GLuint, QSize> m_renderTargetsSize; + QAbstractTexture::TextureFormat m_renderTargetFormat; + + Material *m_material; + GLuint m_activeFBO; + + Renderer *m_renderer; + QByteArray m_uboTempArray; + + bool m_initialized; + + GLint m_maxTextureUnits; + GLuint m_defaultFBO; + GraphicsApiFilterData m_contextInfo; + + QRhi *m_rhi; + QHash<QSurface *, SwapChainInfo> m_swapChains; + QRhiSwapChain *m_currentSwapChain; + QRhiRenderPassDescriptor *m_currentRenderPassDescriptor; + +#ifndef QT_NO_OPENGL + QOffscreenSurface *m_fallbackSurface; +#endif +}; + +} // namespace Rhi +} // namespace Render +} // namespace Qt3DRender + +QT_END_NAMESPACE + +#endif // QT3DRENDER_RENDER_RHI_SUBMISSIONCONTEXT_H diff --git a/src/plugins/renderers/rhi/io/io.pri b/src/plugins/renderers/rhi/io/io.pri new file mode 100644 index 000000000..bac06ba95 --- /dev/null +++ b/src/plugins/renderers/rhi/io/io.pri @@ -0,0 +1,8 @@ +INCLUDEPATH += $$PWD + +SOURCES += \ + $$PWD/rhibuffer.cpp + +HEADERS += \ + $$PWD/rhibuffer_p.h + diff --git a/src/plugins/renderers/rhi/io/rhibuffer.cpp b/src/plugins/renderers/rhi/io/rhibuffer.cpp new file mode 100644 index 000000000..7b63b01a2 --- /dev/null +++ b/src/plugins/renderers/rhi/io/rhibuffer.cpp @@ -0,0 +1,199 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 "rhibuffer_p.h" +#include <submissioncontext_p.h> +#include <QtGui/private/qrhi_p.h> +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { + +namespace Render { + +namespace Rhi { + +namespace { +QRhiBuffer::UsageFlag bufferTypeToRhi(RHIBuffer::Type t) +{ + switch (t) { + case RHIBuffer::Type::ArrayBuffer: + return QRhiBuffer::VertexBuffer; + case RHIBuffer::Type::IndexBuffer: + return QRhiBuffer::IndexBuffer; + case RHIBuffer::Type::UniformBuffer: + return QRhiBuffer::UniformBuffer; + default: + RHI_UNIMPLEMENTED; + return QRhiBuffer::StorageBuffer; + } +} +} + +// A UBO is created for each ShaderData Shader Pair +// That means a UBO is unique to a shader/shaderdata + +RHIBuffer::RHIBuffer() : m_bufferId(0), m_dynamic(true), m_lastTarget(GL_ARRAY_BUFFER) { } + +bool RHIBuffer::bind(SubmissionContext *ctx, Type t) +{ + assert(ctx->m_currentUpdates); + if (this->m_datasToUpload.empty()) + return bool(m_rhiBuffer); + + const auto uploadMethod = m_dynamic ? &QRhiResourceUpdateBatch::updateDynamicBuffer + : qOverload<QRhiBuffer *, int, int, const void *>( + &QRhiResourceUpdateBatch::uploadStaticBuffer); + if (!m_rhiBuffer) { + if (m_allocSize <= 0) + return false; + + const auto kind = m_dynamic ? QRhiBuffer::Dynamic : QRhiBuffer::Static; + const auto usage = bufferTypeToRhi(t); + + // RHI does not seem to support using the same buffer with different types + if (m_rhiBuffer) + assert(m_rhiBuffer->usage() == usage); + + if (!m_rhiBuffer) + m_rhiBuffer = ctx->rhi()->newBuffer(kind, usage, m_allocSize); + assert(m_rhiBuffer); + + m_rhiBuffer->build(); +#if defined(QT_DEBUG) + { + // for debug: we set the buffer to zero + auto ptr = new char[m_allocSize] {}; + (ctx->m_currentUpdates->*uploadMethod)(m_rhiBuffer, 0, m_allocSize, ptr); + delete[] ptr; + } +#endif + } + + for (const std::pair<QByteArray, int> &pair : this->m_datasToUpload) { + const QByteArray &data = pair.first; + int offset = pair.second; + (ctx->m_currentUpdates->*uploadMethod)(m_rhiBuffer, offset, data.size(), data.constData()); + } + + m_datasToUpload.clear(); + return true; +} + +bool RHIBuffer::release(SubmissionContext *ctx) +{ + if (m_rhiBuffer) + m_rhiBuffer->release(); + return true; +} + +bool RHIBuffer::create(SubmissionContext *ctx) +{ + return true; +} + +void RHIBuffer::destroy(SubmissionContext *ctx) +{ + if (m_rhiBuffer) { + m_rhiBuffer->releaseAndDestroyLater(); + m_rhiBuffer = nullptr; + } + m_allocSize = 0; +} + +void RHIBuffer::orphan(SubmissionContext *) +{ + m_datasToUpload.clear(); + if (m_rhiBuffer) { + m_rhiBuffer->releaseAndDestroyLater(); + m_rhiBuffer = nullptr; + } + m_allocSize = 0; +} + +void RHIBuffer::allocate(SubmissionContext *ctx, const QByteArray &data, bool dynamic) +{ + m_datasToUpload.clear(); + m_datasToUpload.push_back({ data, 0 }); + m_allocSize = data.size(); + m_dynamic = dynamic; +} + +void RHIBuffer::update(SubmissionContext *ctx, const QByteArray &data, int offset) +{ + m_datasToUpload.push_back({ data, offset }); +} + +QByteArray RHIBuffer::download(SubmissionContext *ctx, uint size) +{ + RHI_UNIMPLEMENTED; + // char *gpu_ptr = ctx->mapBuffer(m_lastTarget, size); + // QByteArray data; + // if (gpu_ptr != nullptr) { + // data.resize(size); + // std::copy(gpu_ptr, gpu_ptr+size, data.data()); + // } + // ctx->unmapBuffer(m_lastTarget); + // return data; + return {}; +} + +void RHIBuffer::bindBufferBase(SubmissionContext *ctx, int bindingPoint, RHIBuffer::Type t) +{ + RHI_UNIMPLEMENTED; + // ctx->bindBufferBase(glBufferTypes[t], bindingPoint, m_bufferId); +} + +void RHIBuffer::bindBufferBase(SubmissionContext *ctx, int bindingPoint) +{ + RHI_UNIMPLEMENTED; + // ctx->bindBufferBase(m_lastTarget, bindingPoint, m_bufferId); +} + +void RHIBuffer::cleanup() +{ + destroy(nullptr); +} + +} // namespace Rhi + +} // namespace Render + +} // namespace Qt3DRender + +QT_END_NAMESPACE diff --git a/src/plugins/renderers/rhi/io/rhibuffer_p.h b/src/plugins/renderers/rhi/io/rhibuffer_p.h new file mode 100644 index 000000000..4616c4111 --- /dev/null +++ b/src/plugins/renderers/rhi/io/rhibuffer_p.h @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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_RHI_RHIBUFFER_P_H +#define QT3DRENDER_RENDER_RHI_RHIBUFFER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <Qt3DCore/qnodeid.h> +#include <qbytearray.h> + +QT_BEGIN_NAMESPACE +class QRhiBuffer; +namespace Qt3DRender { + +namespace Render { + +namespace Rhi { + +class SubmissionContext; + +class RHIBuffer +{ +public: + RHIBuffer(); + + enum Type { + ArrayBuffer = 0, + UniformBuffer, + IndexBuffer, + ShaderStorageBuffer, + PixelPackBuffer, + PixelUnpackBuffer, + DrawIndirectBuffer + }; + + bool bind(SubmissionContext *ctx, Type t); + bool release(SubmissionContext *ctx); + bool create(SubmissionContext *ctx); + void destroy(SubmissionContext *ctx); + void orphan(SubmissionContext *ctx); + void allocate(SubmissionContext *ctx, const QByteArray &data, bool dynamic = true); + void update(SubmissionContext *ctx, const QByteArray &data, int offset = 0); + QByteArray download(SubmissionContext *ctx, uint size); + void bindBufferBase(SubmissionContext *ctx, int bindingPoint, Type t); + void bindBufferBase(SubmissionContext *ctx, int bindingPoint); + + void cleanup(); + + QRhiBuffer *rhiBuffer() const noexcept { return m_rhiBuffer; } + +private: + uint m_bufferId; + bool m_dynamic; + int m_allocSize {}; + int m_lastTarget; + + QRhiBuffer *m_rhiBuffer {}; + + std::vector<std::pair<QByteArray /*data*/, int /*offset*/>> m_datasToUpload; +}; + +} // namespace Rhi + +} // namespace Render + +} // namespace Qt3DRender + +QT_END_NAMESPACE + +#endif // QT3DRENDER_RENDER_RHI_RHIBUFFER_P_H diff --git a/src/plugins/renderers/rhi/jobs/filtercompatibletechniquejob.cpp b/src/plugins/renderers/rhi/jobs/filtercompatibletechniquejob.cpp new file mode 100644 index 000000000..657e5eea7 --- /dev/null +++ b/src/plugins/renderers/rhi/jobs/filtercompatibletechniquejob.cpp @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 "filtercompatibletechniquejob_p.h" +#include <Qt3DRender/private/techniquemanager_p.h> +#include <Qt3DRender/private/nodemanagers_p.h> +#include <Qt3DRender/private/job_common_p.h> +#include <renderer_p.h> +#include <submissioncontext_p.h> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { +namespace Render { +namespace Rhi { + +FilterCompatibleTechniqueJob::FilterCompatibleTechniqueJob() + : m_manager(nullptr), m_renderer(nullptr) +{ + SET_JOB_RUN_STAT_TYPE(this, JobTypes::FilterCompatibleTechniques, 0) +} + +void FilterCompatibleTechniqueJob::setManager(TechniqueManager *manager) +{ + m_manager = manager; +} + +TechniqueManager *FilterCompatibleTechniqueJob::manager() const +{ + return m_manager; +} + +void FilterCompatibleTechniqueJob::setRenderer(Renderer *renderer) +{ + m_renderer = renderer; +} + +Renderer *FilterCompatibleTechniqueJob::renderer() const +{ + return m_renderer; +} + +void FilterCompatibleTechniqueJob::run() +{ + Q_ASSERT(m_manager != nullptr && m_renderer != nullptr); + Q_ASSERT(m_renderer->isRunning() && m_renderer->submissionContext()->isInitialized()); + + const QVector<Qt3DCore::QNodeId> dirtyTechniqueIds = m_manager->takeDirtyTechniques(); + for (const Qt3DCore::QNodeId techniqueId : dirtyTechniqueIds) { + Technique *technique = m_manager->lookupResource(techniqueId); + if (Q_LIKELY(technique != nullptr)) + technique->setCompatibleWithRenderer( + (*m_renderer->contextInfo() == *technique->graphicsApiFilter())); + } +} + +} // namespace Rhi +} // namespace Render +} // namespace Qt3DRender + +QT_END_NAMESPACE diff --git a/src/plugins/renderers/rhi/jobs/filtercompatibletechniquejob_p.h b/src/plugins/renderers/rhi/jobs/filtercompatibletechniquejob_p.h new file mode 100644 index 000000000..726ed9faa --- /dev/null +++ b/src/plugins/renderers/rhi/jobs/filtercompatibletechniquejob_p.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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_RHI_FILTERCOMPATIBLETECHNIQUEJOB_H +#define QT3DRENDER_RENDER_RHI_FILTERCOMPATIBLETECHNIQUEJOB_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <Qt3DCore/qaspectjob.h> +#include <Qt3DRender/private/qt3drender_global_p.h> + +#include <QSharedPointer> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { +namespace Render { + +class TechniqueManager; + +namespace Rhi { + +class Renderer; + +class Q_AUTOTEST_EXPORT FilterCompatibleTechniqueJob : public Qt3DCore::QAspectJob +{ +public: + FilterCompatibleTechniqueJob(); + + void setManager(TechniqueManager *managers); + TechniqueManager *manager() const; + + void setRenderer(Renderer *renderer); + Renderer *renderer() const; + + void run() override; + +private: + TechniqueManager *m_manager; + Renderer *m_renderer; +}; + +typedef QSharedPointer<FilterCompatibleTechniqueJob> FilterCompatibleTechniqueJobPtr; + +} // namespace Rhi +} // namespace Render +} // namespace Qt3DRender + +QT_END_NAMESPACE + +#endif // QT3DRENDER_RENDER_RHI_FILTERCOMPATIBLETECHNIQUEJOB_H diff --git a/src/plugins/renderers/rhi/jobs/jobs.pri b/src/plugins/renderers/rhi/jobs/jobs.pri new file mode 100644 index 000000000..d80b8bfd9 --- /dev/null +++ b/src/plugins/renderers/rhi/jobs/jobs.pri @@ -0,0 +1,17 @@ +INCLUDEPATH += $$PWD + +SOURCES += \ + $$PWD/filtercompatibletechniquejob.cpp \ + $$PWD/materialparametergathererjob.cpp \ + $$PWD/renderviewcommandbuilderjob.cpp \ + $$PWD/renderviewcommandupdaterjob.cpp \ + $$PWD/renderviewinitializerjob.cpp \ + $$PWD/renderviewjobutils.cpp + +HEADERS += \ + $$PWD/filtercompatibletechniquejob_p.h \ + $$PWD/materialparametergathererjob_p.h \ + $$PWD/renderviewcommandbuilderjob_p.h \ + $$PWD/renderviewcommandupdaterjob_p.h \ + $$PWD/renderviewinitializerjob_p.h \ + $$PWD/renderviewjobutils_p.h diff --git a/src/plugins/renderers/rhi/jobs/materialparametergathererjob.cpp b/src/plugins/renderers/rhi/jobs/materialparametergathererjob.cpp new file mode 100644 index 000000000..c791d30f8 --- /dev/null +++ b/src/plugins/renderers/rhi/jobs/materialparametergathererjob.cpp @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Paul Lemire +** 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 "materialparametergathererjob_p.h" +#include <Qt3DRender/private/nodemanagers_p.h> +#include <Qt3DRender/private/managers_p.h> +#include <Qt3DRender/private/renderpassfilternode_p.h> +#include <Qt3DRender/private/techniquefilternode_p.h> +#include <Qt3DRender/private/job_common_p.h> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { + +namespace Render { + +namespace Rhi { + +namespace { + +int materialParameterGathererCounter = 0; +const int likelyNumberOfParameters = 24; + +} // anonymous + +MaterialParameterGathererJob::MaterialParameterGathererJob() + : Qt3DCore::QAspectJob(), + m_manager(nullptr), + m_techniqueFilter(nullptr), + m_renderPassFilter(nullptr) +{ + SET_JOB_RUN_STAT_TYPE(this, JobTypes::MaterialParameterGathering, + materialParameterGathererCounter++) +} + +// TechniqueFilter / RenderPassFilter + +// Parameters from Material/Effect/Technique + +// Note: we could maybe improve that by having a smart update when we detect +// that a parameter value has changed. That might require way more book keeping +// which might make this solution a bit too complex + +// The fact that this can now be performed in parallel should already provide a big +// improvement +void MaterialParameterGathererJob::run() +{ + for (const HMaterial &materialHandle : qAsConst(m_handles)) { + Material *material = m_manager->materialManager()->data(materialHandle); + + if (Q_UNLIKELY(!material->isEnabled())) + continue; + + Effect *effect = m_manager->effectManager()->lookupResource(material->effect()); + Technique *technique = findTechniqueForEffect(m_manager, m_techniqueFilter, effect); + + if (Q_LIKELY(technique != nullptr)) { + RenderPassList passes = + findRenderPassesForTechnique(m_manager, m_renderPassFilter, technique); + if (Q_LIKELY(passes.size() > 0)) { + // Order set: + // 1 Pass Filter + // 2 Technique Filter + // 3 Material + // 4 Effect + // 5 Technique + // 6 RenderPass + + // Add Parameters define in techniqueFilter and passFilter + // passFilter have priority over techniqueFilter + + ParameterInfoList parameters; + // Doing the reserve allows a gain of 0.5ms on some of the demo examples + parameters.reserve(likelyNumberOfParameters); + + if (m_renderPassFilter) + parametersFromParametersProvider(¶meters, m_manager->parameterManager(), + m_renderPassFilter); + if (m_techniqueFilter) + parametersFromParametersProvider(¶meters, m_manager->parameterManager(), + m_techniqueFilter); + // Get the parameters for our selected rendering setup (override what was defined in + // the technique/pass filter) + parametersFromMaterialEffectTechnique(¶meters, m_manager->parameterManager(), + material, effect, technique); + + for (RenderPass *renderPass : passes) { + ParameterInfoList globalParameters = parameters; + parametersFromParametersProvider(&globalParameters, + m_manager->parameterManager(), renderPass); + m_parameters[material->peerId()].push_back({ renderPass, globalParameters }); + } + } + } + } +} + +} // Rhi + +} // Render + +} // Qt3DRender + +QT_END_NAMESPACE diff --git a/src/plugins/renderers/rhi/jobs/materialparametergathererjob_p.h b/src/plugins/renderers/rhi/jobs/materialparametergathererjob_p.h new file mode 100644 index 000000000..f635755d7 --- /dev/null +++ b/src/plugins/renderers/rhi/jobs/materialparametergathererjob_p.h @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Paul Lemire +** 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_RHI_MATERIALPARAMETERGATHERERJOB_P_H +#define QT3DRENDER_RENDER_RHI_MATERIALPARAMETERGATHERERJOB_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <Qt3DCore/qaspectjob.h> +#include <Qt3DCore/qnodeid.h> +#include <Qt3DRender/private/handle_types_p.h> +#include <Qt3DRender/private/qt3drender_global_p.h> +#include <renderviewjobutils_p.h> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { + +namespace Render { + +class NodeManagers; +class TechniqueFilter; +class RenderPassFilter; + +namespace Rhi { +class Renderer; + +// TO be executed for each FrameGraph branch with a given RenderPassFilter/TechniqueFilter + +class Q_AUTOTEST_EXPORT MaterialParameterGathererJob : public Qt3DCore::QAspectJob +{ +public: + MaterialParameterGathererJob(); + + inline void setNodeManagers(NodeManagers *manager) Q_DECL_NOTHROW { m_manager = manager; } + inline void setTechniqueFilter(TechniqueFilter *techniqueFilter) Q_DECL_NOTHROW + { + m_techniqueFilter = techniqueFilter; + } + inline void setRenderPassFilter(RenderPassFilter *renderPassFilter) Q_DECL_NOTHROW + { + m_renderPassFilter = renderPassFilter; + } + inline const QHash<Qt3DCore::QNodeId, QVector<RenderPassParameterData>> & + materialToPassAndParameter() Q_DECL_NOTHROW + { + return m_parameters; + } + inline void setHandles(const QVector<HMaterial> &handles) Q_DECL_NOTHROW + { + m_handles = handles; + } + + inline TechniqueFilter *techniqueFilter() const Q_DECL_NOTHROW { return m_techniqueFilter; } + inline RenderPassFilter *renderPassFilter() const Q_DECL_NOTHROW { return m_renderPassFilter; } + + void run() final; + +private: + NodeManagers *m_manager; + TechniqueFilter *m_techniqueFilter; + RenderPassFilter *m_renderPassFilter; + + // Material id to array of RenderPasse with parameters + MaterialParameterGathererData m_parameters; + QVector<HMaterial> m_handles; +}; + +typedef QSharedPointer<MaterialParameterGathererJob> MaterialParameterGathererJobPtr; + +} // Rhi + +} // Render + +} // Qt3DRender + +QT_END_NAMESPACE + +#endif // QT3DRENDER_RENDER_RHI_MATERIALPARAMETERGATHERERJOB_P_H diff --git a/src/plugins/renderers/rhi/jobs/renderviewcommandbuilderjob.cpp b/src/plugins/renderers/rhi/jobs/renderviewcommandbuilderjob.cpp new file mode 100644 index 000000000..7a7520b60 --- /dev/null +++ b/src/plugins/renderers/rhi/jobs/renderviewcommandbuilderjob.cpp @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 "renderviewcommandbuilderjob_p.h" +#include <Qt3DRender/private/job_common_p.h> +#include <renderview_p.h> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { + +namespace Render { + +namespace Rhi { + +namespace { +int renderViewInstanceCounter = 0; +} // anonymous + +RenderViewCommandBuilderJob::RenderViewCommandBuilderJob() + : Qt3DCore::QAspectJob(), m_offset(0), m_count(0), m_renderView(nullptr) +{ + SET_JOB_RUN_STAT_TYPE(this, JobTypes::RenderViewCommandBuilder, renderViewInstanceCounter++) +} + +void RenderViewCommandBuilderJob::run() +{ + if (!m_renderView->noDraw()) { + if (m_count == 0) + return; + + const bool isDraw = !m_renderView->isCompute(); + if (isDraw) + m_commandData = m_renderView->buildDrawRenderCommands(m_entities, m_offset, m_count); + else + m_commandData = m_renderView->buildComputeRenderCommands(m_entities, m_offset, m_count); + } +} + +} // Rhi + +} // Render + +} // Qt3DRender + +QT_END_NAMESPACE diff --git a/src/plugins/renderers/opengl/jobs/renderviewbuilderjob_p.h b/src/plugins/renderers/rhi/jobs/renderviewcommandbuilderjob_p.h index 13e19daf7..0832772d3 100644 --- a/src/plugins/renderers/opengl/jobs/renderviewbuilderjob_p.h +++ b/src/plugins/renderers/rhi/jobs/renderviewcommandbuilderjob_p.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2019 Klaralvdalens Datakonsult AB (KDAB). +** Copyright (C) 2020 Klaralvdalens Datakonsult AB (KDAB). ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt3D module of the Qt Toolkit. @@ -37,8 +37,8 @@ ** ****************************************************************************/ -#ifndef QT3DRENDER_RENDER_OPENGL_RENDERVIEWCOMMANDBUILDERJOB_P_H -#define QT3DRENDER_RENDER_OPENGL_RENDERVIEWCOMMANDBUILDERJOB_P_H +#ifndef QT3DRENDER_RENDER_RHI_RENDERVIEWCOMMANDBUILDERJOB_P_H +#define QT3DRENDER_RENDER_RHI_RENDERVIEWCOMMANDBUILDERJOB_P_H // // W A R N I N G @@ -61,9 +61,7 @@ namespace Qt3DRender { namespace Render { -namespace OpenGL { - -class RenderView; +namespace Rhi { class Q_AUTOTEST_EXPORT RenderViewCommandBuilderJob : public Qt3DCore::QAspectJob { @@ -91,7 +89,7 @@ private: typedef QSharedPointer<RenderViewCommandBuilderJob> RenderViewCommandBuilderJobPtr; -} // OpenGL +} // Rhi } // Render @@ -99,4 +97,4 @@ typedef QSharedPointer<RenderViewCommandBuilderJob> RenderViewCommandBuilderJobP QT_END_NAMESPACE -#endif // QT3DRENDER_RENDER_OPENGL_RENDERVIEWCOMMANDBUILDERJOB_P_H +#endif // QT3DRENDER_RENDER_RHI_RENDERVIEWCOMMANDBUILDERJOB_P_H diff --git a/src/plugins/renderers/opengl/jobs/renderviewbuilderjob.cpp b/src/plugins/renderers/rhi/jobs/renderviewcommandupdaterjob.cpp index 468ab9342..442f9a7d3 100644 --- a/src/plugins/renderers/opengl/jobs/renderviewbuilderjob.cpp +++ b/src/plugins/renderers/rhi/jobs/renderviewcommandupdaterjob.cpp @@ -1,5 +1,6 @@ /**************************************************************************** ** +** Copyright (C) 2020 Klaralvdalens Datakonsult AB (KDAB). ** Copyright (C) 2016 Paul Lemire ** Contact: https://www.qt.io/licensing/ ** @@ -48,21 +49,21 @@ namespace Qt3DRender { namespace Render { -namespace OpenGL { +namespace Rhi { namespace { int renderViewInstanceCounter = 0; } // anonymous RenderViewCommandUpdaterJob::RenderViewCommandUpdaterJob() - : Qt3DCore::QAspectJob() - , m_offset(0) - , m_count(0) - , m_renderView(nullptr) - , m_renderer(nullptr) - , m_renderables(nullptr) + : Qt3DCore::QAspectJob(), + m_offset(0), + m_count(0), + m_renderView(nullptr), + m_renderer(nullptr), + m_renderables() { - SET_JOB_RUN_STAT_TYPE(this, JobTypes::RenderCommandUpdater, renderViewInstanceCounter++); + SET_JOB_RUN_STAT_TYPE(this, JobTypes::RenderCommandUpdater, renderViewInstanceCounter++) } void RenderViewCommandUpdaterJob::run() @@ -73,11 +74,11 @@ void RenderViewCommandUpdaterJob::run() if (m_count == 0) return; // Update Render Commands (Uniform Change, Depth Change) - m_renderView->updateRenderCommand(m_renderables, m_offset, m_count); + m_renderView->updateRenderCommand(m_renderables.data(), m_offset, m_count); } } -} // OpenGL +} // Rhi } // Render diff --git a/src/plugins/renderers/rhi/jobs/renderviewcommandupdaterjob_p.h b/src/plugins/renderers/rhi/jobs/renderviewcommandupdaterjob_p.h new file mode 100644 index 000000000..3b40124ad --- /dev/null +++ b/src/plugins/renderers/rhi/jobs/renderviewcommandupdaterjob_p.h @@ -0,0 +1,109 @@ +ļ»æ/**************************************************************************** +** +** Copyright (C) 2020 Klaralvdalens Datakonsult AB (KDAB). +** Copyright (C) 2016 Paul Lemire +** 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_RHI_RENDERVIEWCOMMANDUPDATEJOB_P_H +#define QT3DRENDER_RENDER_RHI_RENDERVIEWCOMMANDUPDATEJOB_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <Qt3DCore/qaspectjob.h> +#include <Qt3DRender/private/handle_types_p.h> +#include <rendercommand_p.h> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { + +namespace Render { + +namespace Rhi { + +class RenderView; +class Renderer; + +class Q_AUTOTEST_EXPORT RenderViewCommandUpdaterJob : public Qt3DCore::QAspectJob +{ +public: + RenderViewCommandUpdaterJob(); + + inline void setRenderView(RenderView *rv) Q_DECL_NOTHROW { m_renderView = rv; } + inline void setRenderer(Renderer *renderer) Q_DECL_NOTHROW { m_renderer = renderer; } + inline void setRenderables(const EntityRenderCommandDataPtr &renderables, int offset, + int count) Q_DECL_NOTHROW + { + m_offset = offset; + m_count = count; + m_renderables = renderables; + } + EntityRenderCommandDataPtr renderables() const { return m_renderables; } + + QVector<RenderCommand> &commands() Q_DECL_NOTHROW { return m_commands; } + + void run() final; + +private: + int m_offset; + int m_count; + RenderView *m_renderView; + Renderer *m_renderer; + EntityRenderCommandDataPtr m_renderables; + QVector<RenderCommand> m_commands; +}; + +typedef QSharedPointer<RenderViewCommandUpdaterJob> RenderViewCommandUpdaterJobPtr; + +} // Rhi + +} // Render + +} // Qt3DRender + +QT_END_NAMESPACE + +#endif // QT3DRENDER_RENDER_RHI_RENDERVIEWCOMMANDUPDATEJOB_P_H diff --git a/src/plugins/renderers/rhi/jobs/renderviewinitializerjob.cpp b/src/plugins/renderers/rhi/jobs/renderviewinitializerjob.cpp new file mode 100644 index 000000000..2a95cecaa --- /dev/null +++ b/src/plugins/renderers/rhi/jobs/renderviewinitializerjob.cpp @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Klaralvdalens Datakonsult AB (KDAB). +** Copyright (C) 2016 Paul Lemire +** 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 "renderviewinitializerjob_p.h" + +#include <renderview_p.h> +#include <renderer_p.h> +#include <renderviewjobutils_p.h> +#include <Qt3DRender/private/renderlogging_p.h> +#include <Qt3DRender/private/job_common_p.h> + +#include <QElapsedTimer> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { +namespace Render { +namespace Rhi { + +namespace { +// only accessed in ctor and dtor of RenderViewJob +// which are always being called in a non concurrent manner +int renderViewInstanceCounter = 0; +} // anonymous + +RenderViewInitializerJob::RenderViewInitializerJob() + : m_renderer(nullptr), + m_fgLeaf(nullptr), + m_index(0), + m_renderView(nullptr) { SET_JOB_RUN_STAT_TYPE(this, JobTypes::RenderView, + renderViewInstanceCounter++) } + + RenderViewInitializerJob::~RenderViewInitializerJob() +{ + renderViewInstanceCounter--; +} + +void RenderViewInitializerJob::run() +{ + qCDebug(Jobs) << Q_FUNC_INFO << m_index; +#if defined(QT3D_RENDER_VIEW_JOB_TIMINGS) + QElapsedTimer timer; + timer.start(); + qint64 gatherLightsTime; + qint64 buildCommandsTime; +#endif + + // Create a RenderView object + // The RenderView are created from a QFrameAllocator stored in the current Thread local storage + m_renderView = new RenderView; + + // RenderView should allocate heap resources using only the currentFrameAllocator + m_renderView->setRenderer(m_renderer); + + // Populate the renderview's configuration from the framegraph + setRenderViewConfigFromFrameGraphLeafNode(m_renderView, m_fgLeaf); +#if defined(QT3D_RENDER_VIEW_JOB_TIMINGS) + qint64 gatherStateTime = timer.nsecsElapsed(); + timer.restart(); +#endif +} + +} // namespace Rhi +} // namespace Render +} // namespace Qt3DRender + +QT_END_NAMESPACE diff --git a/src/plugins/renderers/rhi/jobs/renderviewinitializerjob_p.h b/src/plugins/renderers/rhi/jobs/renderviewinitializerjob_p.h new file mode 100644 index 000000000..fb38c8716 --- /dev/null +++ b/src/plugins/renderers/rhi/jobs/renderviewinitializerjob_p.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Paul Lemire +** Copyright (C) 2020 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_RHI_RENDERVIEWINITIALIZERJOB_H +#define QT3DRENDER_RENDER_RHI_RENDERVIEWINITIALIZERJOB_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <Qt3DCore/qaspectjob.h> +#include <Qt3DCore/private/qframeallocator_p.h> +#include <QSize> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { + +namespace Render { + +class FrameGraphNode; + +namespace Rhi { + +class Renderer; +class RenderView; + +class Q_AUTOTEST_EXPORT RenderViewInitializerJob : public Qt3DCore::QAspectJob +{ +public: + RenderViewInitializerJob(); + ~RenderViewInitializerJob(); + + inline void setRenderer(Renderer *renderer) { m_renderer = renderer; } + inline RenderView *renderView() const Q_DECL_NOTHROW { return m_renderView; } + + inline void setFrameGraphLeafNode(FrameGraphNode *fgLeaf) { m_fgLeaf = fgLeaf; } + + // Sets the position in the queue of RenderViews that the + // RenderView generated by this job should be inserted. This is + // used to ensure that for example a RenderView for creating + // a shadow map texture is submitted before the RenderView that + // contains commands making use of the shadow map + inline void setSubmitOrderIndex(int index) { m_index = index; } + inline int submitOrderIndex() const { return m_index; } + + void run() override; + +private: + Renderer *m_renderer; + FrameGraphNode *m_fgLeaf; + int m_index; + RenderView *m_renderView; +}; + +typedef QSharedPointer<RenderViewInitializerJob> RenderViewInitializerJobPtr; + +} // namespace Rhi +} // namespace Render +} // namespace Qt3DRender + +QT_END_NAMESPACE + +#endif // QT3DRENDER_RENDER_RHI_RENDERVIEWINITIALIZERJOB_H diff --git a/src/plugins/renderers/rhi/jobs/renderviewjobutils.cpp b/src/plugins/renderers/rhi/jobs/renderviewjobutils.cpp new file mode 100644 index 000000000..c9a37c5e1 --- /dev/null +++ b/src/plugins/renderers/rhi/jobs/renderviewjobutils.cpp @@ -0,0 +1,583 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 "renderviewjobutils_p.h" +#include <Qt3DRender/private/renderlogging_p.h> + +#include <Qt3DRender/qgraphicsapifilter.h> +#include <Qt3DRender/private/sphere_p.h> +#include <Qt3DRender/qshaderdata.h> + +#include <Qt3DRender/private/cameraselectornode_p.h> +#include <Qt3DRender/private/clearbuffers_p.h> +#include <Qt3DRender/private/layerfilternode_p.h> +#include <Qt3DRender/private/nodemanagers_p.h> +#include <Qt3DRender/private/effect_p.h> +#include <Qt3DRender/private/renderpassfilternode_p.h> +#include <Qt3DRender/private/rendertargetselectornode_p.h> +#include <Qt3DRender/private/sortpolicy_p.h> +#include <Qt3DRender/private/techniquefilternode_p.h> +#include <Qt3DRender/private/viewportnode_p.h> +#include <Qt3DRender/private/managers_p.h> +#include <Qt3DRender/private/shaderdata_p.h> +#include <Qt3DRender/private/statesetnode_p.h> +#include <Qt3DRender/private/dispatchcompute_p.h> +#include <Qt3DRender/private/rendersurfaceselector_p.h> +#include <Qt3DRender/private/rendercapture_p.h> +#include <Qt3DRender/private/buffercapture_p.h> +#include <Qt3DRender/private/stringtoint_p.h> +#include <Qt3DRender/private/techniquemanager_p.h> +#include <Qt3DRender/private/memorybarrier_p.h> +#include <Qt3DRender/private/blitframebuffer_p.h> +#include <Qt3DRender/private/waitfence_p.h> +#include <Qt3DRender/private/renderstateset_p.h> +#include <renderview_p.h> +#include <shadervariables_p.h> + +QT_BEGIN_NAMESPACE + +using namespace Qt3DCore; + +namespace Qt3DRender { +namespace Render { +namespace Rhi { + +/*! + \internal + Walks up the framegraph tree from \a fgLeaf and builds up as much state + as possible and populates \a rv. For cases where we can't get the specific state + (e.g. because it depends upon more than just the framegraph) we store the data from + the framegraph that will be needed to later when the rest of the data becomes available +*/ +void setRenderViewConfigFromFrameGraphLeafNode(RenderView *rv, const FrameGraphNode *fgLeaf) +{ + // The specific RenderPass to be used is also dependent upon the Effect and TechniqueFilter + // which is referenced by the Material which is referenced by the RenderMesh. So we can + // only store the filter info in the RenderView structure and use it to do the resolving + // when we build the RenderCommand list. + const NodeManagers *manager = rv->nodeManagers(); + const FrameGraphNode *node = fgLeaf; + + while (node) { + FrameGraphNode::FrameGraphNodeType type = node->nodeType(); + if (node->isEnabled()) + switch (type) { + case FrameGraphNode::InvalidNodeType: + // A base FrameGraphNode, can be used for grouping purposes + break; + case FrameGraphNode::CameraSelector: + // Can be set only once and we take camera nearest to the leaf node + if (!rv->renderCameraLens()) { + const CameraSelector *cameraSelector = + static_cast<const CameraSelector *>(node); + Entity *camNode = manager->renderNodesManager()->lookupResource( + cameraSelector->cameraUuid()); + if (camNode) { + CameraLens *lens = camNode->renderComponent<CameraLens>(); + rv->setRenderCameraEntity(camNode); + if (lens && lens->isEnabled()) { + rv->setRenderCameraLens(lens); + // ViewMatrix and ProjectionMatrix are computed + // later in updateMatrices() + // since at this point the transformation matrices + // may not yet have been updated + } + } + } + break; + + case FrameGraphNode::LayerFilter: // Can be set multiple times in the tree + rv->appendLayerFilter(static_cast<const LayerFilterNode *>(node)->peerId()); + break; + + case FrameGraphNode::ProximityFilter: // Can be set multiple times in the tree + rv->appendProximityFilterId(node->peerId()); + break; + + case FrameGraphNode::RenderPassFilter: + // Can be set once + // TODO: Amalgamate all render pass filters from leaf to root + if (!rv->renderPassFilter()) + rv->setRenderPassFilter(static_cast<const RenderPassFilter *>(node)); + break; + + case FrameGraphNode::RenderTarget: { + // Can be set once and we take render target nearest to the leaf node + const RenderTargetSelector *targetSelector = + static_cast<const RenderTargetSelector *>(node); + QNodeId renderTargetUid = targetSelector->renderTargetUuid(); + HTarget renderTargetHandle = + manager->renderTargetManager()->lookupHandle(renderTargetUid); + + // Add renderTarget Handle and build renderCommand AttachmentPack + if (!rv->renderTargetId()) { + rv->setRenderTargetId(renderTargetUid); + + RenderTarget *renderTarget = + manager->renderTargetManager()->data(renderTargetHandle); + if (renderTarget) + rv->setAttachmentPack(AttachmentPack(renderTarget, + manager->attachmentManager(), + targetSelector->outputs())); + } + break; + } + + case FrameGraphNode::ClearBuffers: { + const ClearBuffers *cbNode = static_cast<const ClearBuffers *>(node); + rv->addClearBuffers(cbNode); + break; + } + + case FrameGraphNode::TechniqueFilter: + // Can be set once + // TODO Amalgamate all technique filters from leaf to root + if (!rv->techniqueFilter()) + rv->setTechniqueFilter(static_cast<const TechniqueFilter *>(node)); + break; + + case FrameGraphNode::Viewport: { + // If the Viewport has already been set in a lower node + // Make it so that the new viewport is actually + // a subregion relative to that of the parent viewport + const ViewportNode *vpNode = static_cast<const ViewportNode *>(node); + rv->setViewport(ViewportNode::computeViewport(rv->viewport(), vpNode)); + rv->setGamma(vpNode->gamma()); + break; + } + + case FrameGraphNode::SortMethod: { + const Render::SortPolicy *sortPolicy = + static_cast<const Render::SortPolicy *>(node); + rv->addSortType(sortPolicy->sortTypes()); + break; + } + + case FrameGraphNode::SubtreeEnabler: + // Has no meaning here. SubtreeEnabler was used + // in a prior step to filter the list of RenderViewJobs + break; + + case FrameGraphNode::StateSet: { + const Render::StateSetNode *rStateSet = + static_cast<const Render::StateSetNode *>(node); + // Create global RenderStateSet for renderView if no stateSet was set before + RenderStateSet *stateSet = rv->stateSet(); + if (stateSet == nullptr && rStateSet->hasRenderStates()) { + stateSet = new RenderStateSet(); + rv->setStateSet(stateSet); + } + + // Add states from new stateSet we might be missing + // but don' t override existing states (lower StateSetNode always has priority) + if (rStateSet->hasRenderStates()) + addStatesToRenderStateSet(stateSet, rStateSet->renderStates(), + manager->renderStateManager()); + break; + } + + case FrameGraphNode::NoDraw: { + rv->setNoDraw(true); + break; + } + + case FrameGraphNode::FrustumCulling: { + rv->setFrustumCulling(true); + break; + } + + case FrameGraphNode::ComputeDispatch: { + const Render::DispatchCompute *dispatchCompute = + static_cast<const Render::DispatchCompute *>(node); + rv->setCompute(true); + rv->setComputeWorkgroups(dispatchCompute->x(), dispatchCompute->y(), + dispatchCompute->z()); + break; + } + + case FrameGraphNode::Lighting: { + // TODO + break; + } + + case FrameGraphNode::Surface: { + // Use the surface closest to leaf node + if (rv->surface() == nullptr) { + const Render::RenderSurfaceSelector *surfaceSelector = + static_cast<const Render::RenderSurfaceSelector *>(node); + rv->setSurface(surfaceSelector->surface()); + rv->setSurfaceSize(surfaceSelector->renderTargetSize() + * surfaceSelector->devicePixelRatio()); + rv->setDevicePixelRatio(surfaceSelector->devicePixelRatio()); + } + break; + } + case FrameGraphNode::RenderCapture: { + auto *renderCapture = const_cast<Render::RenderCapture *>( + static_cast<const Render::RenderCapture *>(node)); + if (rv->renderCaptureNodeId().isNull() && renderCapture->wasCaptureRequested()) { + rv->setRenderCaptureNodeId(renderCapture->peerId()); + rv->setRenderCaptureRequest(renderCapture->takeCaptureRequest()); + } + break; + } + + case FrameGraphNode::MemoryBarrier: { + // Not available in rhi + break; + } + + case FrameGraphNode::BufferCapture: { + auto *bufferCapture = const_cast<Render::BufferCapture *>( + static_cast<const Render::BufferCapture *>(node)); + if (bufferCapture != nullptr) + rv->setIsDownloadBuffersEnable(bufferCapture->isEnabled()); + break; + } + + case FrameGraphNode::BlitFramebuffer: { + const Render::BlitFramebuffer *blitFramebufferNode = + static_cast<const Render::BlitFramebuffer *>(node); + rv->setHasBlitFramebufferInfo(true); + BlitFramebufferInfo bfbInfo; + bfbInfo.sourceRenderTargetId = blitFramebufferNode->sourceRenderTargetId(); + bfbInfo.destinationRenderTargetId = + blitFramebufferNode->destinationRenderTargetId(); + bfbInfo.sourceRect = blitFramebufferNode->sourceRect(); + bfbInfo.destinationRect = blitFramebufferNode->destinationRect(); + bfbInfo.sourceAttachmentPoint = blitFramebufferNode->sourceAttachmentPoint(); + bfbInfo.destinationAttachmentPoint = + blitFramebufferNode->destinationAttachmentPoint(); + bfbInfo.interpolationMethod = blitFramebufferNode->interpolationMethod(); + rv->setBlitFrameBufferInfo(bfbInfo); + break; + } + + case FrameGraphNode::WaitFence: { + // Not available in rhi + break; + } + + case FrameGraphNode::SetFence: { + // Not available in rhi + break; + } + + case FrameGraphNode::NoPicking: + // Nothing to do RenderView wise for NoPicking + break; + + default: + // Should never get here + qCWarning(Backend) << "Unhandled FrameGraphNode type"; + } + + node = node->parent(); + } +} + +/*! + \internal + Searches the best matching Technique from \a effect specified. +*/ +Technique *findTechniqueForEffect(NodeManagers *manager, const TechniqueFilter *techniqueFilter, + Effect *effect) +{ + if (!effect) + return nullptr; + + QVector<Technique *> matchingTechniques; + const bool hasInvalidTechniqueFilter = + (techniqueFilter == nullptr || techniqueFilter->filters().isEmpty()); + + // Iterate through the techniques in the effect + const auto techniqueIds = effect->techniques(); + for (const QNodeId techniqueId : techniqueIds) { + Technique *technique = manager->techniqueManager()->lookupResource(techniqueId); + + // Should be valid, if not there likely a problem with node addition/destruction changes + Q_ASSERT(technique); + + // Check if the technique is compatible with the rendering API + // If no techniqueFilter is present, we return the technique as it satisfies OpenGL version + if (technique->isCompatibleWithRenderer() + && (hasInvalidTechniqueFilter + || technique->isCompatibleWithFilters(techniqueFilter->filters()))) + matchingTechniques.append(technique); + } + + if (matchingTechniques.size() == 0) // We failed to find a suitable technique to use :( + return nullptr; + + if (matchingTechniques.size() == 1) + return matchingTechniques.first(); + + // Several compatible techniques, return technique with highest major and minor version + Technique *highest = matchingTechniques.first(); + GraphicsApiFilterData filter = *highest->graphicsApiFilter(); + for (auto it = matchingTechniques.cbegin() + 1; it < matchingTechniques.cend(); ++it) { + if (filter < *(*it)->graphicsApiFilter()) { + filter = *(*it)->graphicsApiFilter(); + highest = *it; + } + } + return highest; +} + +RenderPassList findRenderPassesForTechnique(NodeManagers *manager, + const RenderPassFilter *passFilter, + Technique *technique) +{ + Q_ASSERT(manager); + Q_ASSERT(technique); + + RenderPassList passes; + const auto passIds = technique->renderPasses(); + for (const QNodeId passId : passIds) { + RenderPass *renderPass = manager->renderPassManager()->lookupResource(passId); + + if (renderPass && renderPass->isEnabled()) { + bool foundMatch = (!passFilter || passFilter->filters().size() == 0); + + // A pass filter is present so we need to check for matching criteria + if (!foundMatch && renderPass->filterKeys().size() >= passFilter->filters().size()) { + + // Iterate through the filter criteria and look for render passes with criteria that + // satisfy them + const auto filterKeyIds = passFilter->filters(); + for (const QNodeId filterKeyId : filterKeyIds) { + foundMatch = false; + FilterKey *filterFilterKey = + manager->filterKeyManager()->lookupResource(filterKeyId); + + const auto passFilterKeyIds = renderPass->filterKeys(); + for (const QNodeId passFilterKeyId : passFilterKeyIds) { + FilterKey *passFilterKey = + manager->filterKeyManager()->lookupResource(passFilterKeyId); + if ((foundMatch = (*passFilterKey == *filterFilterKey))) + break; + } + + if (!foundMatch) { + // No match for criterion in any of the render pass' criteria + break; + } + } + } + + if (foundMatch) { + // Found a renderpass that satisfies our needs. Add it in order + passes << renderPass; + } + } + } + + return passes; +} + +ParameterInfoList::const_iterator findParamInfo(ParameterInfoList *params, const int nameId) +{ + const ParameterInfoList::const_iterator end = params->cend(); + ParameterInfoList::const_iterator it = std::lower_bound(params->cbegin(), end, nameId); + if (it != end && it->nameId != nameId) + return end; + return it; +} + +void addParametersForIds(ParameterInfoList *params, ParameterManager *manager, + const Qt3DCore::QNodeIdVector ¶meterIds) +{ + for (const QNodeId paramId : parameterIds) { + const HParameter parameterHandle = manager->lookupHandle(paramId); + const Parameter *param = manager->data(parameterHandle); + ParameterInfoList::iterator it = + std::lower_bound(params->begin(), params->end(), param->nameId()); + if (it == params->end() || it->nameId != param->nameId()) + params->insert(it, ParameterInfo(param->nameId(), parameterHandle)); + } +} + +void parametersFromMaterialEffectTechnique(ParameterInfoList *infoList, ParameterManager *manager, + Material *material, Effect *effect, Technique *technique) +{ + // The parameters are taken in the following priority order: + // + // 1) Material + // 2) Effect + // 3) Technique + // + // That way a user can override defaults in Effect's and Techniques on a + // object manner and a Technique can override global defaults from the Effect. + parametersFromParametersProvider(infoList, manager, material); + parametersFromParametersProvider(infoList, manager, effect); + parametersFromParametersProvider(infoList, manager, technique); +} + +// Only add states with types we don't already have +void addStatesToRenderStateSet(RenderStateSet *stateSet, const QVector<Qt3DCore::QNodeId> &stateIds, + RenderStateManager *manager) +{ + for (const Qt3DCore::QNodeId &stateId : stateIds) { + RenderStateNode *node = manager->lookupResource(stateId); + if (node->isEnabled() && stateSet->canAddStateOfType(node->type())) { + stateSet->addState(node->impl()); + } + } +} + +namespace { + +const QString blockArray = QStringLiteral("[%1]"); +const int qNodeIdTypeId = qMetaTypeId<QNodeId>(); + +} + +UniformBlockValueBuilder::UniformBlockValueBuilder() + : updatedPropertiesOnly(false), shaderDataManager(nullptr), textureManager(nullptr) +{ +} + +UniformBlockValueBuilder::~UniformBlockValueBuilder() { } + +void UniformBlockValueBuilder::buildActiveUniformNameValueMapHelper( + const ShaderData *currentShaderData, const QString &blockName, + const QString &qmlPropertyName, const QVariant &value) +{ + // In the end, values are either scalar or a scalar array + // Composed elements (structs, structs array) are simplified into simple scalars + if (value.userType() == QMetaType::QVariantList) { // Array + QVariantList list = value.value<QVariantList>(); + if (list.at(0).userType() + == qNodeIdTypeId) { // Array of struct qmlPropertyName[i].structMember + for (int i = 0; i < list.size(); ++i) { + const QVariant &variantElement = list.at(i); + if (list.at(i).userType() == qNodeIdTypeId) { + const auto nodeId = variantElement.value<QNodeId>(); + ShaderData *subShaderData = shaderDataManager->lookupResource(nodeId); + if (subShaderData) { + buildActiveUniformNameValueMapStructHelper( + subShaderData, + blockName + QLatin1Char('.') + qmlPropertyName + blockArray.arg(i), + QLatin1String("")); + } + // Note: we only handle ShaderData as nested container nodes here + } + } + } else { // Array of scalar/vec qmlPropertyName[0] + QString varName; + varName.reserve(blockName.length() + 1 + qmlPropertyName.length() + 3); + varName.append(blockName); + varName.append(QLatin1String(".")); + varName.append(qmlPropertyName); + varName.append(QLatin1String("[0]")); + if (uniforms.contains(varName)) { + qCDebug(Shaders) << "UBO array member " << varName << " set for update"; + activeUniformNamesToValue.insert(StringToInt::lookupId(varName), value); + } + } + } else if (value.userType() == qNodeIdTypeId) { // Struct qmlPropertyName.structMember + const auto nodeId = value.value<QNodeId>(); + ShaderData *rSubShaderData = shaderDataManager->lookupResource(nodeId); + if (rSubShaderData) { + buildActiveUniformNameValueMapStructHelper(rSubShaderData, blockName, qmlPropertyName); + } else if (textureManager->contains(nodeId)) { + const auto varId = + StringToInt::lookupId(blockName + QLatin1Char('.') + qmlPropertyName); + activeUniformNamesToValue.insert(varId, value); + } + } else { // Scalar / Vec + QString varName; + varName.reserve(blockName.length() + 1 + qmlPropertyName.length()); + varName.append(blockName); + varName.append(QLatin1String(".")); + varName.append(qmlPropertyName); + if (uniforms.contains(varName)) { + qCDebug(Shaders) << "UBO scalar member " << varName << " set for update"; + + // If the property needs to be transformed, we transform it here as + // the shaderdata cannot hold transformed properties for multiple + // thread contexts at once + activeUniformNamesToValue.insert( + StringToInt::lookupId(varName), + currentShaderData->getTransformedProperty(qmlPropertyName, viewMatrix)); + } + } +} + +void UniformBlockValueBuilder::buildActiveUniformNameValueMapStructHelper( + const ShaderData *rShaderData, const QString &blockName, const QString &qmlPropertyName) +{ + const QHash<QString, ShaderData::PropertyValue> &properties = rShaderData->properties(); + auto it = properties.begin(); + const auto end = properties.end(); + + while (it != end) { + QString fullBlockName; + fullBlockName.reserve(blockName.length() + 1 + qmlPropertyName.length()); + fullBlockName.append(blockName); + if (!qmlPropertyName.isEmpty()) { + fullBlockName.append(QLatin1String(".")); + fullBlockName.append(qmlPropertyName); + } + buildActiveUniformNameValueMapHelper(rShaderData, fullBlockName, it.key(), + it.value().value); + ++it; + } +} + +ParameterInfo::ParameterInfo(const int nameId, const HParameter &handle) + : nameId(nameId), handle(handle) +{ +} + +bool ParameterInfo::operator<(const ParameterInfo &other) const Q_DECL_NOEXCEPT +{ + return nameId < other.nameId; +} + +bool ParameterInfo::operator<(const int otherNameId) const Q_DECL_NOEXCEPT +{ + return nameId < otherNameId; +} + +} // namespace Rhi +} // namespace Render +} // namespace Qt3DRender + +QT_END_NAMESPACE diff --git a/src/plugins/renderers/rhi/jobs/renderviewjobutils_p.h b/src/plugins/renderers/rhi/jobs/renderviewjobutils_p.h new file mode 100644 index 000000000..31bc29b8d --- /dev/null +++ b/src/plugins/renderers/rhi/jobs/renderviewjobutils_p.h @@ -0,0 +1,189 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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_RHI_RENDERVIEWJOBUTILS_P_H +#define QT3DRENDER_RENDER_RHI_RENDERVIEWJOBUTILS_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 <Qt3DCore/qnodeid.h> +#include <QtCore/qhash.h> +#include <QtCore/qvariant.h> +#include <QMatrix4x4> +#include <Qt3DRender/private/uniform_p.h> +#include <Qt3DRender/private/handle_types_p.h> +#include <Qt3DRender/private/aligned_malloc_p.h> +#include <shadervariables_p.h> + +QT_BEGIN_NAMESPACE + +namespace Qt3DCore { +class QFrameAllocator; +} + +namespace Qt3DRender { +namespace Render { + +class FrameGraphNode; +class ParameterManager; +class Effect; +class Entity; +class Material; +class RenderPass; +class Technique; +class TechniqueFilter; +class RenderPassFilter; +class NodeManagers; +class ShaderDataManager; +class ShaderData; +class TextureManager; +class RenderStateManager; +class RenderStateCollection; +class RenderStateSet; + +namespace Rhi { +class Renderer; +class RenderView; +struct ShaderUniform; + +Q_AUTOTEST_EXPORT void setRenderViewConfigFromFrameGraphLeafNode(RenderView *rv, + const FrameGraphNode *fgLeaf); + +Q_AUTOTEST_EXPORT Technique *findTechniqueForEffect(NodeManagers *manager, + const TechniqueFilter *techniqueFilter, + Effect *effect); + +typedef QVarLengthArray<RenderPass *, 4> RenderPassList; +Q_AUTOTEST_EXPORT RenderPassList findRenderPassesForTechnique(NodeManagers *manager, + const RenderPassFilter *passFilter, + Technique *technique); + +// Extracts the type T from a QVariant v without using QVariant::value which is slow +// Note: Assumes you are 100% sure about the type you requested +template<typename T> +inline T variant_value(const QVariant &v) +{ + return *reinterpret_cast<const T *>(v.data()); +} + +struct ParameterInfo +{ + explicit ParameterInfo(const int nameId = -1, const HParameter &handle = HParameter()); + + int nameId; + HParameter handle; + + bool operator<(const int otherNameId) const Q_DECL_NOEXCEPT; + bool operator<(const ParameterInfo &other) const Q_DECL_NOEXCEPT; +}; +typedef QVector<ParameterInfo> ParameterInfoList; + +struct RenderPassParameterData +{ + RenderPass *pass; + ParameterInfoList parameterInfo; +}; +QT3D_DECLARE_TYPEINFO_3(Qt3DRender, Render, Rhi, RenderPassParameterData, Q_MOVABLE_TYPE) + +using MaterialParameterGathererData = QHash<Qt3DCore::QNodeId, QVector<RenderPassParameterData>>; + +Q_AUTOTEST_EXPORT void parametersFromMaterialEffectTechnique(ParameterInfoList *infoList, + ParameterManager *manager, + Material *material, Effect *effect, + Technique *technique); + +Q_AUTOTEST_EXPORT void addParametersForIds(ParameterInfoList *params, ParameterManager *manager, + const QVector<Qt3DCore::QNodeId> ¶meterIds); + +template<class T> +void parametersFromParametersProvider(ParameterInfoList *infoList, ParameterManager *manager, + T *provider) +{ + addParametersForIds(infoList, manager, provider->parameters()); +} + +Q_AUTOTEST_EXPORT ParameterInfoList::const_iterator findParamInfo(ParameterInfoList *infoList, + const int nameId); + +Q_AUTOTEST_EXPORT void addStatesToRenderStateSet(RenderStateSet *stateSet, + const QVector<Qt3DCore::QNodeId> &stateIds, + RenderStateManager *manager); + +typedef QHash<int, QVariant> UniformBlockValueBuilderHash; + +struct Q_AUTOTEST_EXPORT UniformBlockValueBuilder +{ + UniformBlockValueBuilder(); + ~UniformBlockValueBuilder(); + + QT3D_ALIGNED_MALLOC_AND_FREE() + + void buildActiveUniformNameValueMapHelper(const ShaderData *currentShaderData, + const QString &blockName, + const QString &qmlPropertyName, + const QVariant &value); + void buildActiveUniformNameValueMapStructHelper(const ShaderData *rShaderData, + const QString &blockName, + const QString &qmlPropertyName = QString()); + + bool updatedPropertiesOnly; + QSet<QString> uniforms; + UniformBlockValueBuilderHash activeUniformNamesToValue; + ShaderDataManager *shaderDataManager; + TextureManager *textureManager; + Matrix4x4 viewMatrix; +}; + +} // namespace Rhi +} // namespace Render +} // namespace Qt3DRender + +QT_END_NAMESPACE + +#endif // QT3DRENDER_RENDER_RHI_RENDERVIEWJOBUTILS_P_H diff --git a/src/plugins/renderers/rhi/main.cpp b/src/plugins/renderers/rhi/main.cpp new file mode 100644 index 000000000..47efc7530 --- /dev/null +++ b/src/plugins/renderers/rhi/main.cpp @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 <Qt3DRender/private/qrendererplugin_p.h> +#include <renderer_p.h> + +QT_BEGIN_NAMESPACE + +class RhiRendererPlugin : public Qt3DRender::Render::QRendererPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QRendererPluginFactoryInterface_iid FILE "rhirenderer.json") + + Qt3DRender::Render::AbstractRenderer * + create(const QString &key, Qt3DRender::QRenderAspect::RenderType renderMode) override + { + Q_UNUSED(key) + return new Qt3DRender::Render::Rhi::Renderer(renderMode); + } +}; + +QT_END_NAMESPACE + +#include "main.moc" diff --git a/src/plugins/renderers/rhi/managers/managers.pri b/src/plugins/renderers/rhi/managers/managers.pri new file mode 100644 index 000000000..de3f29b30 --- /dev/null +++ b/src/plugins/renderers/rhi/managers/managers.pri @@ -0,0 +1,8 @@ +INCLUDEPATH += $$PWD + +HEADERS += \ + $$PWD/rhihandle_types_p.h \ + $$PWD/rhiresourcemanagers_p.h + +SOURCES += \ + $$PWD/rhiresourcemanagers.cpp diff --git a/src/plugins/renderers/rhi/managers/rhihandle_types_p.h b/src/plugins/renderers/rhi/managers/rhihandle_types_p.h new file mode 100644 index 000000000..2e74131e1 --- /dev/null +++ b/src/plugins/renderers/rhi/managers/rhihandle_types_p.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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_RHI_RHIHANDLE_TYPES_P_H +#define QT3DRENDER_RENDER_RHI_RHIHANDLE_TYPES_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <Qt3DCore/private/qhandle_p.h> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { + +namespace Render { + +namespace Rhi { + +class RHIBuffer; +class RHITexture; +class RHIGraphicsPipeline; + +typedef Qt3DCore::QHandle<RHIBuffer> HRHIBuffer; +typedef Qt3DCore::QHandle<RHITexture> HRHITexture; +typedef Qt3DCore::QHandle<RHIGraphicsPipeline> HRHIGraphicsPipeline; + +} // namespace Rhi + +} // namespace Render + +} // namespace Qt3DRender + +#if defined(_MSC_VER) +#define RHI_UNIMPLEMENTED // do { qDebug() << "Unimplemented: " << __FUNCSIG__; } while (0) +#else +#define RHI_UNIMPLEMENTED // do { qDebug() << "Unimplemented: " << __PRETTY_FUNCTION__; } while (0) +#endif +QT_END_NAMESPACE + +#endif // QT3DRENDER_RENDER_RHI_RHIHANDLE_TYPES_P_H diff --git a/src/plugins/renderers/rhi/managers/rhiresourcemanagers.cpp b/src/plugins/renderers/rhi/managers/rhiresourcemanagers.cpp new file mode 100644 index 000000000..3e813250b --- /dev/null +++ b/src/plugins/renderers/rhi/managers/rhiresourcemanagers.cpp @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 "rhiresourcemanagers_p.h" + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { + +namespace Render { + +namespace Rhi { + +RHIResourceManagers::RHIResourceManagers() + : m_rhiBufferManager(new RHIBufferManager()), + m_rhiShaderManager(new RHIShaderManager()), + m_rhiTextureManager(new RHITextureManager()), + m_rhiGraphicsPipelineManager(new RHIGraphicsPipelineManager()) +{ +} + +RHIResourceManagers::~RHIResourceManagers() +{ + delete m_rhiTextureManager; + delete m_rhiShaderManager; + delete m_rhiBufferManager; + delete m_rhiGraphicsPipelineManager; +} + +void RHIResourceManagers::releaseAllResources() +{ + auto releaseAll = [](auto *manager) noexcept { + const auto handles = manager->activeHandles(); + for (const auto &handle : handles) { + manager->release(handle); + } + }; + + releaseAll(m_rhiTextureManager); + releaseAll(m_rhiBufferManager); + // releaseAll(m_rhiShaderManager); + releaseAll(m_rhiGraphicsPipelineManager); +} + +} // Rhi + +} // Render + +} // Qt3DRender + +QT_END_NAMESPACE diff --git a/src/plugins/renderers/rhi/managers/rhiresourcemanagers_p.h b/src/plugins/renderers/rhi/managers/rhiresourcemanagers_p.h new file mode 100644 index 000000000..34758530d --- /dev/null +++ b/src/plugins/renderers/rhi/managers/rhiresourcemanagers_p.h @@ -0,0 +1,153 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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_RHI_RHIRESOURCEMANAGERS_P_H +#define QT3DRENDER_RENDER_RHI_RHIRESOURCEMANAGERS_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/private/qt3drender_global_p.h> +#include <Qt3DCore/private/qresourcemanager_p.h> +#include <texture_p.h> +#include <rhibuffer_p.h> +#include <rhishader_p.h> +#include <rhigraphicspipeline_p.h> +#include <Qt3DRender/private/apishadermanager_p.h> +#include <Qt3DRender/private/renderstateset_p.h> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { + +namespace Render { + +namespace Rhi { + +class Q_AUTOTEST_EXPORT RHIBufferManager + : public Qt3DCore::QResourceManager<RHIBuffer, Qt3DCore::QNodeId, Qt3DCore::NonLockingPolicy> +{ +}; + +class Q_AUTOTEST_EXPORT RHITextureManager + : public Qt3DCore::QResourceManager<RHITexture, Qt3DCore::QNodeId, Qt3DCore::NonLockingPolicy> +{ +public: + QHash<RHITexture *, Qt3DCore::QNodeId> texNodeIdForRHITexture; +}; + +class Q_AUTOTEST_EXPORT RHIShaderManager : public APIShaderManager<RHIShader> +{ +public: + explicit RHIShaderManager() : APIShaderManager<RHIShader>() { } +}; + +// Geometry | Shader | RenderStateMask +struct GraphicsPipelineIdentifier +{ + HGeometry geometry; + Qt3DCore::QNodeId shader; + int renderViewIndex; +}; + +class Q_AUTOTEST_EXPORT RHIGraphicsPipelineManager + : public Qt3DCore::QResourceManager<RHIGraphicsPipeline, GraphicsPipelineIdentifier, + Qt3DCore::NonLockingPolicy> +{ +public: + RHIGraphicsPipelineManager() { } +}; + +class Q_AUTOTEST_EXPORT RHIResourceManagers +{ +public: + RHIResourceManagers(); + ~RHIResourceManagers(); + + inline RHIShaderManager *rhiShaderManager() const noexcept { return m_rhiShaderManager; } + inline RHITextureManager *rhiTextureManager() const noexcept { return m_rhiTextureManager; } + inline RHIBufferManager *rhiBufferManager() const noexcept { return m_rhiBufferManager; } + inline RHIGraphicsPipelineManager *rhiGraphicsPipelineManager() const noexcept + { + return m_rhiGraphicsPipelineManager; + } + + void releaseAllResources(); + +private: + RHIBufferManager *m_rhiBufferManager; + RHIShaderManager *m_rhiShaderManager; + RHITextureManager *m_rhiTextureManager; + RHIGraphicsPipelineManager *m_rhiGraphicsPipelineManager; +}; + +inline uint qHash(const GraphicsPipelineIdentifier &key, uint seed) +{ + const QPair<HGeometry, Qt3DCore::QNodeId> p = { key.geometry, key.shader }; + using QT_PREPEND_NAMESPACE(qHash); + return qHash(p, seed) + qHash(key.renderViewIndex, seed); +} + +inline bool operator==(const GraphicsPipelineIdentifier &a, const GraphicsPipelineIdentifier &b) +{ + return a.geometry == b.geometry && a.shader == b.shader + && a.renderViewIndex == b.renderViewIndex; +} + +} // Rhi + +} // Render + +} // Qt3DRender + +Q_DECLARE_RESOURCE_INFO(Qt3DRender::Render::Rhi::RHIGraphicsPipeline, Q_REQUIRES_CLEANUP) +Q_DECLARE_RESOURCE_INFO(Qt3DRender::Render::Rhi::RHITexture, Q_REQUIRES_CLEANUP) +Q_DECLARE_RESOURCE_INFO(Qt3DRender::Render::Rhi::RHIBuffer, Q_REQUIRES_CLEANUP) +Q_DECLARE_RESOURCE_INFO(Qt3DRender::Render::Rhi::RHIShader, Q_REQUIRES_CLEANUP) +QT_END_NAMESPACE + +#endif // QT3DRENDER_RENDER_RHI_RHIRESOURCEMANAGERS_P_H diff --git a/src/plugins/renderers/rhi/renderer/commandexecuter.cpp b/src/plugins/renderers/rhi/renderer/commandexecuter.cpp new file mode 100644 index 000000000..cfa6531be --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/commandexecuter.cpp @@ -0,0 +1,400 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Klaralvdalens Datakonsult AB (KDAB). +** Copyright (C) 2016 Paul Lemire <paul.lemire350@gmail.com> +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "commandexecuter_p.h" + +#include <Qt3DCore/private/qabstractaspect_p.h> +#include <Qt3DCore/qbackendnode.h> +#include <Qt3DRender/private/nodemanagers_p.h> +#include <Qt3DRender/private/geometryrenderermanager_p.h> +#include <Qt3DRender/private/stringtoint_p.h> +#include <QJsonObject> +#include <QJsonDocument> +#include <QJsonArray> +#include <submissioncontext_p.h> +#include <renderview_p.h> +#include <rendercommand_p.h> +#include <renderer_p.h> +#include <submissioncontext_p.h> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { + +namespace Debug { + +namespace { + +template<typename Type> +QJsonObject typeToJsonObj(const Type &) noexcept +{ + return QJsonObject(); +} + +template<typename Type> +QJsonValue typeToJsonValue(const Type &t) noexcept +{ + Q_UNUSED(t); + return QJsonValue(); +} + +template<> +QJsonObject typeToJsonObj<QRectF>(const QRectF &rect) noexcept +{ + QJsonObject obj; + + obj.insert(QLatin1String("x"), rect.x()); + obj.insert(QLatin1String("y"), rect.y()); + obj.insert(QLatin1String("width"), rect.width()); + obj.insert(QLatin1String("height"), rect.height()); + + return obj; +} + +template<> +QJsonValue typeToJsonValue<QRectF>(const QRectF &rect) noexcept +{ + QJsonArray value; + + value.push_back(rect.x()); + value.push_back(rect.y()); + value.push_back(rect.width()); + value.push_back(rect.height()); + + return value; +} + +template<> +QJsonObject typeToJsonObj<QSize>(const QSize &s) noexcept +{ + QJsonObject obj; + + obj.insert(QLatin1String("width"), s.width()); + obj.insert(QLatin1String("height"), s.height()); + + return obj; +} + +template<> +QJsonValue typeToJsonValue<QSize>(const QSize &s) noexcept +{ + QJsonArray value; + + value.push_back(s.width()); + value.push_back(s.height()); + + return value; +} + +template<> +QJsonObject typeToJsonObj<QVector3D>(const QVector3D &v) noexcept +{ + QJsonObject obj; + + obj.insert(QLatin1String("x"), v.x()); + obj.insert(QLatin1String("y"), v.y()); + obj.insert(QLatin1String("z"), v.z()); + + return obj; +} + +template<> +QJsonValue typeToJsonValue<QVector3D>(const QVector3D &v) noexcept +{ + QJsonArray value; + + value.push_back(v.x()); + value.push_back(v.y()); + value.push_back(v.z()); + + return value; +} + +template<> +QJsonObject typeToJsonObj<Qt3DCore::QNodeId>(const Qt3DCore::QNodeId &v) noexcept +{ + QJsonObject obj; + obj.insert(QLatin1String("id"), qint64(v.id())); + return obj; +} + +template<> +QJsonValue typeToJsonValue<Qt3DCore::QNodeId>(const Qt3DCore::QNodeId &v) noexcept +{ + QJsonValue value(qint64(v.id())); + return value; +} + +template<> +QJsonObject typeToJsonObj<QVector4D>(const QVector4D &v) noexcept +{ + QJsonObject obj; + + obj.insert(QLatin1String("x"), v.x()); + obj.insert(QLatin1String("y"), v.y()); + obj.insert(QLatin1String("z"), v.z()); + obj.insert(QLatin1String("w"), v.w()); + + return obj; +} + +template<> +QJsonValue typeToJsonValue<QVector4D>(const QVector4D &v) noexcept +{ + QJsonArray value; + + value.push_back(v.x()); + value.push_back(v.y()); + value.push_back(v.z()); + value.push_back(v.w()); + + return value; +} + +template<> +QJsonObject typeToJsonObj<QMatrix4x4>(const QMatrix4x4 &v) noexcept +{ + QJsonObject obj; + + obj.insert(QLatin1String("row 0"), typeToJsonObj(v.row(0))); + obj.insert(QLatin1String("row 1"), typeToJsonObj(v.row(0))); + obj.insert(QLatin1String("row 2"), typeToJsonObj(v.row(0))); + obj.insert(QLatin1String("row 3"), typeToJsonObj(v.row(0))); + + return obj; +} + +template<> +QJsonValue typeToJsonValue<QMatrix4x4>(const QMatrix4x4 &v) noexcept +{ + QJsonArray value; + + value.push_back(typeToJsonValue(v.row(0))); + value.push_back(typeToJsonValue(v.row(1))); + value.push_back(typeToJsonValue(v.row(2))); + value.push_back(typeToJsonValue(v.row(3))); + + return value; +} + +template<> +QJsonValue typeToJsonValue<QVariant>(const QVariant &v) noexcept +{ + const int nodeTypeId = qMetaTypeId<Qt3DCore::QNodeId>(); + + if (v.userType() == nodeTypeId) + return typeToJsonValue(v.value<Qt3DCore::QNodeId>()); + + switch (v.userType()) { + case QMetaType::QVector3D: + return typeToJsonValue(v.value<QVector3D>()); + case QMetaType::QVector4D: + return typeToJsonValue(v.value<QVector4D>()); + case QMetaType::QMatrix4x4: + return typeToJsonValue(v.value<QMatrix4x4>()); + default: + return QJsonValue::fromVariant(v); + } +} + +template<typename Handle, typename Manager> +QJsonObject backendNodeToJSon(Handle handle, Manager *manager) noexcept +{ + Qt3DCore::QBackendNode *node = manager->data(handle); + QJsonObject obj; + Qt3DCore::QNodeId id; + if (node != nullptr) + id = node->peerId(); + obj.insert(QLatin1String("id"), qint64(id.id())); + return obj; +} + +QJsonObject parameterPackToJson(const Render::Rhi::ShaderParameterPack &pack) noexcept +{ + QJsonObject obj; + + const Render::Rhi::PackUniformHash &uniforms = pack.uniforms(); + QJsonArray uniformsArray; + for (int i = 0, m = uniforms.keys.size(); i < m; ++i) { + QJsonObject uniformObj; + uniformObj.insert(QLatin1String("name"), + Render::StringToInt::lookupString(uniforms.keys.at(i))); + const Render::UniformValue::ValueType type = uniforms.values.at(i).valueType(); + uniformObj.insert(QLatin1String("type"), + type == Render::UniformValue::ScalarValue ? QLatin1String("value") + : QLatin1String("texture")); + uniformsArray.push_back(uniformObj); + } + obj.insert(QLatin1String("uniforms"), uniformsArray); + + QJsonArray texturesArray; + const QVector<Render::Rhi::ShaderParameterPack::NamedResource> &textures = pack.textures(); + for (const auto &texture : textures) { + QJsonObject textureObj; + textureObj.insert(QLatin1String("name"), + Render::StringToInt::lookupString(texture.glslNameId)); + textureObj.insert(QLatin1String("id"), qint64(texture.nodeId.id())); + texturesArray.push_back(textureObj); + } + obj.insert(QLatin1String("textures"), texturesArray); + + const QVector<Render::Rhi::BlockToUBO> &ubos = pack.uniformBuffers(); + QJsonArray ubosArray; + for (const auto &ubo : ubos) { + QJsonObject uboObj; + uboObj.insert(QLatin1String("index"), ubo.m_blockIndex); + uboObj.insert(QLatin1String("bufferId"), qint64(ubo.m_bufferID.id())); + ubosArray.push_back(uboObj); + } + obj.insert(QLatin1String("ubos"), ubosArray); + + const QVector<Render::Rhi::BlockToSSBO> &ssbos = pack.shaderStorageBuffers(); + QJsonArray ssbosArray; + for (const auto &ssbo : ssbos) { + QJsonObject ssboObj; + ssboObj.insert(QLatin1String("index"), ssbo.m_blockIndex); + ssboObj.insert(QLatin1String("bufferId"), qint64(ssbo.m_bufferID.id())); + ssbosArray.push_back(ssboObj); + } + obj.insert(QLatin1String("ssbos"), ssbosArray); + + return obj; +} + +} // anonymous + +CommandExecuter::CommandExecuter(Render::Rhi::Renderer *renderer) : m_renderer(renderer) { } + +// Render thread +void CommandExecuter::performAsynchronousCommandExecution( + const QVector<Render::Rhi::RenderView *> &views) +{ + RHI_UNIMPLEMENTED; + //* QMutexLocker lock(&m_pendingCommandsMutex); + //* const QVector<Qt3DCore::Debug::AsynchronousCommandReply *> shellCommands = + //std::move(m_pendingCommands); + //* lock.unlock(); + //* + //* for (auto *reply : shellCommands) { + //* if (reply->commandName() == QLatin1String("glinfo")) { + //* QJsonObject replyObj; + //* const GraphicsApiFilterData *contextInfo = + //m_renderer->submissionContext()->contextInfo(); + //* if (contextInfo != nullptr) { + //* replyObj.insert(QLatin1String("api"), + //* contextInfo->m_api == QGraphicsApiFilter::OpenGL + //* ? QLatin1String("OpenGL") + //* : QLatin1String("OpenGLES")); + //* const QString versionString = + //* QString::number(contextInfo->m_major) + //* + QStringLiteral(".") + //* + QString::number(contextInfo->m_minor); + //* replyObj.insert(QLatin1String("version"), versionString); + //* replyObj.insert(QLatin1String("profile"), + //* contextInfo->m_profile == QGraphicsApiFilter::CoreProfile + //* ? QLatin1String("Core") + //* : contextInfo->m_profile == + //QGraphicsApiFilter::CompatibilityProfile + //* ? QLatin1String("Compatibility") + //* : QLatin1String("None")); + //* } + //* reply->setData(QJsonDocument(replyObj).toJson()); + //* } else if (reply->commandName() == QLatin1String("rendercommands")) { + //* QJsonObject replyObj; + //* + //* QJsonArray viewArray; + //* for (Render::Rhi::RenderView *v : views) { + //* QJsonObject viewObj; + //* viewObj.insert(QLatin1String("viewport"), typeToJsonValue(v->viewport())); + //* viewObj.insert(QLatin1String("surfaceSize"), + //typeToJsonValue(v->surfaceSize())); + //* viewObj.insert(QLatin1String("devicePixelRatio"), v->devicePixelRatio()); + //* viewObj.insert(QLatin1String("noDraw"), v->noDraw()); + //* viewObj.insert(QLatin1String("frustumCulling"), v->frustumCulling()); + //* viewObj.insert(QLatin1String("compute"), v->isCompute()); + //* viewObj.insert(QLatin1String("clearDepthValue"), v->clearDepthValue()); + //* viewObj.insert(QLatin1String("clearStencilValue"), v->clearStencilValue()); + //* + //* QJsonArray renderCommandsArray; + //* for (Render::Rhi::RenderCommand &c : v->commands()) { + //* QJsonObject commandObj; + //* Render::NodeManagers *nodeManagers = m_renderer->nodeManagers(); + //* commandObj.insert(QLatin1String("shader"), + //typeToJsonValue(QVariant::fromValue(c.m_shaderId))); + //* commandObj.insert(QLatin1String("vao"), double(c.m_vao.handle())); + //* commandObj.insert(QLatin1String("instanceCount"), c.m_instanceCount); + //* commandObj.insert(QLatin1String("geometry"), + //backendNodeToJSon(c.m_geometry, nodeManagers->geometryManager())); + //* commandObj.insert(QLatin1String("geometryRenderer"), + //backendNodeToJSon(c.m_geometryRenderer, nodeManagers->geometryRendererManager())); + //* commandObj.insert(QLatin1String("shaderParameterPack"), + //parameterPackToJson(c.m_parameterPack)); + //* + //* renderCommandsArray.push_back(commandObj); + //* } + //* viewObj.insert(QLatin1String("commands"), renderCommandsArray); + //* viewArray.push_back(viewObj); + //* } + //* + //* replyObj.insert(QLatin1String("renderViews"), viewArray); + //* reply->setData(QJsonDocument(replyObj).toJson()); + //* } + //* reply->setFinished(true); + //* } +} + +// Main thread +QVariant CommandExecuter::executeCommand(const QStringList &args) +{ + RHI_UNIMPLEMENTED; + //* // Note: The replies will be deleted by the AspectCommandDebugger + //* if (args.length() > 0 && + //* (args.first() == QLatin1String("glinfo") || + //* args.first() == QLatin1String("rendercommands"))) { + //* auto reply = new Qt3DCore::Debug::AsynchronousCommandReply(args.first()); + //* QMutexLocker lock(&m_pendingCommandsMutex); + //* m_pendingCommands.push_back(reply); + //* return QVariant::fromValue(reply); + //* } + return QVariant(); +} + +} // Debug + +} // Qt3DRenderer + +QT_END_NAMESPACE diff --git a/src/plugins/renderers/rhi/renderer/commandexecuter_p.h b/src/plugins/renderers/rhi/renderer/commandexecuter_p.h new file mode 100644 index 000000000..924930a49 --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/commandexecuter_p.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Klaralvdalens Datakonsult AB (KDAB). +** Copyright (C) 2016 Paul Lemire <paul.lemire350@gmail.com> +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT3DRENDER_DEBUG_COMMANDEXECUTER_H +#define QT3DRENDER_DEBUG_COMMANDEXECUTER_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 <QVariant> +#include <QMutex> + +QT_BEGIN_NAMESPACE + +namespace Qt3DCore { + +namespace Debug { +class AsynchronousCommandReply; +} // Debug + +} // Qt3DCore + +namespace Qt3DRender { + +namespace Render { +namespace Rhi { +class Renderer; +class RenderView; +} // Rhi +} // Render + +namespace Debug { + +class CommandExecuter +{ +public: + explicit CommandExecuter(Render::Rhi::Renderer *renderer); + + void performAsynchronousCommandExecution(const QVector<Render::Rhi::RenderView *> &views); + + QVariant executeCommand(const QStringList &args); + +private: + Render::Rhi::Renderer *m_renderer; + QVector<Qt3DCore::Debug::AsynchronousCommandReply *> m_pendingCommands; + QMutex m_pendingCommandsMutex; +}; + +} // Debug + +} // Qt3DRender + +QT_END_NAMESPACE + +#endif // QT3DRENDER_DEBUG_COMMANDEXECUTER_H diff --git a/src/plugins/renderers/rhi/renderer/logging.cpp b/src/plugins/renderers/rhi/renderer/logging.cpp new file mode 100644 index 000000000..29bdb2145 --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/logging.cpp @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 "logging_p.h" + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { + +namespace Render { + +namespace Rhi { + +Q_LOGGING_CATEGORY(Backend, "Qt3D.Renderer.OpenGL.Backend", QtWarningMsg) +Q_LOGGING_CATEGORY(Frontend, "Qt3D.Renderer.OpenGL.Frontend", QtWarningMsg) +Q_LOGGING_CATEGORY(Io, "Qt3D.Renderer.OpenGL.IO", QtWarningMsg) +Q_LOGGING_CATEGORY(Jobs, "Qt3D.Renderer.OpenGL.Jobs", QtWarningMsg) +Q_LOGGING_CATEGORY(SceneLoaders, "Qt3D.Renderer.OpenGL.SceneLoaders", QtWarningMsg) +Q_LOGGING_CATEGORY(Framegraph, "Qt3D.Renderer.OpenGL.Framegraph", QtWarningMsg) +Q_LOGGING_CATEGORY(RenderNodes, "Qt3D.Renderer.OpenGL.RenderNodes", QtWarningMsg) +Q_LOGGING_CATEGORY(Rendering, "Qt3D.Renderer.OpenGL.Rendering", QtWarningMsg) +Q_LOGGING_CATEGORY(Memory, "Qt3D.Renderer.OpenGL.Memory", QtWarningMsg) +Q_LOGGING_CATEGORY(Shaders, "Qt3D.Renderer.OpenGL.Shaders", QtWarningMsg) +Q_LOGGING_CATEGORY(RenderStates, "Qt3D.Renderer.OpenGL.RenderStates", QtWarningMsg) +Q_LOGGING_CATEGORY(VSyncAdvanceService, "Qt3D.Renderer.OpenGL.VsyncAdvanceService", QtWarningMsg) + +} // namespace Rhi + +} // namespace Render + +} // namespace Qt3DRender + +QT_END_NAMESPACE diff --git a/src/plugins/renderers/rhi/renderer/logging_p.h b/src/plugins/renderers/rhi/renderer/logging_p.h new file mode 100644 index 000000000..45d63978d --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/logging_p.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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_RHI_RENDERLOGGING_P_H +#define QT3DRENDER_RENDER_RHI_RENDERLOGGING_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 <QLoggingCategory> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { + +namespace Render { + +namespace Rhi { + +Q_DECLARE_LOGGING_CATEGORY(Backend) +Q_DECLARE_LOGGING_CATEGORY(Frontend) +Q_DECLARE_LOGGING_CATEGORY(Io) +Q_DECLARE_LOGGING_CATEGORY(Jobs) +Q_DECLARE_LOGGING_CATEGORY(SceneLoaders) +Q_DECLARE_LOGGING_CATEGORY(Framegraph) +Q_DECLARE_LOGGING_CATEGORY(RenderNodes) +Q_DECLARE_LOGGING_CATEGORY(Rendering) +Q_DECLARE_LOGGING_CATEGORY(Memory) +Q_DECLARE_LOGGING_CATEGORY(Shaders) +Q_DECLARE_LOGGING_CATEGORY(RenderStates) +Q_DECLARE_LOGGING_CATEGORY(VSyncAdvanceService) + +} // namespace Rhi + +} // namespace Render + +} // namespace Qt3DRender + +QT_END_NAMESPACE + +#endif // QT3DRENDER_RENDER_RHI_RENDERLOGGING_P_H diff --git a/src/plugins/renderers/rhi/renderer/rendercommand.cpp b/src/plugins/renderers/rhi/renderer/rendercommand.cpp new file mode 100644 index 000000000..8320abac1 --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/rendercommand.cpp @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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" +#include "renderer/rhigraphicspipeline_p.h" + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { +namespace Render { +namespace Rhi { + +RenderCommand::RenderCommand() + : m_rhiShader(nullptr), + m_stateSet(nullptr), + m_depth(0.0f), + m_changeCost(0), + m_type(RenderCommand::Draw), + m_workGroups(), + 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(Qt3DRender::QAttribute::UnsignedShort), + m_indirectAttributeByteOffset(0), + m_drawIndexed(false), + m_drawIndirect(false), + m_primitiveRestartEnabled(false), + m_isValid(false), + indexAttribute(nullptr), + indexBuffer(nullptr), + m_commandUBO(), + pipeline(nullptr) + +{ + m_workGroups[0] = 0; + m_workGroups[1] = 0; + m_workGroups[2] = 0; +} + +bool RenderCommand::isValid() const noexcept +{ + return m_rhiShader && pipeline && pipeline->pipeline(); +} + +bool operator==(const RenderCommand &a, const RenderCommand &b) noexcept +{ + return (a.m_rhiShader == b.m_rhiShader && a.m_material == b.m_material + && a.m_stateSet == b.m_stateSet && a.m_geometry == b.m_geometry + && a.m_geometryRenderer == b.m_geometryRenderer + && a.m_indirectDrawBuffer == b.m_indirectDrawBuffer + && a.m_activeAttributes == b.m_activeAttributes && a.m_depth == b.m_depth + && a.m_changeCost == b.m_changeCost && a.m_shaderId == b.m_shaderId + && a.m_workGroups[0] == b.m_workGroups[0] && a.m_workGroups[1] == b.m_workGroups[1] + && a.m_workGroups[2] == b.m_workGroups[2] && a.m_primitiveCount == b.m_primitiveCount + && a.m_primitiveType == b.m_primitiveType + && a.m_restartIndexValue == b.m_restartIndexValue + && a.m_firstInstance == b.m_firstInstance && a.m_firstVertex == b.m_firstVertex + && a.m_verticesPerPatch == b.m_verticesPerPatch + && a.m_instanceCount == b.m_instanceCount && a.m_indexOffset == b.m_indexOffset + && a.m_indexAttributeByteOffset == b.m_indexAttributeByteOffset + && a.m_drawIndexed == b.m_drawIndexed && a.m_drawIndirect == b.m_drawIndirect + && a.m_primitiveRestartEnabled == b.m_primitiveRestartEnabled + && a.m_isValid == b.m_isValid && a.m_computeCommand == b.m_computeCommand); +} + +} // namespace Rhi +} // namespace Render +} // namespace Qt3DRender + +QT_END_NAMESPACE diff --git a/src/plugins/renderers/rhi/renderer/rendercommand_p.h b/src/plugins/renderers/rhi/renderer/rendercommand_p.h new file mode 100644 index 000000000..e6924a60c --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/rendercommand_p.h @@ -0,0 +1,211 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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_RHI_RENDERCOMMAND_H +#define QT3DRENDER_RENDER_RHI_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 <shaderparameterpack_p.h> +#include <rhihandle_types_p.h> +#include <renderviewjobutils_p.h> +#include <Qt3DRender/private/handle_types_p.h> +#include <Qt3DRender/qgeometryrenderer.h> +#include <QOpenGLShaderProgram> +#include <QOpenGLTexture> +#include <QMatrix4x4> +#include <QtGui/private/qrhi_p.h> +#include <Qt3DRender/qattribute.h> + +QT_BEGIN_NAMESPACE +class QRhiGraphicsPipeline; +class QRhiShaderResourceBindings; + +namespace Qt3DRender { + +namespace Render { + +class RenderStateSet; +using RenderStateSetPtr = QSharedPointer<RenderStateSet>; + +namespace Rhi { + +class RHIShader; +class RHIGraphicsPipeline; + +struct CommandUBO +{ + float modelMatrix[16]; + float inverseModelMatrix[16]; + float modelViewMatrix[16]; + float modelNormalMatrix[12]; + float inverseModelViewMatrix[16]; + float mvp[16]; + float inverseModelViewProjectionMatrix[16]; +}; +static_assert(sizeof(CommandUBO) == 6 * (16 * sizeof(float)) + 1 * (12 * sizeof(float)), + "UBO doesn't match std140"); + +class Q_AUTOTEST_EXPORT RenderCommand +{ +public: + RenderCommand(); + + bool isValid() const noexcept; + + HMaterial m_material; // Purely used to ease sorting (minimize stage changes, binding changes + // ....) + RHIShader *m_rhiShader; // GL Shader to be used at render time + Qt3DCore::QNodeId m_shaderId; // Shader for given pass and mesh + ShaderParameterPack m_parameterPack; // Might need to be reworked so as to be able to destroy + // the Texture while submission is happening. + RenderStateSetPtr m_stateSet; + + HGeometry m_geometry; + HGeometryRenderer m_geometryRenderer; + + HBuffer m_indirectDrawBuffer; // Reference to indirect draw buffer (valid only m_drawIndirect == + // true) + HComputeCommand m_computeCommand; + + // A QAttribute pack might be interesting + // This is a temporary fix in the meantime, to remove the hacked methods in Technique + QVector<int> m_activeAttributes; + + float m_depth; + int m_changeCost; + + enum CommandType { Draw, Compute }; + + CommandType m_type; + int m_workGroups[3]; + + // Values filled for draw calls by Renderer (in prepare Submission) + 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; + Qt3DRender::QAttribute::VertexBaseType m_indexAttributeDataType; + uint m_indirectAttributeByteOffset; + bool m_drawIndexed; + bool m_drawIndirect; + bool m_primitiveRestartEnabled; + bool m_isValid; + + QVarLengthArray<QRhiCommandBuffer::VertexInput, 8> vertex_input; + + const Attribute *indexAttribute {}; + QRhiBuffer *indexBuffer {}; + + CommandUBO m_commandUBO; + RHIGraphicsPipeline *pipeline {}; +}; + +Q_AUTOTEST_EXPORT bool operator==(const RenderCommand &a, const RenderCommand &b) noexcept; + +inline bool operator!=(const RenderCommand &lhs, const RenderCommand &rhs) noexcept +{ + return !operator==(lhs, rhs); +} + +struct EntityRenderCommandData +{ + QVector<Entity *> entities; + QVector<RenderCommand> commands; + QVector<RenderPassParameterData> passesData; + + void reserve(int size) + { + entities.reserve(size); + commands.reserve(size); + passesData.reserve(size); + } + + inline int size() const { return entities.size(); } + + inline void push_back(Entity *e, const RenderCommand &c, const RenderPassParameterData &p) + { + entities.push_back(e); + commands.push_back(c); + passesData.push_back(p); + } + + inline void push_back(Entity *e, RenderCommand &&c, RenderPassParameterData &&p) + { + entities.push_back(e); + commands.push_back(std::move(c)); + passesData.push_back(std::move(p)); + } + + EntityRenderCommandData &operator+=(EntityRenderCommandData &&t) + { + entities += std::move(t.entities); + commands += std::move(t.commands); + passesData += std::move(t.passesData); + return *this; + } +}; + +using EntityRenderCommandDataPtr = QSharedPointer<EntityRenderCommandData>; + +} // namespace Rhi + +} // namespace Render + +} // namespace Qt3DRender + +QT_END_NAMESPACE + +#endif // QT3DRENDER_RENDER_RHI_RENDERCOMMAND_H diff --git a/src/plugins/renderers/rhi/renderer/renderer.cpp b/src/plugins/renderers/rhi/renderer/renderer.cpp new file mode 100644 index 000000000..ee9a26a98 --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/renderer.cpp @@ -0,0 +1,2537 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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/cameralens_p.h> +#include <Qt3DRender/private/entity_p.h> +#include <Qt3DRender/private/renderlogging_p.h> +#include <Qt3DRender/private/material_p.h> +#include <Qt3DRender/private/renderpassfilternode_p.h> +#include <Qt3DRender/private/shader_p.h> +#include <Qt3DRender/private/buffer_p.h> +#include <Qt3DRender/private/technique_p.h> +#include <Qt3DRender/private/renderthread_p.h> +#include <Qt3DRender/private/scenemanager_p.h> +#include <Qt3DRender/private/techniquefilternode_p.h> +#include <Qt3DRender/private/viewportnode_p.h> +#include <Qt3DRender/private/vsyncframeadvanceservice_p.h> +#include <Qt3DRender/private/managers_p.h> +#include <Qt3DRender/private/buffermanager_p.h> +#include <Qt3DRender/private/nodemanagers_p.h> +#include <Qt3DRender/private/geometryrenderermanager_p.h> +#include <Qt3DRender/private/techniquemanager_p.h> +#include <Qt3DRender/private/platformsurfacefilter_p.h> +#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/subtreeenabler_p.h> +#include <Qt3DRender/private/qshaderprogrambuilder_p.h> +#include <Qt3DRender/private/qshaderprogram_p.h> + +#include <Qt3DRender/qcameralens.h> +#include <Qt3DCore/private/qabstractaspectjobmanager_p.h> +#include <Qt3DCore/private/qaspectmanager_p.h> +#include <Qt3DCore/private/qsysteminformationservice_p.h> +#include <Qt3DCore/private/qsysteminformationservice_p_p.h> +#include <Qt3DRender/private/resourceaccessor_p.h> +#include <Qt3DRender/private/renderlogging_p.h> +#include <Qt3DRender/private/renderstateset_p.h> +#include <Qt3DRender/private/setfence_p.h> +#include <Qt3DRender/private/stringtoint_p.h> +#include <Qt3DRender/private/qrenderaspect_p.h> + +#include <rhibuffer_p.h> +#include <rhigraphicspipeline_p.h> + +#include <rendercommand_p.h> +#include <renderqueue_p.h> +#include <renderview_p.h> +#include <texture_p.h> +#include <renderviewbuilder_p.h> +#include <rhiresourcemanagers_p.h> +#include <commandexecuter_p.h> +#include <submissioncontext_p.h> + +#include <QStack> +#include <QOffscreenSurface> +#include <QSurface> +#include <QElapsedTimer> +#include <QLibraryInfo> +#include <QMutexLocker> +#include <QPluginLoader> +#include <QDir> +#include <QUrl> +#include <QOffscreenSurface> +#include <QWindow> +#include <QThread> +#include <QKeyEvent> +#include <QMouseEvent> + +#include <QtGui/private/qopenglcontext_p.h> +#include <QGuiApplication> + +QT_BEGIN_NAMESPACE + +// Crashes on AMD Radeon drivers on Windows. Disable for now. +//#define SHADER_LOADING_IN_COMMAND_THREAD +using namespace Qt3DCore; + +namespace Qt3DRender { +namespace Render { +namespace Rhi { + +namespace { + +class CachingLightGatherer : public LightGatherer +{ +public: + CachingLightGatherer(RendererCache *cache) : LightGatherer(), m_cache(cache) { } + + void run() override + { + LightGatherer::run(); + + QMutexLocker lock(m_cache->mutex()); + m_cache->gatheredLights = lights(); + m_cache->environmentLight = environmentLight(); + } + +private: + RendererCache *m_cache; +}; + +class CachingRenderableEntityFilter : public RenderableEntityFilter +{ +public: + CachingRenderableEntityFilter(RendererCache *cache) : RenderableEntityFilter(), m_cache(cache) + { + } + + void run() override + { + RenderableEntityFilter::run(); + + QVector<Entity *> selectedEntities = filteredEntities(); + std::sort(selectedEntities.begin(), selectedEntities.end()); + + QMutexLocker lock(m_cache->mutex()); + m_cache->renderableEntities = selectedEntities; + } + +private: + RendererCache *m_cache; +}; + +class CachingComputableEntityFilter : public ComputableEntityFilter +{ +public: + CachingComputableEntityFilter(RendererCache *cache) : ComputableEntityFilter(), m_cache(cache) + { + } + + void run() override + { + ComputableEntityFilter::run(); + + QVector<Entity *> selectedEntities = filteredEntities(); + std::sort(selectedEntities.begin(), selectedEntities.end()); + + QMutexLocker lock(m_cache->mutex()); + m_cache->computeEntities = selectedEntities; + } + +private: + RendererCache *m_cache; +}; + +int locationForAttribute(Attribute *attr, RHIShader *shader) noexcept +{ + const QVector<ShaderAttribute> attribInfo = shader->attributes(); + const auto it = std::find_if( + attribInfo.begin(), attribInfo.end(), + [attr](const ShaderAttribute &sAttr) { return attr->nameId() == sAttr.m_nameId; }); + if (it != attribInfo.end()) + return it->m_location; + return -1; +} + +} // anonymous + +/*! + \internal + + Renderer shutdown procedure: + + Since the renderer relies on the surface and OpenGLContext to perform its cleanup, + it is shutdown when the surface is set to nullptr + + When the surface is set to nullptr this will request the RenderThread to terminate + and will prevent createRenderBinJobs from returning a set of jobs as there is nothing + more to be rendered. + + In turn, this will call shutdown which will make the OpenGL context current one last time + to allow cleanups requiring a call to QOpenGLContext::currentContext to execute properly. + At the end of that function, the GraphicsContext is set to null. + + At this point though, the QAspectThread is still running its event loop and will only stop + a short while after. + */ + +Renderer::Renderer(QRenderAspect::RenderType type) + : m_services(nullptr), + m_aspect(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_vsyncFrameAdvanceService(new VSyncFrameAdvanceService(m_renderThread != nullptr)), + m_waitForInitializationToBeCompleted(0), + m_hasBeenInitializedMutex(), + m_exposed(0), + m_lastFrameCorrect(0), + m_glContext(nullptr), + m_time(0), + m_settings(nullptr), + m_updateShaderDataTransformJob(Render::UpdateShaderDataTransformJobPtr::create()), + m_cleanupJob(Render::FrameCleanupJobPtr::create()), + m_sendBufferCaptureJob(Render::SendBufferCaptureJobPtr::create()), + m_filterCompatibleTechniqueJob(FilterCompatibleTechniqueJobPtr::create()), + m_lightGathererJob(new CachingLightGatherer(&m_cache)), + m_renderableEntityFilterJob(new CachingRenderableEntityFilter(&m_cache)), + m_computableEntityFilterJob(new CachingComputableEntityFilter(&m_cache)), + m_bufferGathererJob(SynchronizerJobPtr::create([this] { lookForDirtyBuffers(); }, + JobTypes::DirtyBufferGathering)), + m_textureGathererJob(SynchronizerJobPtr::create([this] { lookForDirtyTextures(); }, + JobTypes::DirtyTextureGathering)), + m_introspectShaderJob(SynchronizerPostFramePtr::create( + [this] { reloadDirtyShaders(); }, + [this](Qt3DCore::QAspectManager *m) { sendShaderChangesToFrontend(m); }, + JobTypes::DirtyShaderGathering)), + m_ownedContext(false), + m_offscreenHelper(nullptr), + m_RHIResourceManagers(nullptr), + m_commandExecuter(new Qt3DRender::Debug::CommandExecuter(this)), + m_shouldSwapBuffers(true) +{ + std::fill_n(m_textureTransform, 4, 0.f); + + // 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(); + + m_introspectShaderJob->addDependency(m_filterCompatibleTechniqueJob); + + m_filterCompatibleTechniqueJob->setRenderer(this); + + m_defaultRenderStateSet = new RenderStateSet; + m_defaultRenderStateSet->addState(StateVariant::createState<DepthTest>(GL_LESS)); + m_defaultRenderStateSet->addState(StateVariant::createState<CullFace>(GL_BACK)); + m_defaultRenderStateSet->addState(StateVariant::createState<ColorMask>(true, true, true, true)); +} + +Renderer::~Renderer() +{ + Q_ASSERT(m_running.fetchAndStoreOrdered(0) == 0); + if (m_renderThread) + Q_ASSERT(m_renderThread->isFinished()); + + delete m_renderQueue; + delete m_defaultRenderStateSet; + delete m_RHIResourceManagers; + + 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; +} + +API Renderer::api() const +{ + return API::OpenGL; +} + +qint64 Renderer::time() const +{ + return m_time; +} + +void Renderer::setTime(qint64 time) +{ + m_time = time; +} + +void Renderer::setJobsInLastFrame(int jobsInLastFrame) +{ + m_jobsInLastFrame = jobsInLastFrame; +} + +void Renderer::setAspect(QRenderAspect *aspect) +{ + m_aspect = aspect; + m_updateShaderDataTransformJob->addDependency( + QRenderAspectPrivate::get(aspect)->m_worldTransformJob); +} + +void Renderer::setNodeManagers(NodeManagers *managers) +{ + m_nodesManager = managers; + m_RHIResourceManagers = new RHIResourceManagers(); + m_scene2DResourceAccessor.reset(new ResourceAccessor(this, m_nodesManager)); + + m_updateShaderDataTransformJob->setManagers(m_nodesManager); + m_cleanupJob->setManagers(m_nodesManager); + m_filterCompatibleTechniqueJob->setManager(m_nodesManager->techniqueManager()); + m_sendBufferCaptureJob->setManagers(m_nodesManager); + m_lightGathererJob->setManager(m_nodesManager->renderNodesManager()); + m_renderableEntityFilterJob->setManager(m_nodesManager->renderNodesManager()); + m_computableEntityFilterJob->setManager(m_nodesManager->renderNodesManager()); +} + +void Renderer::setServices(QServiceLocator *services) +{ + m_services = services; + + m_nodesManager->sceneManager()->setDownloadService(m_services->downloadHelperService()); +} + +QRenderAspect *Renderer::aspect() const +{ + return m_aspect; +} + +NodeManagers *Renderer::nodeManagers() const +{ + return m_nodesManager; +} + +/*! + \internal + + Return context which can be used to share resources safely + with qt3d main render context. +*/ +QOpenGLContext *Renderer::shareContext() const +{ + return nullptr; +} + +// Executed in the reloadDirtyShader job +void Renderer::loadShader(Shader *shader, HShader shaderHandle) +{ + Q_UNUSED(shader); + if (!m_dirtyShaders.contains(shaderHandle)) + m_dirtyShaders.push_back(shaderHandle); +} + +void Renderer::setOpenGLContext(QOpenGLContext *context) +{ + m_glContext = context; +} + +void Renderer::setScreen(QScreen *scr) +{ + m_screen = scr; +} + +QScreen *Renderer::screen() const +{ + return m_screen; +} + +bool Renderer::accessOpenGLTexture(Qt3DCore::QNodeId nodeId, QOpenGLTexture **texture, + QMutex **lock, bool readonly) +{ + RHI_UNIMPLEMENTED; + + Texture *tex = m_nodesManager->textureManager()->lookupResource(nodeId); + if (!tex) + return false; + + RHITexture *glTex = m_RHIResourceManagers->rhiTextureManager()->lookupResource(tex->peerId()); + if (!glTex) + return false; + + if (glTex->isDirty()) + return false; + + if (!readonly) + glTex->setExternalRenderingEnabled(true); + + // RHITexture::TextureUpdateInfo texInfo = + // glTex->createOrUpdateRhiTexture(m_submissionContext.data()); *texture = texInfo.texture; + + if (!readonly) + *lock = glTex->externalRenderingLock(); + + return true; +} + +QSharedPointer<RenderBackendResourceAccessor> Renderer::resourceAccessor() const +{ + return m_scene2DResourceAccessor; +} + +// Called in RenderThread context by the run method of RenderThread +// RenderThread has locked the mutex already and unlocks it when this +// method termintates +void Renderer::initialize() +{ + QMutexLocker lock(&m_hasBeenInitializedMutex); + m_submissionContext.reset(new SubmissionContext); + m_submissionContext->setRenderer(this); + + // RHI initialization + { + qCDebug(Backend) << Q_FUNC_INFO << "Requesting renderer initialize"; + m_submissionContext->initialize(); + + // We need to adapt texture coordinates + // m_textureTransform is (a;b) in texCoord = a * texCoord + b + if (m_submissionContext->rhi()->isYUpInFramebuffer()) { + // OpenGL case - that is what we assume to be the default so we do not change + // anything + m_textureTransform[0] = 1.f; + m_textureTransform[1] = 1.f; + m_textureTransform[2] = 0.f; + m_textureTransform[3] = 0.f; + } else { + // Other cases : y = 1 - y + m_textureTransform[0] = 1.f; + m_textureTransform[1] = -1.f; + m_textureTransform[2] = 0.f; + m_textureTransform[3] = 1.f; + } + + // Awake setScenegraphRoot in case it was waiting + m_waitForInitializationToBeCompleted.release(1); + + // Allow the aspect manager to proceed + m_vsyncFrameAdvanceService->proceedToNextFrame(); + + // Force initial refresh + markDirty(AllDirty, nullptr); + return; + } +} + +/*! + * \internal + * + * Signals for the renderer to stop rendering. If a threaded renderer is in use, + * the render thread will call releaseGraphicsResources() just before the thread exits. + * If rendering synchronously, this function will call releaseGraphicsResources(). + */ +void Renderer::shutdown() +{ + // Ensure we have waited to be fully initialized before trying to shut down + // (in case initialization is taking place at the same time) + QMutexLocker lock(&m_hasBeenInitializedMutex); + + qCDebug(Backend) << Q_FUNC_INFO << "Requesting renderer shutdown"; + m_running.storeRelaxed(0); + + // We delete any renderqueue that we may not have had time to render + // before the surface was destroyed + QMutexLocker lockRenderQueue(m_renderQueue->mutex()); + qDeleteAll(m_renderQueue->nextFrameQueue()); + m_renderQueue->reset(); + lockRenderQueue.unlock(); + + 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(); + } + + // Destroy internal managers + // This needs to be done before the nodeManager is destroy + // as the internal resources might somehow rely on nodeManager resources + delete m_RHIResourceManagers; + m_RHIResourceManagers = nullptr; +} + +/*! + \internal + + When using a threaded renderer this function is called in the context of the + RenderThread to do any shutdown and cleanup that needs to be performed in the + thread where the OpenGL context lives. + + When using Scene3D or anything that provides a custom QOpenGLContext (not + owned by Qt3D) this function is called whenever the signal + QOpenGLContext::aboutToBeDestroyed is emitted. In that case this function + is called in the context of the emitter's thread. +*/ +void Renderer::releaseGraphicsResources() +{ + // We may get called twice when running inside of a Scene3D. Once when Qt Quick + // wants to shutdown, and again when the render aspect gets unregistered. So + // check that we haven't already cleaned up before going any further. + if (!m_submissionContext) + return; + + // Try to temporarily make the context current so we can free up any resources + QMutexLocker locker(&m_offscreenSurfaceMutex); + QOffscreenSurface *offscreenSurface = m_offscreenHelper->offscreenSurface(); + if (!offscreenSurface) { + qWarning() << "Failed to make context current: OpenGL resources will not be destroyed"; + // We still need to delete the submission context + m_submissionContext.reset(nullptr); + return; + } + + //* QOpenGLContext *context = m_submissionContext->openGLContext(); + //* Q_ASSERT(context); + //* + //* if (context->thread() == QThread::currentThread() && context->makeCurrent(offscreenSurface)) + //{ + //* + //* // Clean up the graphics context and any resources + //* const QVector<HRHITexture> activeTexturesHandles = + //m_RHIResourceManagers->rhiTextureManager()->activeHandles(); + //* for (const HRHITexture &textureHandle : activeTexturesHandles) { + //* RHITexture *tex = m_RHIResourceManagers->rhiTextureManager()->data(textureHandle); + //* tex->destroy(); + //* } + //* + //* // Do the same thing with buffers + //* const QVector<HRHIBuffer> activeBuffers = + //m_RHIResourceManagers->rhiBufferManager()->activeHandles(); + //* for (const HRHIBuffer &bufferHandle : activeBuffers) { + //* RHIBuffer *buffer = m_RHIResourceManagers->rhiBufferManager()->data(bufferHandle); + //* buffer->destroy(m_submissionContext.data()); + //* } + //* + //* // Do the same thing with shaders + //* const QVector<RHIShader *> shaders = + //m_RHIResourceManagers->rhiShaderManager()->takeActiveResources(); + //* qDeleteAll(shaders); + //* + //* + //* context->doneCurrent(); + //* } else { + //* qWarning() << "Failed to make context current: OpenGL resources will not be destroyed"; + //* } + //* + //* if (m_ownedContext) + //* delete context; + + 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(Entity *sgRoot) +{ + Q_ASSERT(sgRoot); + + // 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_cleanupJob->setRoot(m_renderSceneRoot); + + // Set all flags to dirty + m_dirtyBits.marked |= AbstractRenderer::AllDirty; +} + +void Renderer::setSettings(RenderSettings *settings) +{ + m_settings = settings; +} + +RenderSettings *Renderer::settings() const +{ + return m_settings; +} + +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.loadRelaxed() > 0) { + doRender(); + // TO DO: Restore windows exposed detection + // Probably needs to happens some place else though + } +} + +// Either called by render if Qt3D is in charge of the RenderThread +// or by QRenderAspectPrivate::renderSynchronous (for Scene3D) +void Renderer::doRender(bool swapBuffers) +{ + Renderer::ViewSubmissionResultData submissionData; + bool hasCleanedQueueAndProceeded = false; + bool preprocessingComplete = false; + bool beganDrawing = false; + + // Blocking until RenderQueue is full + const bool canSubmit = isReadyToSubmit(); + m_shouldSwapBuffers = swapBuffers; + + // Lock the mutex to protect access to the renderQueue while we look for its state + QMutexLocker locker(m_renderQueue->mutex()); + const bool queueIsComplete = m_renderQueue->isFrameQueueComplete(); + const bool queueIsEmpty = m_renderQueue->targetRenderViewCount() == 0; + + bool mustCleanResources = false; + + // 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::Rhi::RenderView *> renderViews = m_renderQueue->nextFrameQueue(); + QTaskLogger submissionStatsPart1(m_services->systemInformation(), + { JobTypes::FrameSubmissionPart1, 0 }, + QTaskLogger::Submission); + QTaskLogger submissionStatsPart2(m_services->systemInformation(), + { JobTypes::FrameSubmissionPart2, 0 }, + QTaskLogger::Submission); + + QVector<RHIPassInfo> rhiPassesInfo; + + if (canRender()) { + QSurface *surface = nullptr; + for (const RenderView *rv : renderViews) { + surface = rv->surface(); + if (surface) + break; + } + + // In case we did not draw because e.g. there wase no swapchain, + // we keep the resource updates from the previous frame. + if (!m_submissionContext->m_currentUpdates) { + m_submissionContext->m_currentUpdates = + m_submissionContext->rhi()->nextResourceUpdateBatch(); + } + + // 1) Execute commands for buffer uploads, texture updates, shader loading first + updateResources(); + + rhiPassesInfo = prepareCommandsSubmission(renderViews); + // 2) Update Pipelines and copy data into commands to allow concurrent submission + preprocessingComplete = true; + + bool hasCommands = false; + for (const RenderView *rv : renderViews) { + const auto &commands = rv->commands(); + hasCommands = std::any_of(commands.begin(), commands.end(), + [](const RenderCommand &cmd) { return cmd.isValid(); }); + if (hasCommands) + break; + } + + if (hasCommands) { + // Scoped to destroy surfaceLock + SurfaceLocker surfaceLock(surface); + const bool surfaceIsValid = (surface && surfaceLock.isSurfaceValid()); + if (surfaceIsValid) { + beganDrawing = m_submissionContext->beginDrawing(surface); + if (beganDrawing) { + // Purge shader which aren't used any longer + static int callCount = 0; + ++callCount; + const int shaderPurgePeriod = 600; + if (callCount % shaderPurgePeriod == 0) + m_RHIResourceManagers->rhiShaderManager()->purge(); + } + } + } + // 2) Proceed to next frame and start preparing frame n + 1 + m_renderQueue->reset(); + locker.unlock(); // Done protecting RenderQueue + m_vsyncFrameAdvanceService->proceedToNextFrame(); + hasCleanedQueueAndProceeded = true; + + // 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 (beganDrawing) { + submissionStatsPart1.end(submissionStatsPart2.restart()); + + // 3) Submit the render commands for frame n (making sure we never reference + // something that could be changing) Render using current device state and renderer + // configuration + submissionData = submitRenderViews(rhiPassesInfo); + + // Perform any required cleanup of the Graphics resources (Buffers deleted, Shader + // deleted...) + mustCleanResources = true; + } + } + + // Execute the pending shell commands + m_commandExecuter->performAsynchronousCommandExecution(renderViews); + + // Delete all the RenderViews which will clear the allocators + // that were used for their allocation + qDeleteAll(renderViews); + } + + // If hasCleanedQueueAndProceeded isn't true this implies that something went wrong + // with the rendering and/or the renderqueue is incomplete from some reason + // or alternatively it could be complete but empty (RenderQueue of size 0) + + if (!hasCleanedQueueAndProceeded) { + // 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 + const bool swapBuffers = submissionData.lastBoundFBOId == m_submissionContext->defaultFBO() + && surfaceLock.isSurfaceValid() && m_shouldSwapBuffers; + m_submissionContext->endDrawing(swapBuffers); + + if (mustCleanResources) + cleanGraphicsResources(); + } +} + +// Called by RenderViewJobs +// When the frameQueue is complete and we are using a renderThread +// we allow the render thread to proceed +void Renderer::enqueueRenderView(RenderView *renderView, int submitOrder) +{ + QMutexLocker locker(m_renderQueue->mutex()); // Prevent out of order execution + // We cannot use a lock free primitive here because: + // - 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.loadRelaxed()) + 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.loadRelaxed()) { + 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() +{ + // Make sure that we've been told to render before rendering + // Prevent ouf of order execution + m_submitRenderViewsSemaphore.acquire(1); + + // Check if shutdown has been requested + if (m_running.loadRelaxed() == 0) + return false; + + // The semaphore should only + // be released when the frame queue is complete and there's + // something to render + // The case of shutdown should have been handled just before + Q_ASSERT(m_renderQueue->isFrameQueueComplete()); + return true; +} + +// Main thread +QVariant Renderer::executeCommand(const QStringList &args) +{ + return m_commandExecuter->executeCommand(args); +} + +/*! + \internal + Called in the context of the aspect thread from QRenderAspect::onRegistered +*/ +void Renderer::setOffscreenSurfaceHelper(OffscreenSurfaceHelper *helper) +{ + QMutexLocker locker(&m_offscreenSurfaceMutex); + m_offscreenHelper = helper; +} + +QSurfaceFormat Renderer::format() +{ + return m_submissionContext->format(); +} + +void Renderer::updateGraphicsPipeline(RenderCommand &cmd, RenderView *rv, int renderViewIndex) +{ + if (!cmd.m_rhiShader) { + qDebug() << "Warning: command has no shader"; + return; + } + + // The Graphics Pipeline defines + // - Render State (Depth, Culling, Stencil, Blending) + // - Shader Resources Binding + // - Shader Vertex Attribute Layout + + // This means we need to have one GraphicsPipeline per + // - geometry + // - material + // - state (RV + RC) + + RenderStateSet *renderState = nullptr; + { + RenderStateSet *globalState = + (rv->stateSet() != nullptr) ? rv->stateSet() : m_defaultRenderStateSet; + + // Merge global state into local state + RenderStateSet *localState = cmd.m_stateSet.data(); + if (localState != nullptr) { + localState->merge(globalState); + renderState = localState; + } else { + renderState = globalState; + } + } + + // Try to retrieve existing pipeline + auto &pipelineManager = *m_RHIResourceManagers->rhiGraphicsPipelineManager(); + const GraphicsPipelineIdentifier pipelineKey { cmd.m_geometry, cmd.m_shaderId, + renderViewIndex }; + RHIGraphicsPipeline *graphicsPipeline = pipelineManager.getOrCreateResource(pipelineKey); + // TO DO: Ensure we find a way to know when the state is dirty to trigger a rebuild + + if (!graphicsPipeline) { + qDebug() << "Warning : could not create a graphics pipeline"; + return; + } + + // Increase score so that we know the pipeline was used for this frame and shouldn't be + // destroyed + graphicsPipeline->increaseScore(); + + // TO DO: Set to true if geometry, shader or render state dirty + bool requiredRebuild = false; + + // Note: we can rebuild add/remove things from the QRhiShaderResourceBindings after having + // created the pipeline and rebuild it. Changes should be picked up automatically + + // Create pipeline if it doesn't exist or needs to be updated + if (graphicsPipeline->pipeline() == nullptr || requiredRebuild) { + bool ok = true; + + const SubmissionContext::SwapChainInfo *swapchain = + m_submissionContext->swapChainForSurface(rv->surface()); + if (!swapchain || !swapchain->swapChain || !swapchain->renderPassDescriptor) + return; + + // TO DO: Find a way to recycle those + // Create Per Command UBO + QRhiBuffer *commandUBO = m_submissionContext->rhi()->newBuffer( + QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, sizeof(CommandUBO)); + QRhiBuffer *rvUBO = m_submissionContext->rhi()->newBuffer( + QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, sizeof(RenderViewUBO)); + commandUBO->build(); + rvUBO->build(); + graphicsPipeline->setCommandUBO(commandUBO); + graphicsPipeline->setRenderViewUBO(rvUBO); + + QVector<QRhiShaderResourceBinding> uboBindings; + uboBindings << QRhiShaderResourceBinding::uniformBuffer( + 0, + QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, + rvUBO) + << QRhiShaderResourceBinding::uniformBuffer( + 1, + QRhiShaderResourceBinding::VertexStage + | QRhiShaderResourceBinding::FragmentStage, + commandUBO); + + // Create additional empty UBO Buffer for UBO with binding point > 1 (since we assume 0 and + // 1 and for Qt3D standard values) + const QVector<ShaderUniformBlock> uniformBlocks = cmd.m_rhiShader->uniformBlocks(); + QHash<int, RHIGraphicsPipeline::UBOBuffer> uboBuffers; + for (const ShaderUniformBlock &block : uniformBlocks) { + if (block.m_binding > 1) { + auto handle = m_RHIResourceManagers->rhiBufferManager()->allocateResource(); + RHIBuffer *ubo = m_RHIResourceManagers->rhiBufferManager()->data(handle); + Q_ASSERT(ubo); + const QByteArray rawData(block.m_size, '\0'); + ubo->allocate(m_submissionContext.data(), rawData, true); + ok = ubo->bind(m_submissionContext.data(), RHIBuffer::UniformBuffer); + uboBuffers[block.m_binding] = { handle, ubo }; + uboBindings << QRhiShaderResourceBinding::uniformBuffer( + block.m_binding, + QRhiShaderResourceBinding::VertexStage + | QRhiShaderResourceBinding::FragmentStage, + ubo->rhiBuffer()); + } + } + graphicsPipeline->setUBOs(uboBuffers); + + // Samplers + for (const auto &textureParameter : cmd.m_parameterPack.textures()) { + const auto handle = m_RHIResourceManagers->rhiTextureManager()->getOrAcquireHandle( + textureParameter.nodeId); + const auto textureData = m_RHIResourceManagers->rhiTextureManager()->data(handle); + + for (const ShaderAttribute &samplerAttribute : cmd.m_rhiShader->samplers()) { + if (samplerAttribute.m_nameId == textureParameter.glslNameId) { + const auto rhiTexture = textureData->getRhiTexture(); + const auto rhiSampler = textureData->getRhiSampler(); + if (rhiTexture && rhiSampler) { + uboBindings.push_back(QRhiShaderResourceBinding::sampledTexture( + samplerAttribute.m_location, + QRhiShaderResourceBinding::FragmentStage, rhiTexture, rhiSampler)); + } + } + } + } + + QRhiShaderResourceBindings *shaderResourceBindings = + m_submissionContext->rhi()->newShaderResourceBindings(); + assert(shaderResourceBindings); + + shaderResourceBindings->setBindings(uboBindings.cbegin(), uboBindings.cend()); + ok = shaderResourceBindings->build(); + assert(ok); + + // Create pipeline + QRhiGraphicsPipeline *pipeline = m_submissionContext->rhi()->newGraphicsPipeline(); + graphicsPipeline->setShaderResourceBindings(shaderResourceBindings); + graphicsPipeline->setPipeline(pipeline); + assert(pipeline); + + const QShader vertexShader = cmd.m_rhiShader->shaderStage(QShader::VertexStage); + const QShader fragmentShader = cmd.m_rhiShader->shaderStage(QShader::FragmentStage); + + assert(vertexShader.isValid()); + assert(fragmentShader.isValid()); + + pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vertexShader }, + { QRhiShaderStage::Fragment, fragmentShader } }); + + QVarLengthArray<QRhiVertexInputBinding, 8> inputBindings; + QVarLengthArray<QRhiVertexInputAttribute, 8> rhiAttributes; + + const auto geom = cmd.m_geometry; + const auto &attributes = geom->attributes(); + + struct BufferBinding + { + Qt3DCore::QNodeId bufferId; + uint stride; + QRhiVertexInputBinding::Classification classification; + uint attributeDivisor; + }; + QVector<BufferBinding> uniqueBindings; + + auto rhiAttributeType = [](Attribute *attr) { + switch (attr->vertexBaseType()) { + case QAttribute::Byte: + case QAttribute::UnsignedByte: { + if (attr->vertexSize() == 1) + return QRhiVertexInputAttribute::UNormByte; + if (attr->vertexSize() == 2) + return QRhiVertexInputAttribute::UNormByte2; + if (attr->vertexSize() == 4) + return QRhiVertexInputAttribute::UNormByte4; + Q_FALLTHROUGH(); + } + case QAttribute::Float: { + if (attr->vertexSize() == 1) + return QRhiVertexInputAttribute::Float; + if (attr->vertexSize() == 2) + return QRhiVertexInputAttribute::Float2; + if (attr->vertexSize() == 3) + return QRhiVertexInputAttribute::Float3; + if (attr->vertexSize() == 4) + return QRhiVertexInputAttribute::Float4; + Q_FALLTHROUGH(); + } + default: + qWarning() << "Attribute type not handles by RHI"; + Q_UNREACHABLE(); + } + }; + + // QRhiVertexInputBinding -> specifies the stride of an attribute, whether it's per vertex + // or per instance and the instance divisor QRhiVertexInputAttribute -> specifies the format + // of the attribute (offset, type), the shader location and the index of the binding + // QRhiCommandBuffer::VertexInput -> binds a buffer to a binding + + QHash<int, int> attributeNameToBinding; + + for (Qt3DCore::QNodeId attribute_id : attributes) { + Attribute *attrib = m_nodesManager->attributeManager()->lookupResource(attribute_id); + if (attrib->attributeType() == QAttribute::VertexAttribute) { + const bool isPerInstanceAttr = attrib->divisor() != 0; + const QRhiVertexInputBinding::Classification classification = isPerInstanceAttr + ? QRhiVertexInputBinding::PerInstance + : QRhiVertexInputBinding::PerVertex; + const BufferBinding binding { attrib->bufferId(), attrib->byteStride(), + classification, + isPerInstanceAttr ? attrib->divisor() : 1U }; + + const auto it = + std::find_if(uniqueBindings.begin(), uniqueBindings.end(), + [binding](const BufferBinding &a) { + return binding.bufferId == a.bufferId + && binding.stride == a.stride + && binding.classification == a.classification + && binding.attributeDivisor == a.attributeDivisor; + }); + + int bindingIndex = uniqueBindings.size(); + if (it == uniqueBindings.end()) + uniqueBindings.push_back(binding); + else + bindingIndex = std::distance(uniqueBindings.begin(), it); + + rhiAttributes.push_back({ bindingIndex, + locationForAttribute(attrib, cmd.m_rhiShader), + rhiAttributeType(attrib), attrib->byteOffset() }); + + attributeNameToBinding.insert(attrib->nameId(), bindingIndex); + } + } + + inputBindings.resize(uniqueBindings.size()); + for (int i = 0, m = uniqueBindings.size(); i < m; ++i) { + const BufferBinding binding = uniqueBindings.at(i); + /* + qDebug() << binding.bufferId + << binding.stride + << binding.classification + << binding.attributeDivisor; + */ + inputBindings[i] = QRhiVertexInputBinding { binding.stride, binding.classification, + int(binding.attributeDivisor) }; + } + + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings(inputBindings.begin(), inputBindings.end()); + inputLayout.setAttributes(rhiAttributes.begin(), rhiAttributes.end()); + + pipeline->setVertexInputLayout(inputLayout); + pipeline->setShaderResourceBindings(shaderResourceBindings); + + pipeline->setRenderPassDescriptor(swapchain->renderPassDescriptor); + + graphicsPipeline->setAttributesToBindingHash(attributeNameToBinding); + + // Render States + m_submissionContext->applyStateSet(renderState, pipeline); + + ok = pipeline->build(); + assert(ok); + } + + // Record RHIGraphicsPipeline into command for later use + if (graphicsPipeline && graphicsPipeline->pipeline()) + cmd.pipeline = graphicsPipeline; +} + +// When this function is called, we must not be processing the commands for frame n+1 +QVector<Renderer::RHIPassInfo> +Renderer::prepareCommandsSubmission(const QVector<RenderView *> &renderViews) +{ + // TO DO: Find a central place to initialize RHI resources + const int renderViewCount = renderViews.size(); + + // We need to have a single RHI RenderPass per RenderTarget + // as creating the pass clears the buffers + // 1) We need to find all adjacents RenderViews that have the same renderTarget + // and submit all of these as part of the same RHI pass + QVector<RHIPassInfo> rhiPassesInfo; + + for (int i = 0; i < renderViewCount;) { + QVector<RenderView *> sameRenderTargetRVs; + QVector<QRhiBuffer *> rvUbos; + RenderView *refRV = renderViews.at(i); + sameRenderTargetRVs.push_back(refRV); + + for (i = i + 1; i < renderViewCount; ++i) { + RenderView *curRV = renderViews.at(i); + if (refRV->renderTargetId() == curRV->renderTargetId()) { + sameRenderTargetRVs.push_back(curRV); + } else + break; + } + + RHIPassInfo bucket; + bucket.rvs = std::move(sameRenderTargetRVs); + bucket.surface = refRV->surface(); + bucket.renderTargetId = refRV->renderTargetId(); + bucket.attachmentPack = refRV->attachmentPack(); + rhiPassesInfo.push_back(bucket); + } + + for (int i = 0; i < renderViewCount; ++i) { + RenderView *rv = renderViews.at(i); + QVector<RenderCommand> &commands = rv->commands(); + for (RenderCommand &command : commands) { + // Update/Create GraphicsPipelines + 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); + // By this time shaders should have been loaded + RHIShader *shader = m_RHIResourceManagers->rhiShaderManager()->lookupResource( + command.m_shaderId); + if (!shader) { + qDebug() << "Warning: could not find shader"; + continue; + } + + // We should never have inserted a command for which these are null + // in the first place + Q_ASSERT(rGeometry && rGeometryRenderer && shader); + + // 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); + + updateGraphicsPipeline(command, rv, i); + + } else if (command.m_type == RenderCommand::Compute) { + RHI_UNIMPLEMENTED; + // By this time shaders have been loaded + RHIShader *shader = m_RHIResourceManagers->rhiShaderManager()->lookupResource( + command.m_shaderId); + command.m_rhiShader = shader; + Q_ASSERT(shader); + + // Prepare the ShaderParameterPack based on the active uniforms of the shader + // shader->prepareUniforms(command.m_parameterPack); + } + } + } + + // 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 + RHI_UNIMPLEMENTED; + for (Attribute *attribute : qAsConst(m_dirtyAttributes)) + attribute->unsetDirty(); + m_dirtyAttributes.clear(); + + for (Geometry *geometry : qAsConst(m_dirtyGeometry)) + geometry->unsetDirty(); + m_dirtyGeometry.clear(); + + return rhiPassesInfo; +} + +// 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); + } +} + +// Called in prepareSubmission +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(buffer->peerId()); + } +} + +// Executed in a job +void Renderer::lookForDirtyTextures() +{ + // To avoid having Texture or TextureImage maintain relationships between + // one another, we instead perform a lookup here to check if a texture + // image has been updated to then notify textures referencing the image + // that they need to be updated + TextureImageManager *imageManager = m_nodesManager->textureImageManager(); + const QVector<HTextureImage> activeTextureImageHandles = imageManager->activeHandles(); + Qt3DCore::QNodeIdVector dirtyImageIds; + for (const HTextureImage &handle : activeTextureImageHandles) { + TextureImage *image = imageManager->data(handle); + if (image->isDirty()) { + dirtyImageIds.push_back(image->peerId()); + image->unsetDirty(); + } + } + + TextureManager *textureManager = m_nodesManager->textureManager(); + const QVector<HTexture> activeTextureHandles = textureManager->activeHandles(); + for (const HTexture &handle : activeTextureHandles) { + Texture *texture = textureManager->data(handle); + const QNodeIdVector imageIds = texture->textureImageIds(); + + // Does the texture reference any of the dirty texture images? + for (const QNodeId imageId : imageIds) { + if (dirtyImageIds.contains(imageId)) { + texture->addDirtyFlag(Texture::DirtyImageGenerators); + break; + } + } + + // Dirty meaning that something has changed on the texture + // either properties, parameters, shared texture id, generator or a texture image + if (texture->dirtyFlags() != Texture::NotDirty) + m_dirtyTextures.push_back(handle); + // Note: texture dirty flags are reset when actually updating the + // textures in updateGLResources() as resetting flags here would make + // us lose information about what was dirty exactly. + } +} + +// Executed in a job +void Renderer::reloadDirtyShaders() +{ + Q_ASSERT(isRunning()); + const 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 <= QShaderProgram::Compute; i++) { + const auto shaderType = static_cast<QShaderProgram::ShaderType>(i); + if (!shaderBuilder->shaderGraph(shaderType).isValid()) + continue; + + if (shaderBuilder->isShaderCodeDirty(shaderType)) { + shaderBuilder->generateCode(shaderType); + m_shaderBuilderUpdates.append(shaderBuilder->takePendingUpdates()); + } + + const auto code = shaderBuilder->shaderCode(shaderType); + shader->setShaderCode(shaderType, code); + } + } + + if (shader != nullptr && shader->isDirty()) + loadShader(shader, shaderHandle); + } + } + } +} + +// Executed in job (in main thread when jobs are done) +void Renderer::sendShaderChangesToFrontend(Qt3DCore::QAspectManager *manager) +{ + Q_ASSERT(isRunning()); + + // Sync Shader + const QVector<HShader> activeShaders = m_nodesManager->shaderManager()->activeHandles(); + for (const HShader &handle : activeShaders) { + Shader *s = m_nodesManager->shaderManager()->data(handle); + if (s->requiresFrontendSync()) { + QShaderProgram *frontend = + static_cast<decltype(frontend)>(manager->lookupNode(s->peerId())); + QShaderProgramPrivate *dFrontend = + static_cast<decltype(dFrontend)>(QNodePrivate::get(frontend)); + s->unsetRequiresFrontendSync(); + dFrontend->setStatus(s->status()); + dFrontend->setLog(s->log()); + } + } + + // Sync ShaderBuilder + const QVector<ShaderBuilderUpdate> shaderBuilderUpdates = std::move(m_shaderBuilderUpdates); + for (const ShaderBuilderUpdate &update : shaderBuilderUpdates) { + QShaderProgramBuilder *builder = + static_cast<decltype(builder)>(manager->lookupNode(update.builderId)); + QShaderProgramBuilderPrivate *dBuilder = + static_cast<decltype(dBuilder)>(QNodePrivate::get(builder)); + dBuilder->setShaderCode(update.shaderCode, update.shaderType); + } +} + +// Executed in a job (in main thread when jobs are done) +void Renderer::sendTextureChangesToFrontend(Qt3DCore::QAspectManager *manager) +{ + const QVector<QPair<Texture::TextureUpdateInfo, Qt3DCore::QNodeIdVector>> + updateTextureProperties = std::move(m_updatedTextureProperties); + for (const auto &pair : updateTextureProperties) { + const Qt3DCore::QNodeIdVector targetIds = pair.second; + for (const Qt3DCore::QNodeId &targetId : targetIds) { + // Lookup texture + Texture *t = m_nodesManager->textureManager()->lookupResource(targetId); + // If backend texture is Dirty, some property has changed and the properties we are + // about to send are already outdate + if (t == nullptr || t->dirtyFlags() != Texture::NotDirty) + continue; + + QAbstractTexture *texture = + static_cast<QAbstractTexture *>(manager->lookupNode(targetId)); + if (!texture) + continue; + const TextureProperties &properties = pair.first.properties; + + const bool blocked = texture->blockNotifications(true); + texture->setWidth(properties.width); + texture->setHeight(properties.height); + texture->setDepth(properties.depth); + texture->setLayers(properties.layers); + texture->setFormat(properties.format); + texture->blockNotifications(blocked); + + QAbstractTexturePrivate *dTexture = + static_cast<QAbstractTexturePrivate *>(QNodePrivate::get(texture)); + + dTexture->setStatus(properties.status); + dTexture->setHandleType(pair.first.handleType); + dTexture->setHandle(pair.first.handle); + } + } +} + +// Executed in a job (in main thread when jobs done) +void Renderer::sendDisablesToFrontend(Qt3DCore::QAspectManager *manager) +{ + // SubtreeEnabled + const auto updatedDisables = std::move(m_updatedDisableSubtreeEnablers); + for (const auto &nodeId : updatedDisables) { + QSubtreeEnabler *frontend = static_cast<decltype(frontend)>(manager->lookupNode(nodeId)); + frontend->setEnabled(false); + } + + // Compute Commands + const QVector<HComputeCommand> activeCommands = + m_nodesManager->computeJobManager()->activeHandles(); + for (const HComputeCommand &handle : activeCommands) { + ComputeCommand *c = m_nodesManager->computeJobManager()->data(handle); + if (c->hasReachedFrameCount()) { + QComputeCommand *frontend = + static_cast<decltype(frontend)>(manager->lookupNode(c->peerId())); + frontend->setEnabled(false); + c->resetHasReachedFrameCount(); + } + } +} + +// Render Thread (or QtQuick RenderThread when using Scene3D) +// Scene3D: When using Scene3D rendering, we can't assume that when +// updateGLResources is called, the resource handles points to still existing +// objects. This is because Scene3D calls doRender independently of whether all +// jobs have completed or not which in turn calls proceedToNextFrame under some +// conditions. Such conditions are usually met on startup to avoid deadlocks. +// proceedToNextFrame triggers the syncChanges calls for the next frame, which +// may contain destruction changes targeting resources. When the above +// happens, this can result in the dirtyResource vectors containing handles of +// objects that may already have been destroyed +void Renderer::updateResources() +{ + { + const QVector<HBuffer> dirtyBufferHandles = std::move(m_dirtyBuffers); + for (const HBuffer &handle : dirtyBufferHandles) { + Buffer *buffer = m_nodesManager->bufferManager()->data(handle); + + // Can be null when using Scene3D rendering + if (buffer == nullptr) + continue; + + // Forces creation if it doesn't exit + // Also note the binding point doesn't really matter here, we just upload data + if (!m_submissionContext->hasRHIBufferForBuffer(buffer)) + m_submissionContext->rhiBufferForRenderBuffer(buffer); + // Update the RHIBuffer data + m_submissionContext->updateBuffer(buffer); + buffer->unsetDirty(); + } + } + +#ifndef SHADER_LOADING_IN_COMMAND_THREAD + { + const QVector<HShader> dirtyShaderHandles = std::move(m_dirtyShaders); + ShaderManager *shaderManager = m_nodesManager->shaderManager(); + for (const HShader &handle : dirtyShaderHandles) { + Shader *shader = shaderManager->data(handle); + + // Can be null when using Scene3D rendering + if (shader == nullptr) + continue; + + // Compile shader + m_submissionContext->loadShader(shader, shaderManager, + m_RHIResourceManagers->rhiShaderManager()); + } + } +#endif + + { + const QVector<HTexture> activeTextureHandles = std::move(m_dirtyTextures); + for (const HTexture &handle : activeTextureHandles) { + Texture *texture = m_nodesManager->textureManager()->data(handle); + + // Can be null when using Scene3D rendering + if (texture == nullptr) + continue; + + // Create or Update RHITexture (the RHITexture instance is created if required + // and all things that can take place without a GL context are done here) + updateTexture(texture); + } + // We want to upload textures data at this point as the SubmissionThread and + // AspectThread are locked ensuring no races between Texture/TextureImage and + // RHITexture + if (m_submissionContext != nullptr) { + RHITextureManager *rhiTextureManager = m_RHIResourceManagers->rhiTextureManager(); + const QVector<HRHITexture> glTextureHandles = rhiTextureManager->activeHandles(); + // Upload texture data + for (const HRHITexture &glTextureHandle : glTextureHandles) { + RHI_UNIMPLEMENTED; + RHITexture *glTexture = rhiTextureManager->data(glTextureHandle); + + // We create/update the actual GL texture using the GL context at this point + const RHITexture::TextureUpdateInfo info = + glTexture->createOrUpdateRhiTexture(m_submissionContext.data()); + + // RHITexture creation provides us width/height/format ... information + // for textures which had not initially specified these information + // (TargetAutomatic...) Gather these information and store them to be distributed by + // a change next frame + const QNodeIdVector referenceTextureIds = { + rhiTextureManager->texNodeIdForRHITexture.value(glTexture) + }; + // Store properties and referenceTextureIds + if (info.wasUpdated) { + Texture::TextureUpdateInfo updateInfo; + updateInfo.properties = info.properties; + updateInfo.handleType = QAbstractTexture::OpenGLTextureId; + // updateInfo.handle = info.texture ? + // QVariant(info.texture->textureId()) : QVariant(); + m_updatedTextureProperties.push_back({ updateInfo, referenceTextureIds }); + } + } + } + + // Record ids of texture to cleanup while we are still blocking the aspect thread + m_textureIdsToCleanup += m_nodesManager->textureManager()->takeTexturesIdsToCleanup(); + } + + // Record list of buffer that might need uploading + lookForDownloadableBuffers(); +} + +// Render Thread +void Renderer::updateTexture(Texture *texture) +{ + RHI_UNIMPLEMENTED; + // Check that the current texture images are still in place, if not, do not update + const bool isValid = texture->isValid(m_nodesManager->textureImageManager()); + if (!isValid) { + qWarning() << Q_FUNC_INFO << "QTexture referencing invalid QTextureImages"; + return; + } + + // All textures are unique, if you instanciate twice the exact same texture + // this will create 2 identical GLTextures, no sharing will take place + + // Try to find the associated RHITexture for the backend Texture + RHITextureManager *rhiTextureManager = m_RHIResourceManagers->rhiTextureManager(); + RHITexture *rhiTexture = rhiTextureManager->lookupResource(texture->peerId()); + + // No RHITexture associated yet -> create it + if (rhiTexture == nullptr) { + rhiTexture = rhiTextureManager->getOrCreateResource(texture->peerId()); + rhiTextureManager->texNodeIdForRHITexture.insert(rhiTexture, texture->peerId()); + } + + // Update RHITexture to match Texture instance + const Texture::DirtyFlags dirtyFlags = texture->dirtyFlags(); + if (dirtyFlags.testFlag(Texture::DirtySharedTextureId)) + rhiTexture->setSharedTextureId(texture->sharedTextureId()); + + if (dirtyFlags.testFlag(Texture::DirtyProperties)) + rhiTexture->setProperties(texture->properties()); + + if (dirtyFlags.testFlag(Texture::DirtyParameters)) + rhiTexture->setParameters(texture->parameters()); + + // Will make the texture requestUpload + if (dirtyFlags.testFlag(Texture::DirtyImageGenerators)) { + const QNodeIdVector textureImageIds = texture->textureImageIds(); + QVector<RHITexture::Image> images; + images.reserve(textureImageIds.size()); + // TODO: Move this into RHITexture directly + for (const QNodeId textureImageId : textureImageIds) { + const TextureImage *img = + m_nodesManager->textureImageManager()->lookupResource(textureImageId); + if (img == nullptr) { + qWarning() << Q_FUNC_INFO << "invalid TextureImage handle"; + } else { + RHITexture::Image glImg { img->dataGenerator(), img->layer(), img->mipLevel(), + img->face() }; + images.push_back(glImg); + } + } + rhiTexture->setImages(images); + } + + // Will make the texture requestUpload + if (dirtyFlags.testFlag(Texture::DirtyDataGenerator)) + rhiTexture->setGenerator(texture->dataGenerator()); + + // Will make the texture requestUpload + if (dirtyFlags.testFlag(Texture::DirtyPendingDataUpdates)) + rhiTexture->addTextureDataUpdates(texture->takePendingTextureDataUpdates()); + + // Unset the dirty flag on the texture + texture->unsetDirty(); +} + +// Render Thread +void Renderer::cleanupTexture(Qt3DCore::QNodeId cleanedUpTextureId) +{ + RHITextureManager *rhiTextureManager = m_RHIResourceManagers->rhiTextureManager(); + RHITexture *glTexture = rhiTextureManager->lookupResource(cleanedUpTextureId); + + // Destroying the RHITexture implicitely also destroy the GL resources + if (glTexture != nullptr) { + rhiTextureManager->releaseResource(cleanedUpTextureId); + rhiTextureManager->texNodeIdForRHITexture.remove(glTexture); + } +} + +// Render Thread +void Renderer::cleanupShader(const Shader *shader) +{ + RHIShaderManager *rhiShaderManager = m_RHIResourceManagers->rhiShaderManager(); + RHIShader *glShader = rhiShaderManager->lookupResource(shader->peerId()); + + if (glShader != nullptr) + rhiShaderManager->abandon(glShader, shader); +} + +// Called by SubmitRenderView +void Renderer::downloadGLBuffers() +{ + const QVector<Qt3DCore::QNodeId> downloadableHandles = std::move(m_downloadableBuffers); + for (const Qt3DCore::QNodeId &bufferId : downloadableHandles) { + BufferManager *bufferManager = m_nodesManager->bufferManager(); + BufferManager::ReadLocker locker(const_cast<const BufferManager *>(bufferManager)); + Buffer *buffer = bufferManager->lookupResource(bufferId); + // Buffer could have been destroyed at this point + if (!buffer) + continue; + // locker is protecting us from the buffer being destroy while we're looking + // up its content + const QByteArray content = m_submissionContext->downloadBufferContent(buffer); + m_sendBufferCaptureJob->addRequest(QPair<Qt3DCore::QNodeId, QByteArray>(bufferId, content)); + } +} + +// Happens in RenderThread context when all RenderViewJobs are done +// Returns the id of the last bound FBO +Renderer::ViewSubmissionResultData +Renderer::submitRenderViews(const QVector<RHIPassInfo> &rhiPassesInfo) +{ + QElapsedTimer timer; + quint64 queueElapsed = 0; + timer.start(); + + quint64 frameElapsed = queueElapsed; + m_lastFrameCorrect.storeRelaxed(1); // everything fine until now..... + + qCDebug(Memory) << Q_FUNC_INFO << "rendering frame "; + + // We might not want to render on the default FBO + uint lastBoundFBOId = 0; // m_submissionContext->boundFrameBufferObject(); + QSurface *surface = nullptr; + QSurface *previousSurface = nullptr; + QSurface *lastUsedSurface = nullptr; + + const int rhiPassesCount = rhiPassesInfo.size(); + + for (int i = 0; i < rhiPassesCount; ++i) { + // Initialize GraphicsContext for drawing + const RHIPassInfo &rhiPassInfo = rhiPassesInfo.at(i); + + // Initialize Previous surface the first time we enter this loop + if (i == 0) { + for (const RenderView *rv : rhiPassInfo.rvs) { + previousSurface = rv->surface(); + if (previousSurface) + break; + } + } + + // Check if using the same surface as the previous RHIPassInfo. + // If not, we have to free up the context from the previous surface + // and make the context current on the new surface + surface = rhiPassInfo.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 rhipassinfo + // TODO: Investigate if it's worth providing a fallback offscreen surface + // to use when surface is null. Or if we should instead expose an + // offscreensurface to Qt3D. + if (!surface || !surfaceLock.isSurfaceValid()) { + m_lastFrameCorrect.storeRelaxed(0); + continue; + } + + lastUsedSurface = surface; + const bool surfaceHasChanged = surface != previousSurface; + + if (surfaceHasChanged && previousSurface) { + const bool swapBuffers = lastBoundFBOId == m_submissionContext->defaultFBO() + && surfaceLock.isSurfaceValid() && m_shouldSwapBuffers; + // We only call swap buffer if we are sure the previous surface is still valid + m_submissionContext->endDrawing(swapBuffers); + } + + if (surfaceHasChanged) { + // If we can't make the context current on the surface, skip to the + // next RenderView. We won't get the full frame but we may get something + if (!m_submissionContext->beginDrawing(surface)) { + qWarning() << "Failed to make OpenGL context current on surface"; + m_lastFrameCorrect.storeRelaxed(0); + continue; + } + + previousSurface = surface; + // lastBoundFBOId = m_submissionContext->boundFrameBufferObject(); + } + + // Apply Memory Barrier if needed + // if (renderView->memoryBarrier() != QMemoryBarrier::None) + // qWarning() << "RHI Doesn't support MemoryBarrier"; + + // Set RenderTarget ... + // Activate RenderTarget + { + m_submissionContext->activateRenderTarget(rhiPassInfo.renderTargetId, + rhiPassInfo.attachmentPack, lastBoundFBOId); + } + + // Execute the render commands + if (!executeCommandsSubmission(rhiPassInfo)) + m_lastFrameCorrect.storeRelaxed( + 0); // something went wrong; make sure to render the next frame! + + // executeCommandsSubmission takes care of restoring the stateset to the value + // of gc->currentContext() at the moment it was called (either + // renderViewStateSet or m_defaultRenderStateSet) + // if (!renderView->renderCaptureNodeId().isNull()) { + // RHI_UNIMPLEMENTED; + //* const QRenderCaptureRequest request = renderView->renderCaptureRequest(); + //* const QSize size = + //m_submissionContext->renderTargetSize(renderView->surfaceSize()); + //* QRect rect(QPoint(0, 0), size); + //* if (!request.rect.isEmpty()) + //* rect = rect.intersected(request.rect); + //* QImage image; + //* if (!rect.isEmpty()) { + //* // Bind fbo as read framebuffer + //* m_submissionContext->bindFramebuffer(m_submissionContext->activeFBO(), + //GraphicsHelperInterface::FBORead); + //* image = m_submissionContext->readFramebuffer(rect); + //* } else { + //* qWarning() << "Requested capture rectangle is outside framebuffer"; + //* } + //* Render::RenderCapture *renderCapture = + //* + //static_cast<Render::RenderCapture*>(m_nodesManager->frameGraphManager()->lookupNode(renderView->renderCaptureNodeId())); + //* renderCapture->addRenderCapture(request.captureId, image); + //* if + //(!m_pendingRenderCaptureSendRequests.contains(renderView->renderCaptureNodeId())) + //* m_pendingRenderCaptureSendRequests.push_back(renderView->renderCaptureNodeId()); + // } + + // if (renderView->isDownloadBuffersEnable()) + // { + // RHI_UNIMPLEMENTED; + ////* downloadGLBuffers(); + // } + + // // Perform BlitFramebuffer operations + // if (renderView->hasBlitFramebufferInfo()) { + // RHI_UNIMPLEMENTED; + ////* 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 RHI Passes " << i + 1 << "/" + << rhiPassesCount << "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()) { + RHI_UNIMPLEMENTED; + // m_submissionContext->bindFramebuffer(lastBoundFBOId, + // GraphicsHelperInterface::FBOReadAndDraw); + } + + // Reset state and call doneCurrent if the surface + // is valid and was actually activated + if (lastUsedSurface) { + RHI_UNIMPLEMENTED; + // 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 Completed in " << timer.elapsed() << "ms"; + + // Stores the necessary information to safely perform + // the last swap buffer call + ViewSubmissionResultData resultData; + resultData.lastBoundFBOId = lastBoundFBOId; + resultData.surface = lastUsedSurface; + return resultData; +} + +void Renderer::markDirty(BackendNodeDirtySet changes, BackendNode *node) +{ + Q_UNUSED(node) + m_dirtyBits.marked |= changes; +} + +Renderer::BackendNodeDirtySet Renderer::dirtyBits() +{ + return m_dirtyBits.marked; +} + +#if defined(QT_BUILD_INTERNAL) +void Renderer::clearDirtyBits(BackendNodeDirtySet changes) +{ + m_dirtyBits.remaining &= ~changes; + m_dirtyBits.marked &= ~changes; +} +#endif + +bool Renderer::shouldRender() const +{ + // Only render if something changed during the last frame, or the last frame + // was not rendered successfully (or render-on-demand is disabled) + return (m_settings->renderPolicy() == QRenderSettings::Always || m_dirtyBits.marked != 0 + || m_dirtyBits.remaining != 0 || !m_lastFrameCorrect.loadRelaxed()); +} + +void Renderer::skipNextFrame() +{ + Q_ASSERT(m_settings->renderPolicy() != QRenderSettings::Always); + + // make submitRenderViews() actually run + m_renderQueue->setNoRender(); + m_submitRenderViewsSemaphore.release(1); +} + +void Renderer::jobsDone(Qt3DCore::QAspectManager *manager) +{ + // called in main thread once all jobs are done running + + // sync captured renders to frontend + const QVector<Qt3DCore::QNodeId> pendingCaptureIds = + std::move(m_pendingRenderCaptureSendRequests); + for (const Qt3DCore::QNodeId &id : qAsConst(pendingCaptureIds)) { + auto *backend = static_cast<Qt3DRender::Render::RenderCapture *>( + m_nodesManager->frameGraphManager()->lookupNode(id)); + backend->syncRenderCapturesToFrontend(manager); + } + + // Do we need to notify any texture about property changes? + if (m_updatedTextureProperties.size() > 0) + sendTextureChangesToFrontend(manager); + + sendDisablesToFrontend(manager); +} + +void Renderer::setPendingEvents(const QList<QPair<QObject *, QMouseEvent>> &mouseEvents, + const QList<QKeyEvent> &keyEvents) +{ + QMutexLocker l(&m_frameEventsMutex); + m_frameMouseEvents = mouseEvents; + m_frameKeyEvents = keyEvents; +} + +// Jobs we may have to run even if no rendering will happen +QVector<QAspectJobPtr> Renderer::preRenderingJobs() +{ + if (m_sendBufferCaptureJob->hasRequests()) + return { m_sendBufferCaptureJob }; + else + return {}; +} + +// Waits to be told to create jobs for the next frame +// Called by QRenderAspect jobsToExecute context of QAspectThread +// Returns all the jobs (and with proper dependency chain) required +// for the rendering of the scene +QVector<Qt3DCore::QAspectJobPtr> Renderer::renderBinJobs() +{ + QVector<QAspectJobPtr> renderBinJobs; + + // Remove previous dependencies + m_cleanupJob->removeDependency(QWeakPointer<QAspectJob>()); + + const BackendNodeDirtySet dirtyBitsForFrame = m_dirtyBits.marked | m_dirtyBits.remaining; + m_dirtyBits.marked = {}; + m_dirtyBits.remaining = {}; + BackendNodeDirtySet notCleared = {}; + + // Add jobs + if (dirtyBitsForFrame & AbstractRenderer::TransformDirty) { + renderBinJobs.push_back(m_updateShaderDataTransformJob); + } + + // TO DO: Conditionally add if skeletons dirty + renderBinJobs.push_back(m_cleanupJob); + + // Jobs to prepare RHI Resource upload + if (dirtyBitsForFrame & AbstractRenderer::BuffersDirty) + renderBinJobs.push_back(m_bufferGathererJob); + + if (dirtyBitsForFrame & AbstractRenderer::TexturesDirty) + renderBinJobs.push_back(m_textureGathererJob); + + // Layer cache is dependent on layers, layer filters (hence FG structure + // changes) and the enabled flag on entities + const bool entitiesEnabledDirty = dirtyBitsForFrame & AbstractRenderer::EntityEnabledDirty; + const bool frameGraphDirty = dirtyBitsForFrame & AbstractRenderer::FrameGraphDirty; + const bool layersDirty = dirtyBitsForFrame & AbstractRenderer::LayersDirty; + const bool layersCacheNeedsToBeRebuilt = layersDirty || entitiesEnabledDirty || frameGraphDirty; + const bool shadersDirty = dirtyBitsForFrame & AbstractRenderer::ShadersDirty; + const bool materialDirty = dirtyBitsForFrame & AbstractRenderer::MaterialDirty; + const bool lightsDirty = dirtyBitsForFrame & AbstractRenderer::LightsDirty; + const bool computeableDirty = dirtyBitsForFrame & AbstractRenderer::ComputeDirty; + const bool renderableDirty = dirtyBitsForFrame & AbstractRenderer::GeometryDirty; + const bool materialCacheNeedsToBeRebuilt = shadersDirty || materialDirty || frameGraphDirty; + const bool renderCommandsDirty = + materialCacheNeedsToBeRebuilt || renderableDirty || computeableDirty; + + // Rebuild Entity Layers list if layers are dirty + + if (renderableDirty) + renderBinJobs.push_back(m_renderableEntityFilterJob); + + if (computeableDirty) + renderBinJobs.push_back(m_computableEntityFilterJob); + + if (lightsDirty) + renderBinJobs.push_back(m_lightGathererJob); + + 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 + if (frameGraphDirty) { + FrameGraphVisitor visitor(m_nodesManager->frameGraphManager()); + m_frameGraphLeaves = visitor.traverse(frameGraphRoot()); + // Remove leaf nodes that no longer exist from cache + const QList<FrameGraphNode *> keys = m_cache.leafNodeCache.keys(); + for (FrameGraphNode *leafNode : keys) { + if (!m_frameGraphLeaves.contains(leafNode)) + m_cache.leafNodeCache.remove(leafNode); + } + + // Handle single shot subtree enablers + const auto subtreeEnablers = visitor.takeEnablersToDisable(); + for (auto *node : subtreeEnablers) + m_updatedDisableSubtreeEnablers.push_back(node->peerId()); + } + + const int fgBranchCount = m_frameGraphLeaves.size(); + for (int i = 0; i < fgBranchCount; ++i) { + FrameGraphNode *leaf = m_frameGraphLeaves.at(i); + RenderViewBuilder builder(leaf, i, this); + // If we have a new RV (wasn't in the cache before, then it contains no cached data) + const bool isNewRV = !m_cache.leafNodeCache.contains(leaf); + builder.setLayerCacheNeedsToBeRebuilt(layersCacheNeedsToBeRebuilt || isNewRV); + builder.setMaterialGathererCacheNeedsToBeRebuilt(materialCacheNeedsToBeRebuilt + || isNewRV); + builder.setRenderCommandCacheNeedsToBeRebuilt(renderCommandsDirty || isNewRV); + + builder.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::FrameGraphDirty; + 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; +} + +QAbstractFrameAdvanceService *Renderer::frameAdvanceService() const +{ + return static_cast<Qt3DCore::QAbstractFrameAdvanceService *>(m_vsyncFrameAdvanceService.data()); +} + +// Called by executeCommands +void Renderer::performDraw(RenderCommand *command) +{ + QRhiCommandBuffer *cb = m_submissionContext->currentFrameCommandBuffer(); + // Indirect Draw Calls + if (command->m_drawIndirect) { + RHI_UNIMPLEMENTED; + } else { // Direct Draw Calls + + // TO DO: Add glMulti Draw variants + if (command->m_primitiveType == QGeometryRenderer::Patches) { + RHI_UNIMPLEMENTED; + //* m_submissionContext->setVerticesPerPatch(command->m_verticesPerPatch); + } + + if (command->m_primitiveRestartEnabled) { + RHI_UNIMPLEMENTED; + //* m_submissionContext->enablePrimitiveRestart(command->m_restartIndexValue); + } + + // TO DO: Add glMulti Draw variants + if (command->m_drawIndexed) { + cb->drawIndexed(command->m_primitiveCount, command->m_instanceCount, + command->m_indexOffset, command->m_indexAttributeByteOffset, + command->m_firstInstance); + } else { + cb->draw(command->m_primitiveCount, command->m_instanceCount, command->m_firstVertex, + command->m_firstInstance); + } + } + +#if defined(QT3D_RENDER_ASPECT_RHI_DEBUG) + int err = m_submissionContext->openGLContext()->functions()->glGetError(); + if (err) + qCWarning(Rendering) << "GL error after drawing mesh:" << QString::number(err, 16); +#endif + + // if (command->m_primitiveRestartEnabled) + // m_submissionContext->disablePrimitiveRestart(); +} + +void Renderer::performCompute(const RenderView *, RenderCommand *command) +{ + RHI_UNIMPLEMENTED; + //* { + //* RHIShader *shader = + //m_RHIResourceManagers->rhiShaderManager()->lookupResource(command->m_shaderId); + //* m_submissionContext->activateShader(shader); + //* } + //* { + //* m_submissionContext->setParameters(command->m_parameterPack); + //* } + //* { + //* 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_RHI_DEBUG) + //* int err = m_submissionContext->openGLContext()->functions()->glGetError(); + //* if (err) + //* qCWarning(Rendering) << "GL error after drawing mesh:" << QString::number(err, 16); + //* #endif +} + +static auto rhiIndexFormat(QAttribute::VertexBaseType type) +{ + switch (type) { + case QAttribute::VertexBaseType ::UnsignedShort: + return QRhiCommandBuffer::IndexUInt16; + case QAttribute::VertexBaseType ::UnsignedInt: + return QRhiCommandBuffer::IndexUInt32; + default: + std::abort(); + } +} + +bool Renderer::uploadBuffersForCommand(QRhiCommandBuffer *cb, const RenderView *rv, + RenderCommand &command) +{ + RHIGraphicsPipeline *graphicsPipeline = command.pipeline; + if (!graphicsPipeline) + return true; + + // Create the vertex input description + + // Note: we have to bind the buffers here -> which will trigger the actual + // upload, as this is the only place where we know about the usage type of the buffers + + const auto geom = command.m_geometry; + const auto &attributes = geom->attributes(); + const QRhiVertexInputLayout layout = graphicsPipeline->pipeline()->vertexInputLayout(); + const int bindingAttributeCount = std::distance(layout.cbeginBindings(), layout.cendBindings()); + command.vertex_input.resize(bindingAttributeCount); + + for (Qt3DCore::QNodeId attribute_id : attributes) { + // TODO isn't there a more efficient way than doing three hash lookups ? + Attribute *attrib = m_nodesManager->attributeManager()->lookupResource(attribute_id); + Buffer *buffer = m_nodesManager->bufferManager()->lookupResource(attrib->bufferId()); + RHIBuffer *hbuf = + m_RHIResourceManagers->rhiBufferManager()->lookupResource(buffer->peerId()); + switch (attrib->attributeType()) { + case QAttribute::VertexAttribute: { + hbuf->bind(&*m_submissionContext, RHIBuffer::Type::ArrayBuffer); + assert(hbuf->rhiBuffer()); + // Find Binding for Attribute + const int bindingIndex = graphicsPipeline->bindingIndexForAttribute(attrib->nameId()); + // We need to reference a binding, a buffer and an offset which is always = 0 + // as Qt3D only assumes interleaved or continuous data but not + // buffer where first half of it is attribute1 and second half attribute2 + command.vertex_input[bindingIndex] = { hbuf->rhiBuffer(), 0 }; + break; + } + case QAttribute::IndexAttribute: { + hbuf->bind(&*m_submissionContext, RHIBuffer::Type::IndexBuffer); + assert(hbuf->rhiBuffer()); + assert(command.indexBuffer == nullptr); + + command.indexBuffer = hbuf->rhiBuffer(); + command.indexAttribute = attrib; + break; + } + case QAttribute::DrawIndirectAttribute: + RHI_UNIMPLEMENTED; + break; + } + } + + for (const BlockToUBO &pack : command.m_parameterPack.uniformBuffers()) { + Buffer *cpuBuffer = nodeManagers()->bufferManager()->lookupResource(pack.m_bufferID); + RHIBuffer *ubo = m_submissionContext->rhiBufferForRenderBuffer(cpuBuffer); + ubo->bind(&*m_submissionContext, RHIBuffer::UniformBuffer); + } + + return true; +} + +namespace { +void printUpload(const UniformValue &value, const QShaderDescription::BlockVariable &member) +{ + switch (member.type) { + case QShaderDescription::VariableType::Int: + qDebug() << "Updating" << member.name << "with int data: " << *value.constData<int>() + << " (offset: " << member.offset << ", size: " << member.size << ")"; + break; + case QShaderDescription::VariableType::Float: + qDebug() << "Updating" << member.name << "with float data: " << *value.constData<float>() + << " (offset: " << member.offset << ", size: " << member.size << ")"; + break; + case QShaderDescription::VariableType::Vec2: + qDebug() << "Updating" << member.name << "with vec2 data: " << value.constData<float>()[0] + << ", " << value.constData<float>()[1] << " (offset: " << member.offset + << ", size: " << member.size << ")"; + ; + break; + case QShaderDescription::VariableType::Vec3: + qDebug() << "Updating" << member.name << "with vec3 data: " << value.constData<float>()[0] + << ", " << value.constData<float>()[1] << ", " << value.constData<float>()[2] + << " (offset: " << member.offset << ", size: " << member.size << ")"; + ; + break; + case QShaderDescription::VariableType::Vec4: + qDebug() << "Updating" << member.name << "with vec4 data: " << value.constData<float>()[0] + << ", " << value.constData<float>()[1] << ", " << value.constData<float>()[2] + << ", " << value.constData<float>()[3] << " (offset: " << member.offset + << ", size: " << member.size << ")"; + ; + break; + default: + qDebug() << "Updating" << member.name << "with data: " << value.constData<char>(); + break; + } +} + +void uploadUniform(SubmissionContext &submissionContext, const PackUniformHash &uniforms, + const RHIShader::UBO_Member &uboMember, + const QHash<int, RHIGraphicsPipeline::UBOBuffer> &uboBuffers, + const QString &uniformName, const QShaderDescription::BlockVariable &member, + int arrayOffset = 0) +{ + const int uniformNameId = StringToInt::lookupId(uniformName); + + if (!uniforms.contains(uniformNameId)) + return; + + const UniformValue value = uniforms.value(uniformNameId); + const ShaderUniformBlock block = uboMember.block; + + // Update UBO with uniform value + Q_ASSERT(uboBuffers.contains(block.m_binding)); + const RHIGraphicsPipeline::UBOBuffer &ubo = uboBuffers[block.m_binding]; + RHIBuffer *buffer = ubo.buffer; + + // TODO we should maybe have this thread_local to not reallocate memory every time + QByteArray rawData; + rawData.resize(member.size); + memcpy(rawData.data(), value.constData<char>(), std::min(value.byteSize(), member.size)); + buffer->update(&submissionContext, rawData, member.offset + arrayOffset); + + // printUpload(value, member); +} +} + +bool Renderer::uploadUBOsForCommand(QRhiCommandBuffer *cb, const RenderView *rv, + const RenderCommand &command) +{ + RHIGraphicsPipeline *pipeline = command.pipeline; + if (!pipeline) + return true; + + // Upload UBO data for the Command + QRhiBuffer *commandUBO = pipeline->commandUBO(); + m_submissionContext->m_currentUpdates->updateDynamicBuffer(commandUBO, 0, sizeof(CommandUBO), + &command.m_commandUBO); + + // We have to update the RV UBO once per graphics pipeline + QRhiBuffer *rvUBO = pipeline->renderViewUBO(); + m_submissionContext->m_currentUpdates->updateDynamicBuffer(rvUBO, 0, sizeof(RenderViewUBO), + rv->renderViewUBO()); + + // Upload UBO for custom parameters + { + RHIShader *shader = + m_RHIResourceManagers->rhiShaderManager()->lookupResource(command.m_shaderId); + if (!shader) + return true; + + const QVector<RHIShader::UBO_Member> &uboMembers = shader->uboMembers(); + const QHash<int, RHIGraphicsPipeline::UBOBuffer> &uboBuffers = pipeline->ubos(); + const ShaderParameterPack ¶meterPack = command.m_parameterPack; + const PackUniformHash &uniforms = parameterPack.uniforms(); + + // Update Buffer CPU side data based on uniforms being set + for (const RHIShader::UBO_Member &uboMember : uboMembers) { + for (const QShaderDescription::BlockVariable &member : qAsConst(uboMember.members)) { + + if (!member.arrayDims.empty()) { + if (!member.structMembers.empty()) { + const int arr0 = member.arrayDims[0]; + for (int i = 0; i < arr0; i++) { + for (const QShaderDescription::BlockVariable &structMember : + member.structMembers) { + const QString processedName = member.name + "[" + QString::number(i) + + "]." + structMember.name; + uploadUniform(*m_submissionContext, uniforms, uboMember, uboBuffers, + processedName, structMember, i * member.size / arr0); + } + } + } else { + uploadUniform(*m_submissionContext, uniforms, uboMember, uboBuffers, + member.name, member); + } + } else { + uploadUniform(*m_submissionContext, uniforms, uboMember, uboBuffers, + member.name, member); + } + } + } + // Upload changes to GPU Buffer + for (const RHIGraphicsPipeline::UBOBuffer &ubo : uboBuffers) { + // Binding triggers the upload + ubo.buffer->bind(m_submissionContext.data(), RHIBuffer::UniformBuffer); + } + } + return true; +} + +bool Renderer::performDraw(QRhiCommandBuffer *cb, const QRhiViewport &vp, + const QRhiScissor *scissor, const RenderCommand &command) +{ + RHIGraphicsPipeline *pipeline = command.pipeline; + if (!pipeline) + return true; + + // Setup the rendering pass + cb->setGraphicsPipeline(pipeline->pipeline()); + cb->setViewport(vp); + if (scissor) + cb->setScissor(*scissor); + cb->setShaderResources(pipeline->pipeline()->shaderResourceBindings()); + + // Send the draw command + if (Q_UNLIKELY(!command.indexBuffer)) { + cb->setVertexInput(0, command.vertex_input.size(), command.vertex_input.data()); + cb->draw(command.m_primitiveCount, command.m_instanceCount, command.m_firstVertex, + command.m_firstInstance); + } else { + auto indexFormat = rhiIndexFormat(command.indexAttribute->vertexBaseType()); + auto indexOffset = command.indexAttribute->byteOffset(); + cb->setVertexInput(0, command.vertex_input.size(), command.vertex_input.data(), + command.indexBuffer, indexOffset, indexFormat); + cb->drawIndexed(command.m_primitiveCount, command.m_instanceCount, command.m_indexOffset, + command.m_indexAttributeByteOffset, command.m_firstInstance); + } + return true; +} + +// Called by RenderView->submit() in RenderThread context +// Returns true, if all RenderCommands were sent to the GPU +bool Renderer::executeCommandsSubmission(const RHIPassInfo &passInfo) +{ + bool allCommandsIssued = true; + + const QVector<RenderView *> &renderViews = passInfo.rvs; + QColor clearColor; + QRhiDepthStencilClearValue clearDepthStencil; + + // Submit the commands to the underlying graphics API (RHI) + QRhiCommandBuffer *cb = m_submissionContext->currentFrameCommandBuffer(); + + // Upload data for all RenderCommands + for (RenderView *rv : renderViews) { + // Render drawing commands + + QVector<RenderCommand> &commands = rv->commands(); + + // Upload all the required data to rhi... + for (RenderCommand &command : commands) { + if (command.m_type == RenderCommand::Draw) { + uploadBuffersForCommand(cb, rv, command); + uploadUBOsForCommand(cb, rv, command); + } + } + + // Record clear information + if (rv->clearTypes() != QClearBuffers::None) { + clearColor = [=] { + auto col = rv->globalClearColorBufferInfo().clearColor; + return QColor::fromRgbF(col.x(), col.y(), col.z(), col.w()); + }(); + clearDepthStencil = { rv->clearDepthValue(), (quint32)rv->clearStencilValue() }; + } + } + // TO DO: should be moved elsewhere + // Perform compute actions + // cb->beginComputePass(m_submissionContext->m_currentUpdates); + // for (RenderCommand &command : commands) { + // if (command.m_type == RenderCommand::Compute) { + // performCompute(rv, &command); + // } + // } + // cb->endComputePass(); + // m_submissionContext->m_currentUpdates = + // m_submissionContext->rhi()->nextResourceUpdateBatch(); + + // Draw the commands + + // TO DO: Retrieve real renderTarget for RHIPassInfo + QRhiRenderTarget *renderTarget = m_submissionContext->currentFrameRenderTarget(); + + // Begin pass + cb->beginPass(renderTarget, clearColor, clearDepthStencil, + m_submissionContext->m_currentUpdates); + + // Per Pass Global States + for (RenderView *rv : renderViews) { + // Viewport + QRhiViewport vp; + QRhiScissor scissor; + bool hasScissor = false; + { + const float x = rv->viewport().x() * rv->surfaceSize().width(); + const float y = (1. - rv->viewport().y() - rv->viewport().height()) + * rv->surfaceSize().height(); + const float w = rv->viewport().width() * rv->surfaceSize().width(); + const float h = rv->viewport().height() * rv->surfaceSize().height(); + // qDebug() << x << y << w << h; + vp = { x, y, w, h }; + } + // Scissoring + { + RenderStateSet *ss = rv->stateSet(); + if (ss == nullptr) + ss = m_defaultRenderStateSet; + StateVariant *scissorTestSVariant = + m_submissionContext->getState(ss, StateMask::ScissorStateMask); + if (scissorTestSVariant) { + const ScissorTest *scissorTest = + static_cast<const ScissorTest *>(scissorTestSVariant->constState()); + const auto &scissorValues = scissorTest->values(); + scissor = { std::get<0>(scissorValues), std::get<1>(scissorValues), + std::get<2>(scissorValues), std::get<3>(scissorValues) }; + hasScissor = true; + } + } + + // Render drawing commands + const QVector<RenderCommand> &commands = rv->commands(); + + for (const RenderCommand &command : commands) { + if (command.m_type == RenderCommand::Draw) { + performDraw(cb, vp, hasScissor ? &scissor : nullptr, command); + } + } + } + + cb->endPass(); + m_submissionContext->m_currentUpdates = m_submissionContext->rhi()->nextResourceUpdateBatch(); + + return allCommandsIssued; +} + +// Erase graphics related resources that may become unused after a frame +void Renderer::cleanGraphicsResources() +{ + // Remove unused GraphicsPipeline + RHIGraphicsPipelineManager *pipelineManager = + m_RHIResourceManagers->rhiGraphicsPipelineManager(); + const QVector<HRHIGraphicsPipeline> graphicsPipelinesHandles = pipelineManager->activeHandles(); + for (HRHIGraphicsPipeline pipelineHandle : graphicsPipelinesHandles) { + RHIGraphicsPipeline *pipeline = pipelineManager->data(pipelineHandle); + pipeline->decreaseScore(); + // Pipeline wasn't used recently, let's destroy it + if (pipeline->score() < 0) { + pipeline->cleanup(); + } + } + + // Clean buffers + const QVector<Qt3DCore::QNodeId> buffersToRelease = + m_nodesManager->bufferManager()->takeBuffersToRelease(); + for (Qt3DCore::QNodeId bufferId : buffersToRelease) + m_submissionContext->releaseBuffer(bufferId); + + // When Textures are cleaned up, their id is saved so that they can be + // cleaned up in the render thread + const QVector<Qt3DCore::QNodeId> cleanedUpTextureIds = std::move(m_textureIdsToCleanup); + for (const Qt3DCore::QNodeId textureCleanedUpId : cleanedUpTextureIds) + cleanupTexture(textureCleanedUpId); + + // Abandon GL shaders when a Shader node is destroyed Note: We are sure + // that when this gets executed, all scene changes have been received and + // shader nodes updated + const QVector<Qt3DCore::QNodeId> cleanedUpShaderIds = + m_nodesManager->shaderManager()->takeShaderIdsToCleanup(); + for (const Qt3DCore::QNodeId shaderCleanedUpId : cleanedUpShaderIds) { + cleanupShader(m_nodesManager->shaderManager()->lookupResource(shaderCleanedUpId)); + // We can really release the texture at this point + m_nodesManager->shaderManager()->releaseResource(shaderCleanedUpId); + } +} + +const GraphicsApiFilterData *Renderer::contextInfo() const +{ + return m_submissionContext->contextInfo(); +} + +SubmissionContext *Renderer::submissionContext() const +{ + return m_submissionContext.data(); +} + +} // namespace Rhi +} // namespace Render +} // namespace Qt3DRender + +QT_END_NAMESPACE diff --git a/src/plugins/renderers/rhi/renderer/renderer.pri b/src/plugins/renderers/rhi/renderer/renderer.pri new file mode 100644 index 000000000..4ec8cca9b --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/renderer.pri @@ -0,0 +1,27 @@ +INCLUDEPATH += $$PWD + +SOURCES += \ + $$PWD/rendercommand.cpp \ + $$PWD/renderer.cpp \ + $$PWD/renderqueue.cpp \ + $$PWD/renderview.cpp \ + $$PWD/renderviewbuilder.cpp \ + $$PWD/rhigraphicspipeline.cpp \ + $$PWD/rhishader.cpp \ + $$PWD/shaderparameterpack.cpp \ + $$PWD/logging.cpp \ + $$PWD/commandexecuter.cpp + +HEADERS += \ + $$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/rhigraphicspipeline_p.h \ + $$PWD/rhishader_p.h \ + $$PWD/shaderparameterpack_p.h \ + $$PWD/shadervariables_p.h \ + $$PWD/logging_p.h \ + $$PWD/commandexecuter_p.h diff --git a/src/plugins/renderers/rhi/renderer/renderer_p.h b/src/plugins/renderers/rhi/renderer/renderer_p.h new file mode 100644 index 000000000..8cceca801 --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/renderer_p.h @@ -0,0 +1,443 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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_RHI_RENDERER_H +#define QT3DRENDER_RENDER_RHI_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/handle_types_p.h> +#include <Qt3DRender/private/abstractrenderer_p.h> +#include <Qt3DCore/qaspectjob.h> +#include <Qt3DRender/private/qt3drender_global_p.h> +#include <Qt3DRender/private/rendersettings_p.h> +#include <Qt3DRender/private/updateshaderdatatransformjob_p.h> +#include <Qt3DRender/private/framecleanupjob_p.h> +#include <Qt3DRender/private/platformsurfacefilter_p.h> +#include <Qt3DRender/private/sendbuffercapturejob_p.h> +#include <Qt3DRender/private/genericlambdajob_p.h> +#include <Qt3DRender/private/shaderbuilder_p.h> +#include <Qt3DRender/private/lightgatherer_p.h> +#include <Qt3DRender/private/texture_p.h> +#include <Qt3DRender/private/attachmentpack_p.h> +#include <Qt3DRender/private/filterentitybycomponentjob_p.h> + +#include <QtGui/private/qrhi_p.h> + +#include <shaderparameterpack_p.h> +#include <renderviewinitializerjob_p.h> +#include <filtercompatibletechniquejob_p.h> +#include <renderercache_p.h> +#include <logging_p.h> +#include <rhihandle_types_p.h> +#include <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> + +#if defined(QT_BUILD_INTERNAL) +class tst_Renderer; +#endif + +QT_BEGIN_NAMESPACE + +class QSurface; +class QMouseEvent; +class QScreen; + +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; + +namespace Debug { +class CommandExecuter; +} + +namespace Render { + +class CameraLens; +class FrameGraphNode; +class Material; +class Technique; +class Shader; +class Entity; +class Effect; +class RenderPass; +class RenderThread; +class RenderStateSet; +class VSyncFrameAdvanceService; +class NodeManagers; +class ResourceAccessor; + +using ComputableEntityFilter = FilterEntityByComponentJob<Render::ComputeCommand, Render::Material>; +using ComputableEntityFilterPtr = QSharedPointer<ComputableEntityFilter>; +using RenderableEntityFilter = + FilterEntityByComponentJob<Render::GeometryRenderer, Render::Material>; +using RenderableEntityFilterPtr = QSharedPointer<RenderableEntityFilter>; + +using SynchronizerJobPtr = GenericLambdaJobPtr<std::function<void()>>; +using SynchronizerPostFramePtr = + GenericLambdaJobAndPostFramePtr<std::function<void()>, + std::function<void(Qt3DCore::QAspectManager *)>>; + +namespace Rhi { + +class CommandThread; +class SubmissionContext; +class RenderCommand; +class RenderQueue; +class RenderView; +class RHIShader; +class RHIResourceManagers; + +class Q_AUTOTEST_EXPORT Renderer : public AbstractRenderer +{ +public: + explicit Renderer(QRenderAspect::RenderType type); + ~Renderer(); + + void dumpInfo() const override; + API api() const override; + + qint64 time() const override; + void setTime(qint64 time) override; + void setJobsInLastFrame(int jobsInLastFrame) override; + + void setAspect(QRenderAspect *aspect) override; + void setNodeManagers(NodeManagers *managers) override; + void setServices(Qt3DCore::QServiceLocator *services) override; + void setSurfaceExposed(bool exposed) override; + + QRenderAspect *aspect() const 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 swapBuffers = true) override; + void cleanGraphicsResources() override; + + bool isRunning() const override { return m_running.loadRelaxed(); } + + void setSceneRoot(Entity *sgRoot) override; + Entity *sceneRoot() const override { return m_renderSceneRoot; } + + FrameGraphNode *frameGraphRoot() const override; + RenderQueue *renderQueue() const { return m_renderQueue; } + + void markDirty(BackendNodeDirtySet changes, BackendNode *node) override; + BackendNodeDirtySet dirtyBits() override; + +#if defined(QT_BUILD_INTERNAL) + void clearDirtyBits(BackendNodeDirtySet changes) override; +#endif + bool shouldRender() const override; + void skipNextFrame() override; + void jobsDone(Qt3DCore::QAspectManager *manager) override; + + void setPendingEvents(const QList<QPair<QObject *, QMouseEvent>> &mouseEvents, + const QList<QKeyEvent> &keyEvents) override; + + QVector<Qt3DCore::QAspectJobPtr> preRenderingJobs() override; + QVector<Qt3DCore::QAspectJobPtr> renderBinJobs() override; + + inline FrameCleanupJobPtr frameCleanupJob() const { return m_cleanupJob; } + inline UpdateShaderDataTransformJobPtr updateShaderDataTransformJob() const + { + return m_updateShaderDataTransformJob; + } + inline FilterCompatibleTechniqueJobPtr filterCompatibleTechniqueJob() const + { + return m_filterCompatibleTechniqueJob; + } + inline SynchronizerPostFramePtr introspectShadersJob() const { return m_introspectShaderJob; } + inline Qt3DCore::QAspectJobPtr bufferGathererJob() const { return m_bufferGathererJob; } + inline Qt3DCore::QAspectJobPtr textureGathererJob() const { return m_textureGathererJob; } + inline LightGathererPtr lightGathererJob() const { return m_lightGathererJob; } + inline RenderableEntityFilterPtr renderableEntityFilterJob() const + { + return m_renderableEntityFilterJob; + } + inline ComputableEntityFilterPtr computableEntityFilterJob() const + { + return m_computableEntityFilterJob; + } + + Qt3DCore::QAbstractFrameAdvanceService *frameAdvanceService() const override; + + void setSettings(RenderSettings *settings) override; + RenderSettings *settings() const override; + QOpenGLContext *shareContext() const override; + + inline RHIResourceManagers *rhiResourceManagers() const { return m_RHIResourceManagers; } + + // Executed in secondary GL thread + void loadShader(Shader *shader, Qt3DRender::Render::HShader shaderHandle) override; + + void updateResources(); + void updateTexture(Texture *texture); + void cleanupTexture(Qt3DCore::QNodeId cleanedUpTextureId); + void cleanupShader(const Shader *shader); + void downloadGLBuffers(); + void blitFramebuffer(Qt3DCore::QNodeId inputRenderTargetId, + Qt3DCore::QNodeId outputRenderTargetId, QRect inputRect, QRect outputRect, + GLuint defaultFramebuffer); + + struct RHIPassInfo + { + QVector<RenderView *> rvs; + QSurface *surface = nullptr; + Qt3DCore::QNodeId renderTargetId; + AttachmentPack attachmentPack; + }; + + QVector<RHIPassInfo> prepareCommandsSubmission(const QVector<RenderView *> &renderViews); + bool executeCommandsSubmission(const RHIPassInfo &passInfo); + + // For Scene2D rendering + void setOpenGLContext(QOpenGLContext *context) override; + bool accessOpenGLTexture(Qt3DCore::QNodeId nodeId, QOpenGLTexture **texture, QMutex **lock, + bool readonly) override; + QSharedPointer<RenderBackendResourceAccessor> resourceAccessor() const override; + + 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 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<RHIPassInfo> &rhiPassesInfo); + + RendererCache *cache() { return &m_cache; } + void setScreen(QScreen *scr) override; + QScreen *screen() const override; + + float *textureTransform() noexcept { return m_textureTransform; } + const float *textureTransform() const noexcept { return m_textureTransform; } +#ifdef QT3D_RENDER_UNIT_TESTS +public: +#else + +private: +#endif + bool canRender() const; + + Qt3DCore::QServiceLocator *m_services; + QRenderAspect *m_aspect; + 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; + + RenderQueue *m_renderQueue; + QScopedPointer<RenderThread> m_renderThread; + QScopedPointer<VSyncFrameAdvanceService> m_vsyncFrameAdvanceService; + + QSemaphore m_submitRenderViewsSemaphore; + QSemaphore m_waitForInitializationToBeCompleted; + QMutex m_hasBeenInitializedMutex; + + QAtomicInt m_running; + + QVector<Attribute *> m_dirtyAttributes; + QVector<Geometry *> m_dirtyGeometry; + QAtomicInt m_exposed; + + struct DirtyBits + { + BackendNodeDirtySet marked; // marked dirty since last job build + BackendNodeDirtySet remaining; // remaining dirty after jobs have finished + }; + DirtyBits m_dirtyBits; + + QAtomicInt m_lastFrameCorrect; + QOpenGLContext *m_glContext; + + qint64 m_time; + + RenderSettings *m_settings; + + UpdateShaderDataTransformJobPtr m_updateShaderDataTransformJob; + FrameCleanupJobPtr m_cleanupJob; + SendBufferCaptureJobPtr m_sendBufferCaptureJob; + FilterCompatibleTechniqueJobPtr m_filterCompatibleTechniqueJob; + LightGathererPtr m_lightGathererJob; + RenderableEntityFilterPtr m_renderableEntityFilterJob; + ComputableEntityFilterPtr m_computableEntityFilterJob; + + QVector<Qt3DCore::QNodeId> m_pendingRenderCaptureSendRequests; + + void performDraw(RenderCommand *command); + void performCompute(const RenderView *rv, RenderCommand *command); + + SynchronizerJobPtr m_bufferGathererJob; + SynchronizerJobPtr m_textureGathererJob; + SynchronizerPostFramePtr m_introspectShaderJob; + + void lookForDirtyBuffers(); + void lookForDownloadableBuffers(); + void lookForDirtyTextures(); + void reloadDirtyShaders(); + void sendShaderChangesToFrontend(Qt3DCore::QAspectManager *manager); + void sendTextureChangesToFrontend(Qt3DCore::QAspectManager *manager); + void sendSetFenceHandlesToFrontend(); + void sendDisablesToFrontend(Qt3DCore::QAspectManager *manager); + + QVector<HBuffer> m_dirtyBuffers; + QVector<Qt3DCore::QNodeId> m_downloadableBuffers; + QVector<HShader> m_dirtyShaders; + QVector<HTexture> m_dirtyTextures; + QVector<QPair<Texture::TextureUpdateInfo, Qt3DCore::QNodeIdVector>> m_updatedTextureProperties; + QVector<Qt3DCore::QNodeId> m_updatedDisableSubtreeEnablers; + Qt3DCore::QNodeIdVector m_textureIdsToCleanup; + QVector<ShaderBuilderUpdate> m_shaderBuilderUpdates; + + bool m_ownedContext; + + OffscreenSurfaceHelper *m_offscreenHelper; + RHIResourceManagers *m_RHIResourceManagers; + QMutex m_offscreenSurfaceMutex; + + QScopedPointer<Qt3DRender::Debug::CommandExecuter> m_commandExecuter; + +#ifdef QT_BUILD_INTERNAL + friend class ::tst_Renderer; +#endif + + QMetaObject::Connection m_contextConnection; + RendererCache m_cache; + bool m_shouldSwapBuffers; + + QVector<FrameGraphNode *> m_frameGraphLeaves; + QScreen *m_screen = nullptr; + QSharedPointer<ResourceAccessor> m_scene2DResourceAccessor; + + QOffscreenSurface *m_fallbackSurface {}; + + bool m_hasSwapChain = false; + + QList<QPair<QObject *, QMouseEvent>> m_frameMouseEvents; + QList<QKeyEvent> m_frameKeyEvents; + QMutex m_frameEventsMutex; + int m_jobsInLastFrame = 0; + + float m_textureTransform[4]; + + void updateGraphicsPipeline(RenderCommand &command, RenderView *rv, int renderViewIndex); + bool uploadBuffersForCommand(QRhiCommandBuffer *cb, const RenderView *rv, + RenderCommand &command); + bool uploadUBOsForCommand(QRhiCommandBuffer *cb, const RenderView *rv, + const RenderCommand &command); + bool performDraw(QRhiCommandBuffer *cb, const QRhiViewport &vp, const QRhiScissor *scissor, + const RenderCommand &command); +}; + +} // namespace Rhi +} // namespace Render +} // namespace Qt3DRender + +QT_END_NAMESPACE + +#endif // QT3DRENDER_RENDER_RHI_RENDERER_H diff --git a/src/plugins/renderers/rhi/renderer/renderercache_p.h b/src/plugins/renderers/rhi/renderer/renderercache_p.h new file mode 100644 index 000000000..ec3223dd2 --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/renderercache_p.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** 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_RHI_RENDERERCACHE_P_H +#define QT3DRENDER_RENDER_RHI_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 <renderviewjobutils_p.h> +#include <Qt3DRender/private/lightsource_p.h> +#include <rendercommand_p.h> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { + +namespace Render { + +namespace Rhi { + +struct RendererCache +{ + struct LeafNodeData + { + QVector<Entity *> filterEntitiesByLayer; + MaterialParameterGathererData materialParameterGatherer; + EntityRenderCommandData renderCommandData; + }; + + // Shared amongst all RV cache + QVector<Entity *> renderableEntities; + QVector<Entity *> computeEntities; + QVector<LightSource> gatheredLights; + EnvironmentLight *environmentLight; + + // Per RV cache + QHash<FrameGraphNode *, LeafNodeData> leafNodeCache; + + QMutex *mutex() { return &m_mutex; } + +private: + QMutex m_mutex; +}; + +} // namespace Rhi + +} // namespace Render + +} // namespace Qt3DRender + +QT_END_NAMESPACE + +#endif // QT3DRENDER_RENDER_RHI_RENDERERCACHE_P_H diff --git a/src/plugins/renderers/rhi/renderer/renderqueue.cpp b/src/plugins/renderers/rhi/renderer/renderqueue.cpp new file mode 100644 index 000000000..39eaddc9a --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/renderqueue.cpp @@ -0,0 +1,138 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 <renderview_p.h> +#include <QThread> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { + +namespace Render { + +namespace Rhi { + +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 Rhi + +} // namespace Render + +} // namespace Qt3DRender + +QT_END_NAMESPACE diff --git a/src/plugins/renderers/rhi/renderer/renderqueue_p.h b/src/plugins/renderers/rhi/renderer/renderqueue_p.h new file mode 100644 index 000000000..3b6eec13d --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/renderqueue_p.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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_RHI_RENDERQUEUE_H +#define QT3DRENDER_RENDER_RHI_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 { + +namespace Rhi { + +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 Rhi + +} // namespace Render + +} // namespace Qt3DRender + +QT_END_NAMESPACE + +#endif // QT3DRENDER_RENDER_RHI_RENDERQUEUE_H diff --git a/src/plugins/renderers/rhi/renderer/renderview.cpp b/src/plugins/renderers/rhi/renderer/renderview.cpp new file mode 100644 index 000000000..54295ef5f --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/renderview.cpp @@ -0,0 +1,1253 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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/effect_p.h> +#include <Qt3DRender/private/entity_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/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 <Qt3DRender/private/renderlogging_p.h> +#include <Qt3DRender/private/renderstateset_p.h> +#include <rendercommand_p.h> +#include <renderer_p.h> +#include <submissioncontext_p.h> +#include <rhiresourcemanagers_p.h> +#include <Qt3DCore/qentity.h> +#include <QtGui/qsurface.h> +#include <algorithm> +#include <atomic> +#include <cstdlib> +#include <QDebug> +#if defined(QT3D_RENDER_VIEW_JOB_TIMINGS) +#include <QElapsedTimer> +#endif + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { +namespace Render { +namespace Rhi { + +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]; + +std::atomic_bool wasInitialized {}; + +} // anonymous namespace + +// 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, bool yIsUp) +{ + if (lens) { + if (yIsUp) { + // OpenGL + return lens->projection(); + } else { + // Others. Note : this could likely be optimized... + auto p = lens->projection(); + Matrix4x4 rev { 0, 0, 0, 0, 0, -2 * p.m22(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + p += rev; + return p; + } + } else { + qWarning() << "[Qt3D Renderer] No Camera Lens found. Add a CameraSelector to your Frame " + "Graph or make sure that no entities will be rendered."; + return Matrix4x4(); + } +} + +RenderView::RenderView() + : m_isDownloadBuffersEnable(false), + m_hasBlitFramebufferInfo(false), + m_renderer(nullptr), + m_manager(nullptr), + m_devicePixelRatio(1.), + m_viewport(QRectF(0., 0., 1., 1.)), + m_gamma(2.2f), + m_surface(nullptr), + m_clearBuffer(QClearBuffers::None), + m_clearDepthValue(1.f), + m_clearStencilValue(0), + m_stateSet(nullptr), + m_noDraw(false), + m_compute(false), + m_frustumCulling(false), + m_environmentLight(nullptr) +{ + m_workGroups[0] = 1; + m_workGroups[1] = 1; + m_workGroups[2] = 1; + + if (Q_UNLIKELY(!wasInitialized.exchange(true))) { + // Needed as we can control the init order of static/global variables across compile units + // and this hash relies on the static StringToInt class + + 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; +} + +namespace { + +template<int SortType> +struct AdjacentSubRangeFinder +{ + static bool adjacentSubRange(const RenderCommand &, const RenderCommand &) + { + Q_UNREACHABLE(); + return false; + } +}; + +template<> +struct AdjacentSubRangeFinder<QSortPolicy::StateChangeCost> +{ + static bool adjacentSubRange(const RenderCommand &a, const RenderCommand &b) + { + return a.m_changeCost == b.m_changeCost; + } +}; + +template<> +struct AdjacentSubRangeFinder<QSortPolicy::BackToFront> +{ + static bool adjacentSubRange(const RenderCommand &a, const RenderCommand &b) + { + return a.m_depth == b.m_depth; + } +}; + +template<> +struct AdjacentSubRangeFinder<QSortPolicy::Material> +{ + static bool adjacentSubRange(const RenderCommand &a, const RenderCommand &b) + { + return a.m_rhiShader == b.m_rhiShader; + } +}; + +template<> +struct AdjacentSubRangeFinder<QSortPolicy::FrontToBack> +{ + static bool adjacentSubRange(const RenderCommand &a, const RenderCommand &b) + { + return a.m_depth == b.m_depth; + } +}; + +template<> +struct AdjacentSubRangeFinder<QSortPolicy::Texture> +{ + static bool adjacentSubRange(const RenderCommand &a, const RenderCommand &b) + { + // Two renderCommands are adjacent if one contains all the other command's textures + QVector<ShaderParameterPack::NamedResource> texturesA = a.m_parameterPack.textures(); + QVector<ShaderParameterPack::NamedResource> texturesB = b.m_parameterPack.textures(); + + if (texturesB.size() > texturesA.size()) + qSwap(texturesA, texturesB); + + // textureB.size() is always <= textureA.size() + for (const ShaderParameterPack::NamedResource &texB : qAsConst(texturesB)) { + if (!texturesA.contains(texB)) + return false; + } + return true; + } +}; + +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, [](const RenderCommand &a, const 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, [](const RenderCommand &a, const 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 shader + std::stable_sort(begin, end, [](const RenderCommand &a, const RenderCommand &b) { + return a.m_rhiShader > b.m_rhiShader; + }); + } +}; + +template<> +struct SubRangeSorter<QSortPolicy::FrontToBack> +{ + static void sortSubRange(CommandIt begin, const CommandIt end) + { + std::stable_sort(begin, end, [](const RenderCommand &a, const RenderCommand &b) { + return a.m_depth < b.m_depth; + }); + } +}; + +template<> +struct SubRangeSorter<QSortPolicy::Texture> +{ + static void sortSubRange(CommandIt begin, const CommandIt end) + { +#ifndef Q_OS_WIN + std::stable_sort(begin, end, [] (const RenderCommand &a, const RenderCommand &b) { + QVector<ShaderParameterPack::NamedResource> texturesA = a.m_parameterPack.textures(); + QVector<ShaderParameterPack::NamedResource> texturesB = b.m_parameterPack.textures(); + + const int originalTextureASize = texturesA.size(); + + if (texturesB.size() > texturesA.size()) + qSwap(texturesA, texturesB); + + int identicalTextureCount = 0; + + for (const ShaderParameterPack::NamedResource &texB : qAsConst(texturesB)) { + if (texturesA.contains(texB)) + ++identicalTextureCount; + } + + return identicalTextureCount < originalTextureASize; + }); +#endif + } +}; + +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); + case QSortPolicy::Texture: + return advanceUntilNonAdjacent( + commands, begin, end, + AdjacentSubRangeFinder<QSortPolicy::Texture>::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, + [](const RenderCommand &a, const 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; + case QSortPolicy::Texture: + SubRangeSorter<QSortPolicy::Texture>::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() +{ + // Compares the bitsetKey of the RenderCommands + // Key[Depth | StateCost | Shader] + 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; + const int commandSize = m_commands.size(); + while (i < commandSize) { + int j = i; + + // Advance while commands share the same shader + while (i < commandSize && + m_commands[j].m_rhiShader == m_commands[i].m_rhiShader) + ++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[j].m_parameterPack.m_uniforms; + + for (int u = 0; u < uniforms.keys.size();) { + // 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 int uniformNameId = uniforms.keys.at(u); + const UniformValue &refValue = cachedUniforms.value(uniformNameId); + const UniformValue &newValue = uniforms.values.at(u); + if (newValue == refValue) { + uniforms.erase(u); + } else { + // Record updated value so that subsequent comparison + // for the next command will be made againts latest + // uniform value + cachedUniforms.insert(uniformNameId, newValue); + ++u; + } + } + ++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 +EntityRenderCommandData RenderView::buildDrawRenderCommands(const QVector<Entity *> &entities, + int offset, int count) const +{ + EntityRenderCommandData commands; + RHIShaderManager *rhiShaderManager = m_renderer->rhiResourceManagers()->rhiShaderManager(); + + commands.reserve(count); + + for (int i = 0; i < count; ++i) { + const int idx = offset + i; + Entity *entity = entities.at(idx); + 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->geometryManager()->lookupHandle(geometryRenderer->geometryId()); + Geometry *geometry = m_manager->geometryManager()->data(geometryHandle); + + if (geometry == nullptr) + continue; + + // 1 RenderCommand per RenderPass pass on an Entity with a Mesh + for (const RenderPassParameterData &passData : renderPassData) { + // Add the RenderPass Parameters + RenderCommand command = {}; + command.m_geometryRenderer = geometryRendererHandle; + command.m_geometry = geometryHandle; + + 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 = RenderStateSetPtr::create(); + addStatesToRenderStateSet(command.m_stateSet.data(), pass->renderStates(), + m_manager->renderStateManager()); + if (m_stateSet != nullptr) + command.m_stateSet->merge(m_stateSet); + command.m_changeCost = + m_renderer->defaultRenderState()->changeCost(command.m_stateSet.data()); + } + command.m_shaderId = pass->shaderProgram(); + command.m_rhiShader = rhiShaderManager->lookupResource(command.m_shaderId); + + // It takes two frames to have a valid command as we can only + // reference a glShader at frame n if it has been loaded at frame n - 1 + + if (!command.m_shaderId) + continue; + + { // Scoped to show extent + + // Update the draw command with what's going to be needed for the drawing + int primitiveCount = geometryRenderer->vertexCount(); + int estimatedCount = 0; + Attribute *indexAttribute = nullptr; + Attribute *indirectAttribute = nullptr; + + const QVector<Qt3DCore::QNodeId> attributeIds = geometry->attributes(); + for (Qt3DCore::QNodeId attributeId : attributeIds) { + Attribute *attribute = + m_manager->attributeManager()->lookupResource(attributeId); + switch (attribute->attributeType()) { + case QAttribute::IndexAttribute: + indexAttribute = attribute; + break; + case QAttribute::DrawIndirectAttribute: + indirectAttribute = attribute; + break; + case QAttribute::VertexAttribute: + estimatedCount = std::max(int(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 = indexAttribute->vertexBaseType(); + command.m_indexAttributeByteOffset = indexAttribute->byteOffset() + + geometryRenderer->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_manager->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 = 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(); + } // scope + + commands.push_back(entity, std::move(command), passData); + } + } + } + + return commands; +} + +EntityRenderCommandData RenderView::buildComputeRenderCommands(const QVector<Entity *> &entities, + int offset, int count) const +{ + // 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/ + EntityRenderCommandData commands; + RHIShaderManager *rhiShaderManager = m_renderer->rhiResourceManagers()->rhiShaderManager(); + + commands.reserve(count); + + for (int i = 0; i < count; ++i) { + const int idx = offset + i; + Entity *entity = entities.at(idx); + ComputeCommand *computeJob = nullptr; + HComputeCommand computeCommandHandle = entity->componentHandle<ComputeCommand>(); + if ((computeJob = nodeManagers()->computeJobManager()->data(computeCommandHandle)) + != 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 + RenderCommand command = {}; + RenderPass *pass = passData.pass; + + if (pass->hasRenderStates()) { + command.m_stateSet = RenderStateSetPtr::create(); + addStatesToRenderStateSet(command.m_stateSet.data(), 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.data()); + } + command.m_shaderId = pass->shaderProgram(); + command.m_rhiShader = rhiShaderManager->lookupResource(command.m_shaderId); + + // It takes two frames to have a valid command as we can only + // reference a glShader at frame n if it has been loaded at frame n - 1 + assert(command.m_rhiShader); + if (!command.m_rhiShader) + continue; + + command.m_computeCommand = computeCommandHandle; + 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()); + + commands.push_back(entity, std::move(command), passData); + } + } + } + + return commands; +} + +void RenderView::updateRenderCommand(EntityRenderCommandData *renderCommandData, int offset, + int count) +{ + // 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); + + // Update RenderViewUBO (Qt3D standard uniforms) + const bool yIsUp = m_renderer->submissionContext()->rhi()->isYUpInFramebuffer(); + const Matrix4x4 projectionMatrix = getProjectionMatrix(m_data.m_renderCameraLens, yIsUp); + const Matrix4x4 inverseViewMatrix = m_data.m_viewMatrix.inverted(); + const Matrix4x4 inversedProjectionMatrix = projectionMatrix.inverted(); + const Matrix4x4 viewProjectionMatrix = (projectionMatrix * m_data.m_viewMatrix); + const Matrix4x4 inversedViewProjectionMatrix = viewProjectionMatrix.inverted(); + { + memcpy(&m_renderViewUBO.viewMatrix, &m_data.m_viewMatrix, sizeof(Matrix4x4)); + memcpy(&m_renderViewUBO.projectionMatrix, &projectionMatrix, sizeof(Matrix4x4)); + memcpy(&m_renderViewUBO.viewProjectionMatrix, &viewProjectionMatrix, sizeof(Matrix4x4)); + memcpy(&m_renderViewUBO.inverseViewMatrix, &inverseViewMatrix, sizeof(Matrix4x4)); + memcpy(&m_renderViewUBO.inverseProjectionMatrix, &inversedProjectionMatrix, + sizeof(Matrix4x4)); + memcpy(&m_renderViewUBO.inverseViewProjectionMatrix, &inversedViewProjectionMatrix, + sizeof(Matrix4x4)); + { + QMatrix4x4 viewportMatrix; + // TO DO: Implement on Matrix4x4 + viewportMatrix.viewport(resolveViewport(m_viewport, m_surfaceSize)); + Matrix4x4 vpMatrix(viewportMatrix); + Matrix4x4 invVpMatrix = vpMatrix.inverted(); + memcpy(&m_renderViewUBO.viewportMatrix, &vpMatrix, sizeof(Matrix4x4)); + memcpy(&m_renderViewUBO.inverseViewportMatrix, &invVpMatrix, sizeof(Matrix4x4)); + } + memcpy(&m_renderViewUBO.textureTransformMatrix, m_renderer->textureTransform(), + sizeof(float) * 4); + + memcpy(&m_renderViewUBO.eyePosition, &m_data.m_eyePos, sizeof(float) * 3); + const float ratio = + float(m_surfaceSize.width()) / std::max(1.f, float(m_surfaceSize.height())); + memcpy(&m_renderViewUBO.aspectRatio, &ratio, sizeof(float)); + memcpy(&m_renderViewUBO.gamma, &m_gamma, sizeof(float)); + const float exposure = + m_data.m_renderCameraLens ? m_data.m_renderCameraLens->exposure() : 0.0f; + memcpy(&m_renderViewUBO.exposure, &exposure, sizeof(float)); + const float timeValue = float(m_renderer->time() / 1000000000.0f); + memcpy(&m_renderViewUBO.time, &timeValue, sizeof(float)); + } + + for (int i = 0, m = count; i < m; ++i) { + const int idx = offset + i; + Entity *entity = renderCommandData->entities.at(idx); + const RenderPassParameterData passData = renderCommandData->passesData.at(idx); + RenderCommand &command = renderCommandData->commands[idx]; + + // 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; + EnvironmentLight *environmentLight = nullptr; + + if (command.m_type == RenderCommand::Draw) { + // 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); + + environmentLight = m_environmentLight; + 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; + }); + } + lightSources = lightSources.mid(0, std::max(lightSources.size(), MAX_LIGHTS)); + } else { // Compute + // Note: if frameCount has reached 0 in the previous frame, isEnabled + // would be false + ComputeCommand *computeJob = + m_manager->computeJobManager()->data(command.m_computeCommand); + if (computeJob->runType() == QComputeCommand::Manual) + computeJob->updateFrameCount(); + } + + ParameterInfoList globalParameters = passData.parameterInfo; + // setShaderAndUniforms can initialize a localData + // make sure this is cleared before we leave this function + + setShaderAndUniforms(&command, globalParameters, entity, lightSources, environmentLight); + + // Update CommandUBO (Qt3D standard uniforms) + const Matrix4x4 worldTransform = *(entity->worldTransform()); + const Matrix4x4 inverseWorldTransform = worldTransform.inverted(); + const QMatrix3x3 modelNormalMatrix = convertToQMatrix4x4(worldTransform).normalMatrix(); + const Matrix4x4 modelViewMatrix = m_data.m_viewMatrix * worldTransform; + const Matrix4x4 inverseModelViewMatrix = modelViewMatrix.inverted(); + const Matrix4x4 mvp = projectionMatrix * modelViewMatrix; + const Matrix4x4 inverseModelViewProjection = mvp.inverted(); + { + memcpy(&command.m_commandUBO.modelMatrix, &worldTransform, sizeof(Matrix4x4)); + memcpy(&command.m_commandUBO.inverseModelMatrix, &inverseWorldTransform, + sizeof(Matrix4x4)); + memcpy(&command.m_commandUBO.modelViewMatrix, &modelViewMatrix, sizeof(Matrix4x4)); + { + float(&normal)[12] = command.m_commandUBO.modelNormalMatrix; + normal[0] = modelNormalMatrix.constData()[0 * 3 + 0]; + normal[1] = modelNormalMatrix.constData()[0 * 3 + 1]; + normal[2] = modelNormalMatrix.constData()[0 * 3 + 2]; + + normal[4] = modelNormalMatrix.constData()[1 * 3 + 0]; + normal[5] = modelNormalMatrix.constData()[1 * 3 + 1]; + normal[6] = modelNormalMatrix.constData()[1 * 3 + 2]; + + normal[8] = modelNormalMatrix.constData()[2 * 3 + 0]; + normal[9] = modelNormalMatrix.constData()[2 * 3 + 1]; + normal[10] = modelNormalMatrix.constData()[2 * 3 + 2]; + } + memcpy(&command.m_commandUBO.inverseModelViewMatrix, &inverseModelViewMatrix, + sizeof(Matrix4x4)); + memcpy(&command.m_commandUBO.mvp, &mvp, sizeof(Matrix4x4)); + memcpy(&command.m_commandUBO.inverseModelViewProjectionMatrix, + &inverseModelViewProjection, sizeof(Matrix4x4)); + } + } + + // We reset the local data once we are done with it + m_localData.setLocalData(nullptr); +} + +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 or Image + // 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); + UniformValue::ValueType resourceType = UniformValue::TextureValue; + + for (int i = 0; i < uniformArraySize; ++i) { + const Qt3DCore::QNodeId resourceId = nodeIds[i]; + + const Texture *tex = m_manager->textureManager()->lookupResource(resourceId); + if (tex != nullptr) { + uniformPack.setTexture(nameId, i, resourceId); + } else { + const ShaderImage *img = + m_manager->shaderImageManager()->lookupResource(resourceId); + if (img != nullptr) { + resourceType = UniformValue::ShaderImageValue; + uniformPack.setImage(nameId, i, resourceId); + } + } + } + + // This uniform will be overridden in SubmissionContext::setParameters + // and -1 values will be replaced by valid Texture or Image units + UniformValue uniformValue(uniformArraySize * sizeof(int), resourceType); + std::fill(uniformValue.data<int>(), uniformValue.data<int>() + uniformArraySize, -1); + uniformPack.setUniform(nameId, uniformValue); + } else { + uniformPack.setUniform(nameId, value); + } +} + +void RenderView::setUniformBlockValue(ShaderParameterPack &uniformPack, const RHIShader *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 + } + } +} + +void RenderView::setShaderStorageValue(ShaderParameterPack &uniformPack, const RHIShader *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(); + shaderStorageBlock.m_bindingIndex = block.m_binding; + uniformPack.setShaderStorageBuffer(shaderStorageBlock); + // Buffer update to GL buffer will be done at render time + } + } +} + +void RenderView::setDefaultUniformBlockShaderDataValue(ShaderParameterPack &uniformPack, + const RHIShader *shader, + const 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->unqualifiedUniformNames(); + // 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::applyParameter(const Parameter *param, RenderCommand *command, + const RHIShader *shader) const noexcept +{ + const int nameId = param->nameId(); + const UniformValue &uniformValue = param->uniformValue(); + switch (shader->categorizeVariable(nameId)) { + case RHIShader::Uniform: { + setUniformValue(command->m_parameterPack, nameId, uniformValue); + break; + } + case RHIShader::UBO: { + setUniformBlockValue(command->m_parameterPack, shader, + shader->uniformBlockForBlockNameId(nameId), uniformValue); + break; + } + case RHIShader::SSBO: { + setShaderStorageValue(command->m_parameterPack, shader, + shader->storageBlockForBlockNameId(nameId), uniformValue); + break; + } + case RHIShader::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(nameId)); + } + break; + } + } +} + +void RenderView::setShaderAndUniforms(RenderCommand *command, 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 + + RHIShader *shader = command->m_rhiShader; + if (shader != nullptr && shader->isLoaded()) { + + // 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 + + // 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 + if (!m_renderTarget.isNull() && !shader->isLoaded()) { + QHash<QString, int> fragOutputs; + 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); + } + // Set frag outputs in the shaders if hash not empty + if (!fragOutputs.isEmpty()) + shader->setFragOutputs(fragOutputs); + } + + if (shader->hasActiveVariables()) { + + // Unlike the GL engine, the standard uniforms are set a bit before this function, + // in RenderView::updateRenderCommand + + // Set default attributes + command->m_activeAttributes = shader->attributeNamesIds(); + + // At this point we know whether the command is a valid draw command or not + // We still need to process the uniforms as the command could be a compute command + command->m_isValid = !command->m_activeAttributes.empty(); + + // 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) { + const Parameter *param = m_manager->data<Parameter, ParameterManager>(it->handle); + applyParameter(param, command, shader); + ++it; + } + + // Lights + int lightIdx = 0; + for (const LightSource &lightSource : activeLightSources) { + if (lightIdx == MAX_LIGHTS) + break; + Entity *lightEntity = lightSource.entity; + const Matrix4x4 lightWorldTransform = *(lightEntity->worldTransform()); + const Vector3D worldPos = lightWorldTransform * Vector3D(0.0f, 0.0f, 0.0f); + 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 (shader->hasUniform(LIGHT_COUNT_NAME_ID)) + setUniformValue(command->m_parameterPack, LIGHT_COUNT_NAME_ID, + UniformValue(qMax((environmentLight ? 0 : 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()) { + static const int irradianceId = + StringToInt::lookupId(QLatin1String("envLight_irradiance")); + static const int specularId = + StringToInt::lookupId(QLatin1String("envLight_specular")); + ShaderData *shaderData = m_manager->shaderDataManager()->lookupResource( + environmentLight->shaderData()); + if (shaderData) { + envLightCount = 1; + + // ("specularSize", "irradiance", "irradianceSize", "specular") + auto irr = + shaderData->properties()["irradiance"].value.value<Qt3DCore::QNodeId>(); + auto spec = + shaderData->properties()["specular"].value.value<Qt3DCore::QNodeId>(); + + setUniformValue(command->m_parameterPack, irradianceId, irr); + setUniformValue(command->m_parameterPack, specularId, spec); + } + } + setUniformValue(command->m_parameterPack, + StringToInt::lookupId(QStringLiteral("envLightCount")), envLightCount); + } + } +} + +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 Rhi +} // namespace Render +} // namespace Qt3DRender + +QT_END_NAMESPACE diff --git a/src/plugins/renderers/rhi/renderer/renderview_p.h b/src/plugins/renderers/rhi/renderer/renderview_p.h new file mode 100644 index 000000000..3206b6e66 --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/renderview_p.h @@ -0,0 +1,455 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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_RHI_RENDERVIEW_H +#define QT3DRENDER_RENDER_RHI_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/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 <Qt3DRender/private/qwaitfence_p.h> + +#include <Qt3DCore/private/qframeallocator_p.h> +#include <Qt3DRender/private/aligned_malloc_p.h> + +#include <renderer_p.h> +// TODO: Move out once this is all refactored +#include <renderviewjobutils_p.h> + +#include <QVector> +#include <QSurface> +#include <QMutex> +#include <QColor> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { + +class QRenderPass; + +namespace Render { + +class NodeManagers; +class RenderPassFilter; +class TechniqueFilter; +class ViewportNode; +class Effect; +class RenderPass; + +namespace Rhi { + +class Renderer; +class RenderCommand; + +typedef QPair<ShaderUniform, QVariant> ActivePropertyContent; +typedef QPair<QString, ActivePropertyContent> ActiveProperty; + +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 + +struct RenderViewUBO +{ + float viewMatrix[16]; + float projectionMatrix[16]; + float viewProjectionMatrix[16]; + float inverseViewMatrix[16]; + float inverseProjectionMatrix[16]; + float inverseViewProjectionMatrix[16]; + float viewportMatrix[16]; + float inverseViewportMatrix[16]; + float textureTransformMatrix[4]; + float eyePosition[3]; + float aspectRatio; + float gamma; + float exposure; + float time; +}; +static_assert(sizeof(RenderViewUBO) == sizeof(float) * (8 * 16 + 1 * 4 + 1 * 3 + 4 * 1), + "UBO doesn't match std140"); + +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; } + + inline const RenderViewUBO *renderViewUBO() const { return &m_renderViewUBO; } + + RenderPassList passesAndParameters(ParameterInfoList *parameter, Entity *node, + bool useDefaultMaterials = true); + + EntityRenderCommandData buildDrawRenderCommands(const QVector<Entity *> &entities, int offset, + int count) const; + EntityRenderCommandData buildComputeRenderCommands(const QVector<Entity *> &entities, + int offset, int count) const; + + void updateRenderCommand(EntityRenderCommandData *renderCommandData, int offset, int count); + + void setCommands(const QVector<RenderCommand> &commands) Q_DECL_NOTHROW + { + m_commands = commands; + } + QVector<RenderCommand> &commands() { return m_commands; } + QVector<RenderCommand> commands() const { 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; + } + + // 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, 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]; + + RenderViewUBO m_renderViewUBO; + + QVector<RenderCommand> m_commands; + mutable QVector<LightSource> m_lightSources; + EnvironmentLight *m_environmentLight; + + MaterialParameterGathererData m_parameters; + + void setUniformValue(ShaderParameterPack &uniformPack, int nameId, + const UniformValue &value) const; + void setUniformBlockValue(ShaderParameterPack &uniformPack, const RHIShader *shader, + const ShaderUniformBlock &block, const UniformValue &value) const; + void setShaderStorageValue(ShaderParameterPack &uniformPack, const RHIShader *shader, + const ShaderStorageBlock &block, const UniformValue &value) const; + void setDefaultUniformBlockShaderDataValue(ShaderParameterPack &uniformPack, + const RHIShader *shader, + const ShaderData *shaderData, + const QString &structName) const; + void applyParameter(const Parameter *param, RenderCommand *command, + const RHIShader *shader) const noexcept; +}; + +} // namespace Rhi +} // namespace Render +} // namespace Qt3DRender + +QT_END_NAMESPACE + +#endif // QT3DRENDER_RENDER_RHI_ENDERVIEW_H diff --git a/src/plugins/renderers/rhi/renderer/renderviewbuilder.cpp b/src/plugins/renderers/rhi/renderer/renderviewbuilder.cpp new file mode 100644 index 000000000..6455d2e10 --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/renderviewbuilder.cpp @@ -0,0 +1,841 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 <Qt3DRender/private/qrenderaspect_p.h> + +#include <QThread> + +namespace Qt3DRender { + +namespace Render { +namespace Rhi { + +// In some cases having less jobs is better (especially on fast cpus where +// splitting just adds more overhead). Ideally, we should try to set the value +// depending on the platform/CPU/nbr of cores +const int RenderViewBuilder::m_optimalParallelJobCount = QThread::idealThreadCount(); + +namespace { + +int findIdealNumberOfWorkers(int elementCount, int packetSize = 100) +{ + if (elementCount == 0 || packetSize == 0) + return 0; + return std::min(std::max(elementCount / packetSize, 1), RenderViewBuilder::optimalJobCount()); +} + +class SyncPreCommandBuilding +{ +public: + explicit SyncPreCommandBuilding( + RenderViewInitializerJobPtr renderViewInitializerJob, + const QVector<RenderViewCommandBuilderJobPtr> &renderViewCommandBuilderJobs, + Renderer *renderer, FrameGraphNode *leafNode) + : m_renderViewInitializer(std::move(renderViewInitializerJob)), + m_renderViewCommandBuilderJobs(renderViewCommandBuilderJobs), + m_renderer(renderer), + m_leafNode(leafNode) + { + } + + void operator()() + { + // Split commands to build among jobs + QMutexLocker lock(m_renderer->cache()->mutex()); + // Rebuild RenderCommands for all entities in RV (ignoring filtering) + RendererCache *cache = m_renderer->cache(); + const RendererCache::LeafNodeData &dataCacheForLeaf = cache->leafNodeCache[m_leafNode]; + RenderView *rv = m_renderViewInitializer->renderView(); + const auto entities = !rv->isCompute() ? cache->renderableEntities : cache->computeEntities; + + rv->setMaterialParameterTable(dataCacheForLeaf.materialParameterGatherer); + + lock.unlock(); + + // Split among the ideal number of command builders + const int idealPacketSize = + std::min(std::max(100, entities.size() / RenderViewBuilder::optimalJobCount()), + entities.size()); + // Try to split work into an ideal number of workers + const int m = findIdealNumberOfWorkers(entities.size(), idealPacketSize); + + for (int i = 0; i < m; ++i) { + const RenderViewCommandBuilderJobPtr renderViewCommandBuilder = + m_renderViewCommandBuilderJobs.at(i); + const int count = + (i == m - 1) ? entities.size() - (i * idealPacketSize) : idealPacketSize; + renderViewCommandBuilder->setEntities(entities, i * idealPacketSize, count); + } + } + +private: + RenderViewInitializerJobPtr m_renderViewInitializer; + QVector<RenderViewCommandBuilderJobPtr> m_renderViewCommandBuilderJobs; + Renderer *m_renderer; + FrameGraphNode *m_leafNode; +}; + +class SyncRenderViewPostCommandUpdate +{ +public: + explicit SyncRenderViewPostCommandUpdate( + const RenderViewInitializerJobPtr &renderViewJob, + const QVector<RenderViewCommandUpdaterJobPtr> &renderViewCommandUpdateJobs, + Renderer *renderer) + : m_renderViewJob(renderViewJob), + m_renderViewCommandUpdaterJobs(renderViewCommandUpdateJobs), + m_renderer(renderer) + { + } + + void operator()() + { + // Append all the commands and sort them + RenderView *rv = m_renderViewJob->renderView(); + + const EntityRenderCommandDataPtr commandData = + m_renderViewCommandUpdaterJobs.first()->renderables(); + + if (commandData) { + const QVector<RenderCommand> commands = std::move(commandData->commands); + rv->setCommands(commands); + + // TO DO: Find way to store commands once or at least only when required + // 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<RenderViewCommandUpdaterJobPtr> m_renderViewCommandUpdaterJobs; + Renderer *m_renderer; +}; + +class SyncPreFrustumCulling +{ +public: + explicit SyncPreFrustumCulling(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 SyncRenderViewPostInitialization +{ +public: + explicit SyncRenderViewPostInitialization( + const RenderViewInitializerJobPtr &renderViewJob, + const FrustumCullingJobPtr &frustumCullingJob, + const FilterLayerEntityJobPtr &filterEntityByLayerJob, + const FilterProximityDistanceJobPtr &filterProximityJob, + const QVector<MaterialParameterGathererJobPtr> &materialGathererJobs, + const QVector<RenderViewCommandUpdaterJobPtr> &renderViewCommandUpdaterJobs, + const QVector<RenderViewCommandBuilderJobPtr> &renderViewCommandBuilderJobs) + : m_renderViewJob(renderViewJob), + m_frustumCullingJob(frustumCullingJob), + m_filterEntityByLayerJob(filterEntityByLayerJob), + m_filterProximityJob(filterProximityJob), + m_materialGathererJobs(materialGathererJobs), + m_renderViewCommandUpdaterJobs(renderViewCommandUpdaterJobs), + m_renderViewCommandBuilderJobs(renderViewCommandBuilderJobs) + { + } + + 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 and updates + for (const auto &renderViewCommandUpdater : qAsConst(m_renderViewCommandUpdaterJobs)) + renderViewCommandUpdater->setRenderView(rv); + for (const auto &renderViewCommandBuilder : qAsConst(m_renderViewCommandBuilderJobs)) + 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<RenderViewCommandUpdaterJobPtr> m_renderViewCommandUpdaterJobs; + QVector<RenderViewCommandBuilderJobPtr> m_renderViewCommandBuilderJobs; +}; + +class SyncRenderViewPreCommandUpdate +{ +public: + explicit SyncRenderViewPreCommandUpdate( + const RenderViewInitializerJobPtr &renderViewJob, + const FrustumCullingJobPtr &frustumCullingJob, + const FilterProximityDistanceJobPtr &filterProximityJob, + const QVector<MaterialParameterGathererJobPtr> &materialGathererJobs, + const QVector<RenderViewCommandUpdaterJobPtr> &renderViewCommandUpdaterJobs, + const QVector<RenderViewCommandBuilderJobPtr> &renderViewCommandBuilderJobs, + Renderer *renderer, FrameGraphNode *leafNode, bool fullCommandRebuild) + : m_renderViewJob(renderViewJob), + m_frustumCullingJob(frustumCullingJob), + m_filterProximityJob(filterProximityJob), + m_materialGathererJobs(materialGathererJobs), + m_renderViewCommandUpdaterJobs(renderViewCommandUpdaterJobs), + m_renderViewCommandBuilderJobs(renderViewCommandBuilderJobs), + m_renderer(renderer), + m_leafNode(leafNode), + m_fullRebuild(fullCommandRebuild) + { + } + + void operator()() + { + // Set the result of previous job computations + // for final RenderCommand building + RenderView *rv = m_renderViewJob->renderView(); + + if (!rv->noDraw()) { + ///////// CACHE LOCKED //////////// + // Retrieve Data from Cache + RendererCache *cache = m_renderer->cache(); + QMutexLocker lock(cache->mutex()); + Q_ASSERT(cache->leafNodeCache.contains(m_leafNode)); + + const bool isDraw = !rv->isCompute(); + const RendererCache::LeafNodeData &dataCacheForLeaf = cache->leafNodeCache[m_leafNode]; + + // Rebuild RenderCommands if required + // This should happen fairly infrequently (FrameGraph Change, Geometry/Material change) + // and allow to skip that step most of the time + if (m_fullRebuild) { + EntityRenderCommandData commandData; + // Reduction + { + int totalCommandCount = 0; + for (const RenderViewCommandBuilderJobPtr &renderViewCommandBuilder : + qAsConst(m_renderViewCommandBuilderJobs)) + totalCommandCount += renderViewCommandBuilder->commandData().size(); + commandData.reserve(totalCommandCount); + // assert(totalCommandCount != 0); + for (const RenderViewCommandBuilderJobPtr &renderViewCommandBuilder : + qAsConst(m_renderViewCommandBuilderJobs)) + commandData += std::move(renderViewCommandBuilder->commandData()); + } + + // Store new cache + RendererCache::LeafNodeData &writableCacheForLeaf = + cache->leafNodeCache[m_leafNode]; + writableCacheForLeaf.renderCommandData = std::move(commandData); + } + const EntityRenderCommandData commandData = dataCacheForLeaf.renderCommandData; + const QVector<Entity *> filteredEntities = dataCacheForLeaf.filterEntitiesByLayer; + QVector<Entity *> renderableEntities = + isDraw ? cache->renderableEntities : cache->computeEntities; + QVector<LightSource> lightSources = cache->gatheredLights; + + rv->setMaterialParameterTable(dataCacheForLeaf.materialParameterGatherer); + rv->setEnvironmentLight(cache->environmentLight); + lock.unlock(); + ///////// END OF CACHE LOCKED //////////// + + // Filter out entities that weren't selected by the layer filters + // 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. + 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 + if (!rv->proximityFilterIds().empty()) + renderableEntities = RenderViewBuilder::entitiesInSubset( + renderableEntities, m_filterProximityJob->filteredEntities()); + } + + // Early return in case we have nothing to filter + if (renderableEntities.size() == 0) + return; + + // Filter out Render commands for which the Entity wasn't selected because + // of frustum, proximity or layer filtering + EntityRenderCommandDataPtr filteredCommandData = EntityRenderCommandDataPtr::create(); + filteredCommandData->reserve(renderableEntities.size()); + // Because dataCacheForLeaf.renderableEntities or computeEntities are sorted + // What we get out of EntityRenderCommandData is also sorted by Entity + auto eIt = std::cbegin(renderableEntities); + const auto eEnd = std::cend(renderableEntities); + int cIt = 0; + const int cEnd = commandData.size(); + + while (eIt != eEnd) { + const Entity *targetEntity = *eIt; + // Advance until we have commands whose Entity has a lower address + // than the selected filtered entity + while (cIt != cEnd && commandData.entities.at(cIt) < targetEntity) + ++cIt; + + // Push pointers to command data for all commands that match the + // entity + while (cIt != cEnd && commandData.entities.at(cIt) == targetEntity) { + filteredCommandData->push_back(commandData.entities.at(cIt), + commandData.commands.at(cIt), + commandData.passesData.at(cIt)); + ++cIt; + } + ++eIt; + } + + // Split among the number of command builders + // The idealPacketSize is at least 100 entities per worker + const int idealPacketSize = std::min( + std::max(100, + filteredCommandData->size() / RenderViewBuilder::optimalJobCount()), + filteredCommandData->size()); + const int m = findIdealNumberOfWorkers(filteredCommandData->size(), idealPacketSize); + + for (int i = 0; i < m; ++i) { + const RenderViewCommandUpdaterJobPtr renderViewCommandBuilder = + m_renderViewCommandUpdaterJobs.at(i); + const int count = (i == m - 1) ? filteredCommandData->size() - (i * idealPacketSize) + : idealPacketSize; + renderViewCommandBuilder->setRenderables(filteredCommandData, i * idealPacketSize, + count); + } + } + } + +private: + RenderViewInitializerJobPtr m_renderViewJob; + FrustumCullingJobPtr m_frustumCullingJob; + FilterProximityDistanceJobPtr m_filterProximityJob; + QVector<MaterialParameterGathererJobPtr> m_materialGathererJobs; + QVector<RenderViewCommandUpdaterJobPtr> m_renderViewCommandUpdaterJobs; + QVector<RenderViewCommandBuilderJobPtr> m_renderViewCommandBuilderJobs; + Renderer *m_renderer; + FrameGraphNode *m_leafNode; + bool m_fullRebuild; +}; + +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_renderCommandCacheNeedsToBeRebuilt(false), + m_renderViewJob(RenderViewInitializerJobPtr::create()), + m_filterEntityByLayerJob(), + m_frustumCullingJob(new Render::FrustumCullingJob()), + m_syncPreFrustumCullingJob(SynchronizerJobPtr::create( + SyncPreFrustumCulling(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; +} + +FrustumCullingJobPtr RenderViewBuilder::frustumCullingJob() const +{ + return m_frustumCullingJob; +} + +QVector<RenderViewCommandUpdaterJobPtr> RenderViewBuilder::renderViewCommandUpdaterJobs() const +{ + return m_renderViewCommandUpdaterJobs; +} + +QVector<RenderViewCommandBuilderJobPtr> RenderViewBuilder::renderViewCommandBuilderJobs() const +{ + return m_renderViewCommandBuilderJobs; +} + +QVector<MaterialParameterGathererJobPtr> RenderViewBuilder::materialGathererJobs() const +{ + return m_materialGathererJobs; +} + +SynchronizerJobPtr RenderViewBuilder::syncRenderViewPostInitializationJob() const +{ + return m_syncRenderViewPostInitializationJob; +} + +SynchronizerJobPtr RenderViewBuilder::syncPreFrustumCullingJob() const +{ + return m_syncPreFrustumCullingJob; +} + +SynchronizerJobPtr RenderViewBuilder::syncRenderViewPreCommandBuildingJob() const +{ + return m_syncRenderViewPreCommandBuildingJob; +} + +SynchronizerJobPtr RenderViewBuilder::syncRenderViewPreCommandUpdateJob() const +{ + return m_syncRenderViewPreCommandUpdateJob; +} + +SynchronizerJobPtr RenderViewBuilder::syncRenderViewPostCommandUpdateJob() const +{ + return m_syncRenderViewPostCommandUpdateJob; +} + +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 + m_filterProximityJob->setManager(m_renderer->nodeManagers()); + m_frustumCullingJob->setRoot(m_renderer->sceneRoot()); + + if (m_renderCommandCacheNeedsToBeRebuilt) { + + m_renderViewCommandBuilderJobs.reserve(RenderViewBuilder::m_optimalParallelJobCount); + for (auto i = 0; i < RenderViewBuilder::m_optimalParallelJobCount; ++i) { + auto renderViewCommandBuilder = Render::Rhi::RenderViewCommandBuilderJobPtr::create(); + m_renderViewCommandBuilderJobs.push_back(renderViewCommandBuilder); + } + m_syncRenderViewPreCommandBuildingJob = SynchronizerJobPtr::create( + SyncPreCommandBuilding(m_renderViewJob, m_renderViewCommandBuilderJobs, m_renderer, + m_leafNode), + JobTypes::SyncRenderViewPreCommandBuilding); + } + + 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_renderViewCommandUpdaterJobs.reserve(RenderViewBuilder::m_optimalParallelJobCount); + for (auto i = 0; i < RenderViewBuilder::m_optimalParallelJobCount; ++i) { + auto renderViewCommandUpdater = Render::Rhi::RenderViewCommandUpdaterJobPtr::create(); + renderViewCommandUpdater->setRenderer(m_renderer); + m_renderViewCommandUpdaterJobs.push_back(renderViewCommandUpdater); + } + + 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 = MaterialParameterGathererJobPtr::create(); + materialGatherer->setNodeManagers(m_renderer->nodeManagers()); + 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_syncRenderViewPreCommandUpdateJob = SynchronizerJobPtr::create( + SyncRenderViewPreCommandUpdate(m_renderViewJob, m_frustumCullingJob, + m_filterProximityJob, m_materialGathererJobs, + m_renderViewCommandUpdaterJobs, + m_renderViewCommandBuilderJobs, m_renderer, m_leafNode, + m_renderCommandCacheNeedsToBeRebuilt), + JobTypes::SyncRenderViewPreCommandUpdate); + + m_syncRenderViewPostCommandUpdateJob = SynchronizerJobPtr::create( + SyncRenderViewPostCommandUpdate(m_renderViewJob, m_renderViewCommandUpdaterJobs, + m_renderer), + JobTypes::SyncRenderViewPostCommandUpdate); + + m_syncRenderViewPostInitializationJob = SynchronizerJobPtr::create( + SyncRenderViewPostInitialization(m_renderViewJob, m_frustumCullingJob, + m_filterEntityByLayerJob, m_filterProximityJob, + m_materialGathererJobs, m_renderViewCommandUpdaterJobs, + m_renderViewCommandBuilderJobs), + JobTypes::SyncRenderViewInitialization); +} + +QVector<Qt3DCore::QAspectJobPtr> RenderViewBuilder::buildJobHierachy() const +{ + QVector<Qt3DCore::QAspectJobPtr> jobs; + auto daspect = QRenderAspectPrivate::get(m_renderer->aspect()); + auto expandBVJob = daspect->m_expandBoundingVolumeJob; + auto worldTransformJob = daspect->m_worldTransformJob; + auto updateTreeEnabledJob = daspect->m_updateTreeEnabledJob; + auto updateSkinningPaletteJob = daspect->m_updateSkinningPaletteJob; + auto updateEntityLayersJob = daspect->m_updateEntityLayersJob; + + jobs.reserve(m_materialGathererJobs.size() + m_renderViewCommandUpdaterJobs.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(updateSkinningPaletteJob); + + m_syncPreFrustumCullingJob->addDependency(worldTransformJob); + m_syncPreFrustumCullingJob->addDependency(m_renderer->updateShaderDataTransformJob()); + m_syncPreFrustumCullingJob->addDependency(m_syncRenderViewPostInitializationJob); + + m_frustumCullingJob->addDependency(expandBVJob); + m_frustumCullingJob->addDependency(m_syncPreFrustumCullingJob); + + m_setClearDrawBufferIndexJob->addDependency(m_syncRenderViewPostInitializationJob); + + m_syncRenderViewPostInitializationJob->addDependency(m_renderViewJob); + + m_filterProximityJob->addDependency(expandBVJob); + m_filterProximityJob->addDependency(m_syncRenderViewPostInitializationJob); + + m_syncRenderViewPreCommandUpdateJob->addDependency(m_syncRenderViewPostInitializationJob); + m_syncRenderViewPreCommandUpdateJob->addDependency(m_filterProximityJob); + m_syncRenderViewPreCommandUpdateJob->addDependency(m_frustumCullingJob); + + // Ensure the RenderThread won't be able to process dirtyResources + // before they have been completely gathered + m_syncRenderViewPreCommandUpdateJob->addDependency(m_renderer->introspectShadersJob()); + m_syncRenderViewPreCommandUpdateJob->addDependency(m_renderer->bufferGathererJob()); + m_syncRenderViewPreCommandUpdateJob->addDependency(m_renderer->textureGathererJob()); + m_syncRenderViewPreCommandUpdateJob->addDependency(m_renderer->lightGathererJob()); + + for (const auto &renderViewCommandUpdater : qAsConst(m_renderViewCommandUpdaterJobs)) { + renderViewCommandUpdater->addDependency(m_syncRenderViewPreCommandUpdateJob); + m_syncRenderViewPostCommandUpdateJob->addDependency(renderViewCommandUpdater); + } + + m_renderer->frameCleanupJob()->addDependency(m_syncRenderViewPostCommandUpdateJob); + m_renderer->frameCleanupJob()->addDependency(m_setClearDrawBufferIndexJob); + + // Add jobs + jobs.push_back(m_renderViewJob); // Step 1 + + jobs.push_back(m_syncRenderViewPostInitializationJob); // Step 2 + + if (m_renderCommandCacheNeedsToBeRebuilt) { // Step 3 + m_syncRenderViewPreCommandBuildingJob->addDependency( + m_renderer->computableEntityFilterJob()); + m_syncRenderViewPreCommandBuildingJob->addDependency( + m_renderer->renderableEntityFilterJob()); + m_syncRenderViewPreCommandBuildingJob->addDependency(m_syncRenderViewPostInitializationJob); + + if (m_materialGathererCacheNeedsToBeRebuilt) + m_syncRenderViewPreCommandBuildingJob->addDependency(m_syncMaterialGathererJob); + + jobs.push_back(m_syncRenderViewPreCommandBuildingJob); + + for (const auto &renderViewCommandBuilder : qAsConst(m_renderViewCommandBuilderJobs)) { + renderViewCommandBuilder->addDependency(m_syncRenderViewPreCommandBuildingJob); + m_syncRenderViewPreCommandUpdateJob->addDependency(renderViewCommandBuilder); + jobs.push_back(renderViewCommandBuilder); + } + } + + if (m_layerCacheNeedsToBeRebuilt) { + m_filterEntityByLayerJob->addDependency(updateEntityLayersJob); + m_filterEntityByLayerJob->addDependency(m_syncRenderViewPostInitializationJob); + m_filterEntityByLayerJob->addDependency(updateTreeEnabledJob); + + m_syncFilterEntityByLayerJob->addDependency(m_filterEntityByLayerJob); + m_syncRenderViewPreCommandUpdateJob->addDependency(m_syncFilterEntityByLayerJob); + + jobs.push_back(m_filterEntityByLayerJob); // Step 3 + jobs.push_back(m_syncFilterEntityByLayerJob); // Step 4 + } + jobs.push_back(m_syncPreFrustumCullingJob); // 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_syncRenderViewPostInitializationJob); + materialGatherer->addDependency(m_renderer->introspectShadersJob()); + materialGatherer->addDependency(m_renderer->filterCompatibleTechniqueJob()); + jobs.push_back(materialGatherer); // Step3 + m_syncMaterialGathererJob->addDependency(materialGatherer); + } + m_syncRenderViewPreCommandUpdateJob->addDependency(m_syncMaterialGathererJob); + + jobs.push_back(m_syncMaterialGathererJob); // Step 3 + } + + jobs.push_back(m_frustumCullingJob); // Step 4 + jobs.push_back(m_syncRenderViewPreCommandUpdateJob); // Step 5 + + // Build RenderCommands or Update RenderCommand Uniforms + for (const auto &renderViewCommandBuilder : qAsConst(m_renderViewCommandUpdaterJobs)) // Step 6 + jobs.push_back(renderViewCommandBuilder); + + jobs.push_back(m_syncRenderViewPostCommandUpdateJob); // 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; +} + +void RenderViewBuilder::setRenderCommandCacheNeedsToBeRebuilt(bool needsToBeRebuilt) +{ + m_renderCommandCacheNeedsToBeRebuilt = needsToBeRebuilt; +} + +bool RenderViewBuilder::renderCommandCacheNeedsToBeRebuilt() const +{ + return m_renderCommandCacheNeedsToBeRebuilt; +} + +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; +} + +} // Rhi + +} // Render + +} // Qt3DRender + +QT_END_NAMESPACE diff --git a/src/plugins/renderers/rhi/renderer/renderviewbuilder_p.h b/src/plugins/renderers/rhi/renderer/renderviewbuilder_p.h new file mode 100644 index 000000000..4df57b139 --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/renderviewbuilder_p.h @@ -0,0 +1,153 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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_RHI_RENDERVIEWBUILDER_H +#define QT3DRENDER_RENDER_RHI_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/filterlayerentityjob_p.h> +#include <Qt3DRender/private/genericlambdajob_p.h> +#include <Qt3DRender/private/nodemanagers_p.h> +#include <Qt3DRender/private/frustumcullingjob_p.h> +#include <Qt3DRender/private/filterproximitydistancejob_p.h> +#include <renderviewcommandbuilderjob_p.h> +#include <renderviewcommandupdaterjob_p.h> +#include <materialparametergathererjob_p.h> +#include <renderview_p.h> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { + +namespace Render { + +namespace Rhi { + +class Renderer; + +using SynchronizerJobPtr = GenericLambdaJobPtr<std::function<void()>>; + +class Q_AUTOTEST_EXPORT RenderViewBuilder +{ +public: + explicit RenderViewBuilder(Render::FrameGraphNode *leafNode, int renderViewIndex, + Renderer *renderer); + + RenderViewInitializerJobPtr renderViewJob() const; + FilterLayerEntityJobPtr filterEntityByLayerJob() const; + FrustumCullingJobPtr frustumCullingJob() const; + QVector<RenderViewCommandBuilderJobPtr> renderViewCommandBuilderJobs() const; + QVector<RenderViewCommandUpdaterJobPtr> renderViewCommandUpdaterJobs() const; + QVector<MaterialParameterGathererJobPtr> materialGathererJobs() const; + SynchronizerJobPtr syncRenderViewPostInitializationJob() const; + SynchronizerJobPtr syncPreFrustumCullingJob() const; + SynchronizerJobPtr syncRenderViewPreCommandBuildingJob() const; + SynchronizerJobPtr syncRenderViewPreCommandUpdateJob() const; + SynchronizerJobPtr syncRenderViewPostCommandUpdateJob() 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; + void setRenderCommandCacheNeedsToBeRebuilt(bool needsToBeRebuilt); + bool renderCommandCacheNeedsToBeRebuilt() 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; + bool m_renderCommandCacheNeedsToBeRebuilt; + + RenderViewInitializerJobPtr m_renderViewJob; + FilterLayerEntityJobPtr m_filterEntityByLayerJob; + FrustumCullingJobPtr m_frustumCullingJob; + QVector<RenderViewCommandBuilderJobPtr> m_renderViewCommandBuilderJobs; + QVector<RenderViewCommandUpdaterJobPtr> m_renderViewCommandUpdaterJobs; + QVector<MaterialParameterGathererJobPtr> m_materialGathererJobs; + + SynchronizerJobPtr m_syncRenderViewPostInitializationJob; + SynchronizerJobPtr m_syncPreFrustumCullingJob; + SynchronizerJobPtr m_syncRenderViewPreCommandBuildingJob; + SynchronizerJobPtr m_syncRenderViewPreCommandUpdateJob; + SynchronizerJobPtr m_syncRenderViewPostCommandUpdateJob; + SynchronizerJobPtr m_setClearDrawBufferIndexJob; + SynchronizerJobPtr m_syncFilterEntityByLayerJob; + SynchronizerJobPtr m_syncMaterialGathererJob; + FilterProximityDistanceJobPtr m_filterProximityJob; + + static const int m_optimalParallelJobCount; +}; + +} // Rhi + +} // Render + +} // Qt3DRender + +QT_END_NAMESPACE + +#endif // QT3DRENDER_RENDER_RHI_RENDERVIEWBUILDER_H diff --git a/src/plugins/renderers/rhi/renderer/rhigraphicspipeline.cpp b/src/plugins/renderers/rhi/renderer/rhigraphicspipeline.cpp new file mode 100644 index 000000000..2a76f9c10 --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/rhigraphicspipeline.cpp @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 "rhigraphicspipeline_p.h" + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { + +namespace Render { + +namespace Rhi { + +RHIGraphicsPipeline::RHIGraphicsPipeline() + : m_rvUbo(nullptr), + m_cmdUbo(nullptr), + m_pipeline(nullptr), + m_shaderResourceBindings(nullptr), + m_score(0) +{ +} + +RHIGraphicsPipeline::~RHIGraphicsPipeline() { } + +void RHIGraphicsPipeline::cleanup() +{ + delete m_shaderResourceBindings; + delete m_rvUbo; + delete m_cmdUbo; + delete m_pipeline; + m_rvUbo = nullptr; + m_cmdUbo = nullptr; + m_pipeline = nullptr; + m_shaderResourceBindings = nullptr; + m_ubos.clear(); + m_attributeNameIdToBindingIndex.clear(); +} + +} // Rhi + +} // Render + +} // Qt3DRender + +QT_END_NAMESPACE diff --git a/src/plugins/renderers/rhi/renderer/rhigraphicspipeline_p.h b/src/plugins/renderers/rhi/renderer/rhigraphicspipeline_p.h new file mode 100644 index 000000000..a13bf50d3 --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/rhigraphicspipeline_p.h @@ -0,0 +1,127 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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_RHI_RHIGRAPHICSPIPELINE_H +#define QT3DRENDER_RENDER_RHI_RHIGRAPHICSPIPELINE_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 <private/qrhi_p.h> +#include <rhihandle_types_p.h> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { + +namespace Render { + +namespace Rhi { + +class RHIBuffer; + +class RHIGraphicsPipeline +{ +public: + struct UBOBuffer + { + HRHIBuffer handle {}; + RHIBuffer *buffer {}; + }; + + RHIGraphicsPipeline(); + ~RHIGraphicsPipeline(); + + QRhiBuffer *commandUBO() const { return m_cmdUbo; } + QRhiBuffer *renderViewUBO() const { return m_rvUbo; } + QRhiGraphicsPipeline *pipeline() const { return m_pipeline; } + QRhiShaderResourceBindings *shaderResourceBindings() const { return m_shaderResourceBindings; } + QHash<int, UBOBuffer> ubos() const { return m_ubos; } + int score() const { return m_score; } + + void setPipeline(QRhiGraphicsPipeline *pipeline) { m_pipeline = pipeline; } + void setCommandUBO(QRhiBuffer *commandUBO) { m_cmdUbo = commandUBO; } + void setRenderViewUBO(QRhiBuffer *rvUBO) { m_rvUbo = rvUBO; } + void setShaderResourceBindings(QRhiShaderResourceBindings *shaderResourceBindings) + { + m_shaderResourceBindings = shaderResourceBindings; + } + void setUBOs(const QHash<int, UBOBuffer> ubos) { m_ubos = ubos; } + + void setAttributesToBindingHash(const QHash<int, int> &attributeNameToBinding) + { + m_attributeNameIdToBindingIndex = attributeNameToBinding; + } + int bindingIndexForAttribute(int attributeNameId) const + { + return m_attributeNameIdToBindingIndex.value(attributeNameId, -1); + } + void increaseScore() { ++m_score; } + void decreaseScore() { --m_score; } + + void cleanup(); + +private: + QRhiBuffer *m_rvUbo; + QRhiBuffer *m_cmdUbo; + QRhiGraphicsPipeline *m_pipeline; + QRhiShaderResourceBindings *m_shaderResourceBindings; + // For user defined uniforms + QHash<int, UBOBuffer> m_ubos; + QHash<int, int> m_attributeNameIdToBindingIndex; + int m_score; +}; + +} // Rhi + +} // Render + +} // Qt3DRender + +QT_END_NAMESPACE + +#endif // QT3DRENDER_RENDER_RHI_RHIGRAPHICSPIPELINE_H diff --git a/src/plugins/renderers/rhi/renderer/rhishader.cpp b/src/plugins/renderers/rhi/renderer/rhishader.cpp new file mode 100644 index 000000000..20da46347 --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/rhishader.cpp @@ -0,0 +1,681 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 "rhishader_p.h" +#include <QMutexLocker> +#include <Qt3DRender/private/stringtoint_p.h> +#include <submissioncontext_p.h> +#include <logging_p.h> +#include <QRegularExpression> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { + +namespace Render { + +namespace Rhi { + +RHIShader::RHIShader() : m_isLoaded(false) +{ + m_shaderCode.resize(static_cast<int>(QShaderProgram::Compute) + 1); +} + +QVector<QString> RHIShader::uniformsNames() const +{ + return m_uniformsNames; +} + +QVector<QString> RHIShader::attributesNames() const +{ + return m_attributesNames; +} + +QVector<QString> RHIShader::uniformBlockNames() const +{ + return m_uniformBlockNames; +} + +QVector<QString> RHIShader::storageBlockNames() const +{ + return m_shaderStorageBlockNames; +} + +QVector<QString> RHIShader::samplerNames() const +{ + return m_samplerNames; +} + +QVector<QString> RHIShader::imagesNames() const +{ + return m_imageNames; +} + +QVector<QByteArray> RHIShader::shaderCode() const +{ + return m_shaderCode; +} + +namespace { +static constexpr QRhiVertexInputAttribute::Format +rhiInputType(QShaderDescription::VariableType type) +{ + switch (type) { + case QShaderDescription::Vec4: + return QRhiVertexInputAttribute::Float4; + case QShaderDescription::Vec3: + return QRhiVertexInputAttribute::Float3; + case QShaderDescription::Vec2: + return QRhiVertexInputAttribute::Float2; + case QShaderDescription::Float: + return QRhiVertexInputAttribute::Float; + default: + // TODO UNormByte4, UNormByte2, UNormByte + RHI_UNIMPLEMENTED; + return QRhiVertexInputAttribute::UNormByte; + break; + } +} + +static constexpr int rhiTypeSize(QShaderDescription::VariableType type) +{ + switch (type) { + case QShaderDescription::Unknown: + return 0; + + case QShaderDescription::Float: + return 1; + case QShaderDescription::Vec2: + return 2; + case QShaderDescription::Vec3: + return 3; + case QShaderDescription::Vec4: + return 4; + case QShaderDescription::Mat2: + return 2 * 2; + case QShaderDescription::Mat2x3: + return 2 * 3; + case QShaderDescription::Mat2x4: + return 2 * 4; + case QShaderDescription::Mat3: + return 3 * 3; + case QShaderDescription::Mat3x2: + return 3 * 2; + case QShaderDescription::Mat3x4: + return 3 * 4; + case QShaderDescription::Mat4: + return 4 * 4; + case QShaderDescription::Mat4x2: + return 4 * 2; + case QShaderDescription::Mat4x3: + return 4 * 3; + + case QShaderDescription::Int: + return 1; + case QShaderDescription::Int2: + return 2; + case QShaderDescription::Int3: + return 3; + case QShaderDescription::Int4: + return 4; + + case QShaderDescription::Uint: + return 1; + case QShaderDescription::Uint2: + return 2; + case QShaderDescription::Uint3: + return 3; + case QShaderDescription::Uint4: + return 4; + + case QShaderDescription::Bool: + return 1; + case QShaderDescription::Bool2: + return 2; + case QShaderDescription::Bool3: + return 3; + case QShaderDescription::Bool4: + return 4; + + case QShaderDescription::Double: + return 1; + case QShaderDescription::Double2: + return 2; + case QShaderDescription::Double3: + return 3; + case QShaderDescription::Double4: + return 4; + case QShaderDescription::DMat2: + return 2 * 2; + case QShaderDescription::DMat2x3: + return 2 * 3; + case QShaderDescription::DMat2x4: + return 2 * 4; + case QShaderDescription::DMat3: + return 3 * 3; + case QShaderDescription::DMat3x2: + return 3 * 2; + case QShaderDescription::DMat3x4: + return 3 * 4; + case QShaderDescription::DMat4: + return 4 * 4; + case QShaderDescription::DMat4x2: + return 4 * 2; + case QShaderDescription::DMat4x3: + return 4 * 3; + + case QShaderDescription::Sampler1D: + return 0; + case QShaderDescription::Sampler2D: + return 0; + case QShaderDescription::Sampler2DMS: + return 0; + case QShaderDescription::Sampler3D: + return 0; + case QShaderDescription::SamplerCube: + return 0; + case QShaderDescription::Sampler1DArray: + return 0; + case QShaderDescription::Sampler2DArray: + return 0; + case QShaderDescription::Sampler2DMSArray: + return 0; + case QShaderDescription::Sampler3DArray: + return 0; + case QShaderDescription::SamplerCubeArray: + return 0; + case QShaderDescription::SamplerRect: + return 0; + case QShaderDescription::SamplerBuffer: + return 0; + + case QShaderDescription::Image1D: + return 0; + case QShaderDescription::Image2D: + return 0; + case QShaderDescription::Image2DMS: + return 0; + case QShaderDescription::Image3D: + return 0; + case QShaderDescription::ImageCube: + return 0; + case QShaderDescription::Image1DArray: + return 0; + case QShaderDescription::Image2DArray: + return 0; + case QShaderDescription::Image2DMSArray: + return 0; + case QShaderDescription::Image3DArray: + return 0; + case QShaderDescription::ImageCubeArray: + return 0; + case QShaderDescription::ImageRect: + return 0; + case QShaderDescription::ImageBuffer: + return 0; + + case QShaderDescription::Struct: + return 0; + default: + return 0; + } +} + +template<typename T, typename Pred> +QVector<T> stableRemoveDuplicates(QVector<T> in, Pred predicate) +{ + QVector<T> out; + for (const auto &element : in) { + if (std::none_of(out.begin(), out.end(), + [&](T &other) { return predicate(element, other); })) + out.push_back(element); + } + return out; +} + +// Utility function to enumerate an array of dimensions +// Given dims == [0, 3, 2] and maxs == [4, 4, 4] +// changes dims into [0, 3, 3] +// Given dims == [0, 3, 3] and maxs == [4, 4, 4] +// changes dims into [1, 0, 0] +bool incrementArray(QVarLengthArray<int> &dims, const QVector<int> &maxs) +{ + const int n = dims.size(); + int i = n; + for (; i-- > 0;) { + if (dims[i] == maxs[i] - 1) { + if (i == 0) { + // we're done + return false; + } + continue; + + } else { + dims[i]++; + for (int j = i + 1; j < n; j++) { + dims[j] = 0; + } + return true; + } + } + return false; +} + +// Call a function with a string such as [0][3][2] +// for all valable array values, given an array of dimension sizes. +// Dimensions must all be >= 1 +template<typename F> +void forEachArrayAccessor(const QVector<int> &maxs, F f) +{ + if (std::any_of(maxs.begin(), maxs.end(), [](int v) { return v <= 0; })) + return; + + QVarLengthArray<int> dims; + dims.resize(maxs.size()); + + // QVarLengthArray does not initialize ints + std::fill(dims.begin(), dims.end(), 0); + + QString str; + + do { + str.resize(0); + for (int k : dims) { + str += QStringLiteral("[%1]").arg(k); + } + f(str); + } while (incrementArray(dims, maxs)); +} +} + +void RHIShader::recordAllUniforms(const QShaderDescription::BlockVariable &member, + QString parentName) +{ + const bool isStruct = !member.structMembers.empty(); + const bool isArray = !member.arrayDims.empty(); + + // "foo.bar" + const QString fullMemberName = parentName + member.name; + m_unqualifiedUniformNames << fullMemberName; + + if (isStruct && !isArray) { + m_structNames << fullMemberName; + m_structNamesIds << StringToInt::lookupId(fullMemberName); + + for (const QShaderDescription::BlockVariable& bv : member.structMembers) { + // recordAllUniforms("baz", "foo.bar.") + recordAllUniforms(bv, fullMemberName + QLatin1Char('.')); + } + } else if (!isStruct && isArray) { + // We iterate through all the [l][n][m] by building [0][0][0] and incrementing + forEachArrayAccessor(member.arrayDims, [&](const QString &str) { + // "foo.bar[1][2]" + m_unqualifiedUniformNames << (fullMemberName + str); + // Question : does it make sense to also record foo[0], foo[0][0], etc... + // if there are e.g. 3 dimensions ? + }); + } + else if (isStruct && isArray) { + // Record the struct names + forEachArrayAccessor(member.arrayDims, [&] (const QString& str) { + m_structNames << (fullMemberName + str); + m_structNamesIds << StringToInt::lookupId(m_structNames.back()); + }); + + // Record the struct members + for (const QShaderDescription::BlockVariable& bv : member.structMembers) { + forEachArrayAccessor(member.arrayDims, [&] (const QString& str) { + //recordAllUniforms("baz", "foo.bar[1][2].") + recordAllUniforms(bv, fullMemberName + str + QLatin1Char('.')); + }); + } + } +} + +void RHIShader::introspect() +{ + const thread_local QRegularExpression generatedUBOName { "_[0-9]+" }; + QVector<QShaderDescription::UniformBlock> rhiUBO; + QVector<QShaderDescription::StorageBlock> rhiSSBO; + + QVector<ShaderUniformBlock> uniformBlocks; + QVector<ShaderStorageBlock> storageBlocks; + QVector<ShaderAttribute> attributes; + QVector<ShaderAttribute> samplers; + QVector<ShaderAttribute> images; + + // Introspect shader vertex input + if (m_stages[QShader::VertexStage].isValid()) { + const QShaderDescription &vtx = m_stages[QShader::VertexStage].description(); + + for (const QShaderDescription::InOutVariable &input : vtx.inputVariables()) { + attributes.push_back(ShaderAttribute { input.name, StringToInt::lookupId(input.name), + input.type, rhiTypeSize(input.type), + input.location }); + } + + rhiUBO += vtx.uniformBlocks(); + rhiSSBO += vtx.storageBlocks(); + } + + // Introspect shader uniforms + + if (m_stages[QShader::FragmentStage].isValid()) { + const QShaderDescription &frag = m_stages[QShader::FragmentStage].description(); + for (const QShaderDescription::InOutVariable &sampler : frag.combinedImageSamplers()) { + samplers.push_back(ShaderAttribute { sampler.name, StringToInt::lookupId(sampler.name), + sampler.type, rhiTypeSize(sampler.type), + sampler.binding }); + } + for (const QShaderDescription::InOutVariable &image : frag.storageImages()) { + images.push_back(ShaderAttribute { image.name, StringToInt::lookupId(image.name), + image.type, rhiTypeSize(image.type), + image.binding }); + } + + rhiUBO += frag.uniformBlocks(); + rhiSSBO += frag.storageBlocks(); + } + + rhiUBO = stableRemoveDuplicates(rhiUBO, + [](const QShaderDescription::UniformBlock &lhs, + const QShaderDescription::UniformBlock &rhs) { + return lhs.blockName == rhs.blockName; + }); + rhiSSBO = stableRemoveDuplicates(rhiSSBO, + [](const QShaderDescription::StorageBlock &lhs, + const QShaderDescription::StorageBlock &rhs) { + return lhs.blockName == rhs.blockName; + }); + + for (const QShaderDescription::UniformBlock &ubo : rhiUBO) { + uniformBlocks.push_back(ShaderUniformBlock { ubo.blockName, + StringToInt::lookupId(ubo.blockName), -1, + ubo.binding, ubo.members.size(), ubo.size }); + const bool addUnqualifiedUniforms = ubo.structName.contains(generatedUBOName); + + // Parse Uniform Block members so that we can later on map a Parameter name to an actual + // member + const QVector<QShaderDescription::BlockVariable> members = ubo.members; + + QVector<int> namesIds; + namesIds.reserve(members.size()); + + for (const QShaderDescription::BlockVariable &member : members) { + namesIds << StringToInt::lookupId(member.name); + if (addUnqualifiedUniforms) { + recordAllUniforms(member, QStringLiteral("")); + } + } + m_uniformsNamesIds += namesIds; + m_uboMembers.push_back({ uniformBlocks.last(), members }); + } + + for (const QShaderDescription::StorageBlock &ssbo : rhiSSBO) { + storageBlocks.push_back(ShaderStorageBlock { ssbo.blockName, -1, -1, ssbo.binding, 0, 0 }); + } + + initializeAttributes(attributes); + initializeUniformBlocks(uniformBlocks); + initializeShaderStorageBlocks(storageBlocks); + initializeSamplers(samplers); + initializeImages(images); +} + +QHash<QString, ShaderUniform> RHIShader::activeUniformsForUniformBlock(int blockIndex) const +{ + return m_uniformBlockIndexToShaderUniforms.value(blockIndex); +} + +ShaderUniformBlock RHIShader::uniformBlockForBlockIndex(int blockIndex) const noexcept +{ + for (int i = 0, m = m_uniformBlocks.size(); i < m; ++i) { + if (m_uniformBlocks[i].m_index == blockIndex) { + return m_uniformBlocks[i]; + } + } + return ShaderUniformBlock(); +} + +ShaderUniformBlock RHIShader::uniformBlockForBlockNameId(int blockNameId) const noexcept +{ + for (int i = 0, m = m_uniformBlocks.size(); i < m; ++i) { + if (m_uniformBlocks[i].m_nameId == blockNameId) { + return m_uniformBlocks[i]; + } + } + return ShaderUniformBlock(); +} + +ShaderUniformBlock RHIShader::uniformBlockForBlockName(const QString &blockName) const noexcept +{ + for (int i = 0, m = m_uniformBlocks.size(); i < m; ++i) { + if (m_uniformBlocks[i].m_name == blockName) { + return m_uniformBlocks[i]; + } + } + return ShaderUniformBlock(); +} + +ShaderStorageBlock RHIShader::storageBlockForBlockIndex(int blockIndex) const noexcept +{ + for (int i = 0, m = m_shaderStorageBlockNames.size(); i < m; ++i) { + if (m_shaderStorageBlocks[i].m_index == blockIndex) + return m_shaderStorageBlocks[i]; + } + return ShaderStorageBlock(); +} + +ShaderStorageBlock RHIShader::storageBlockForBlockNameId(int blockNameId) const noexcept +{ + for (int i = 0, m = m_shaderStorageBlockNames.size(); i < m; ++i) { + if (m_shaderStorageBlocks[i].m_nameId == blockNameId) + return m_shaderStorageBlocks[i]; + } + return ShaderStorageBlock(); +} + +ShaderStorageBlock RHIShader::storageBlockForBlockName(const QString &blockName) const noexcept +{ + for (int i = 0, m = m_shaderStorageBlockNames.size(); i < m; ++i) { + if (m_shaderStorageBlocks[i].m_name == blockName) + return m_shaderStorageBlocks[i]; + } + return ShaderStorageBlock(); +} + +RHIShader::ParameterKind RHIShader::categorizeVariable(int nameId) const noexcept +{ + if (m_uniformsNamesIds.contains(nameId)) + return ParameterKind::Uniform; + else if (m_uniformBlockNamesIds.contains(nameId)) + return ParameterKind::UBO; + else if (m_shaderStorageBlockNamesIds.contains(nameId)) + return ParameterKind::SSBO; + else if (m_structNamesIds.contains(nameId)) + return ParameterKind::Struct; + return ParameterKind::Uniform; +} + +bool RHIShader::hasUniform(int nameId) const noexcept +{ + return m_uniformsNamesIds.contains(nameId); +} + +bool RHIShader::hasActiveVariables() const noexcept +{ + return !m_attributeNamesIds.empty() || !m_uniformsNamesIds.empty() + || !m_uniformBlockNamesIds.empty() || !m_shaderStorageBlockNamesIds.empty(); +} + +void RHIShader::prepareUniforms(ShaderParameterPack &pack) +{ + const PackUniformHash &values = pack.uniforms(); + + auto it = values.keys.cbegin(); + const auto end = values.keys.cend(); + + while (it != end) { + // Find if there's a uniform with the same name id + for (const ShaderUniform &uniform : qAsConst(m_uniforms)) { + if (uniform.m_nameId == *it) { + pack.setSubmissionUniform(uniform); + break; + } + } + ++it; + } +} + +void RHIShader::setFragOutputs(const QHash<QString, int> &fragOutputs) +{ + { + QMutexLocker lock(&m_mutex); + m_fragOutputs = fragOutputs; + } + // updateDNA(); +} + +const QHash<QString, int> RHIShader::fragOutputs() const +{ + QMutexLocker lock(&m_mutex); + return m_fragOutputs; +} + +void RHIShader::initializeAttributes(const QVector<ShaderAttribute> &attributesDescription) +{ + m_attributes = attributesDescription; + m_attributesNames.resize(attributesDescription.size()); + m_attributeNamesIds.resize(attributesDescription.size()); + for (int i = 0, m = attributesDescription.size(); i < m; i++) { + m_attributesNames[i] = attributesDescription[i].m_name; + m_attributes[i].m_nameId = StringToInt::lookupId(m_attributesNames[i]); + m_attributeNamesIds[i] = m_attributes[i].m_nameId; + qCDebug(Shaders) << "Active Attribute " << attributesDescription[i].m_name; + } +} + +void RHIShader::initializeSamplers(const QVector<ShaderAttribute> &samplersDescription) +{ + m_samplers = samplersDescription; + m_samplerNames.resize(samplersDescription.size()); + m_samplerIds.resize(samplersDescription.size()); + for (int i = 0, m = samplersDescription.size(); i < m; i++) { + m_samplerNames[i] = samplersDescription[i].m_name; + m_samplers[i].m_nameId = StringToInt::lookupId(m_samplerNames[i]); + m_samplerIds[i] = m_samplers[i].m_nameId; + qCDebug(Shaders) << "Active sampler " << samplersDescription[i].m_name; + } +} + +void RHIShader::initializeImages(const QVector<ShaderAttribute> &imagesDescription) +{ + m_images = imagesDescription; + m_imageNames.resize(imagesDescription.size()); + m_imageIds.resize(imagesDescription.size()); + for (int i = 0, m = imagesDescription.size(); i < m; i++) { + m_imageNames[i] = imagesDescription[i].m_name; + m_images[i].m_nameId = StringToInt::lookupId(m_imageNames[i]); + m_imageIds[i] = m_images[i].m_nameId; + qCDebug(Shaders) << "Active image " << imagesDescription[i].m_name; + } +} + +void RHIShader::initializeUniformBlocks(const QVector<ShaderUniformBlock> &uniformBlockDescription) +{ + m_uniformBlocks = uniformBlockDescription; + m_uniformBlockNames.resize(uniformBlockDescription.size()); + m_uniformBlockNamesIds.resize(uniformBlockDescription.size()); + for (int i = 0, m = uniformBlockDescription.size(); i < m; ++i) { + m_uniformBlockNames[i] = m_uniformBlocks[i].m_name; + m_uniformBlockNamesIds[i] = StringToInt::lookupId(m_uniformBlockNames[i]); + m_uniformBlocks[i].m_nameId = m_uniformBlockNamesIds[i]; + qCDebug(Shaders) << "Initializing Uniform Block {" << m_uniformBlockNames[i] << "}"; + + // Find all active uniforms for the shader block + QVector<ShaderUniform>::const_iterator uniformsIt = m_uniforms.cbegin(); + const QVector<ShaderUniform>::const_iterator uniformsEnd = m_uniforms.cend(); + + QVector<QString>::const_iterator uniformNamesIt = m_uniformsNames.cbegin(); + const QVector<QString>::const_iterator uniformNamesEnd = m_attributesNames.cend(); + + QHash<QString, ShaderUniform> activeUniformsInBlock; + + while (uniformsIt != uniformsEnd && uniformNamesIt != uniformNamesEnd) { + if (uniformsIt->m_blockIndex == uniformBlockDescription[i].m_index) { + QString uniformName = *uniformNamesIt; + if (!m_uniformBlockNames[i].isEmpty() + && !uniformName.startsWith(m_uniformBlockNames[i])) + uniformName = m_uniformBlockNames[i] + QLatin1Char('.') + *uniformNamesIt; + activeUniformsInBlock.insert(uniformName, *uniformsIt); + qCDebug(Shaders) << "Active Uniform Block " << uniformName << " in block " + << m_uniformBlockNames[i] << " at index " + << uniformsIt->m_blockIndex; + } + ++uniformsIt; + ++uniformNamesIt; + } + m_uniformBlockIndexToShaderUniforms.insert(uniformBlockDescription[i].m_index, + activeUniformsInBlock); + } +} + +void RHIShader::initializeShaderStorageBlocks( + const QVector<ShaderStorageBlock> &shaderStorageBlockDescription) +{ + m_shaderStorageBlocks = shaderStorageBlockDescription; + m_shaderStorageBlockNames.resize(shaderStorageBlockDescription.size()); + m_shaderStorageBlockNamesIds.resize(shaderStorageBlockDescription.size()); + + for (int i = 0, m = shaderStorageBlockDescription.size(); i < m; ++i) { + m_shaderStorageBlockNames[i] = m_shaderStorageBlocks[i].m_name; + m_shaderStorageBlockNamesIds[i] = StringToInt::lookupId(m_shaderStorageBlockNames[i]); + m_shaderStorageBlocks[i].m_nameId = m_shaderStorageBlockNamesIds[i]; + qCDebug(Shaders) << "Initializing Shader Storage Block {" << m_shaderStorageBlockNames[i] + << "}"; + } +} + +} // Rhi + +} // Render + +} // Qt3DRender + +QT_END_NAMESPACE diff --git a/src/plugins/renderers/rhi/renderer/rhishader_p.h b/src/plugins/renderers/rhi/renderer/rhishader_p.h new file mode 100644 index 000000000..774dcbdb0 --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/rhishader_p.h @@ -0,0 +1,198 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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_RHI_GLSHADER_P_H +#define QT3DRENDER_RENDER_RHI_GLSHADER_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 <shadervariables_p.h> +#include <shaderparameterpack_p.h> +#include <Qt3DRender/qshaderprogram.h> +#include <QMutex> +#include <QtGui/private/qshader_p.h> +#include <QtGui/private/qrhi_p.h> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { + +namespace Render { + +namespace Rhi { + +class Q_AUTOTEST_EXPORT RHIShader +{ +public: + struct UBO_Member + { + ShaderUniformBlock block; + QVector<QShaderDescription::BlockVariable> members; + }; + + RHIShader(); + + bool isLoaded() const { return m_isLoaded; } + void setLoaded(bool loaded) { m_isLoaded = loaded; } + + void prepareUniforms(ShaderParameterPack &pack); + + void setFragOutputs(const QHash<QString, int> &fragOutputs); + const QHash<QString, int> fragOutputs() const; + + inline QVector<int> uniformsNamesIds() const { return m_uniformsNamesIds; } + inline QVector<int> standardUniformNameIds() const { return m_standardUniformNamesIds; } + inline QVector<int> uniformBlockNamesIds() const { return m_uniformBlockNamesIds; } + inline QVector<int> storageBlockNamesIds() const { return m_shaderStorageBlockNamesIds; } + inline QVector<int> attributeNamesIds() const { return m_attributeNamesIds; } + + QVector<QString> uniformsNames() const; + QVector<QString> attributesNames() const; + QVector<QString> uniformBlockNames() const; + QVector<QString> storageBlockNames() const; + QVector<QString> samplerNames() const; + QVector<QString> imagesNames() const; + + inline QVector<ShaderUniform> uniforms() const { return m_uniforms; } + inline QVector<ShaderAttribute> attributes() const { return m_attributes; } + inline QVector<ShaderUniformBlock> uniformBlocks() const { return m_uniformBlocks; } + inline QVector<ShaderStorageBlock> storageBlocks() const { return m_shaderStorageBlocks; } + inline QVector<ShaderAttribute> samplers() const { return m_samplers; } + inline QVector<ShaderAttribute> images() const { return m_images; } + + QHash<QString, ShaderUniform> activeUniformsForUniformBlock(int blockIndex) const; + + ShaderUniformBlock uniformBlockForBlockIndex(int blockNameId) const noexcept; + ShaderUniformBlock uniformBlockForBlockNameId(int blockIndex) const noexcept; + ShaderUniformBlock uniformBlockForBlockName(const QString &blockName) const noexcept; + + ShaderStorageBlock storageBlockForBlockIndex(int blockIndex) const noexcept; + ShaderStorageBlock storageBlockForBlockNameId(int blockNameId) const noexcept; + ShaderStorageBlock storageBlockForBlockName(const QString &blockName) const noexcept; + + enum ParameterKind { Uniform, UBO, SSBO, Struct }; + ParameterKind categorizeVariable(int nameId) const noexcept; + + bool hasUniform(int nameId) const noexcept; + bool hasActiveVariables() const noexcept; + + void setShaderCode(const QVector<QByteArray> shaderCode) { m_shaderCode = shaderCode; } + QVector<QByteArray> shaderCode() const; + + const QShader &shaderStage(QShader::Stage stage) const noexcept { return m_stages[stage]; } + QVector<UBO_Member> uboMembers() const { return m_uboMembers; } + + const QSet<QString> &unqualifiedUniformNames() const noexcept + { + return m_unqualifiedUniformNames; + } + + void introspect(); + +private: + bool m_isLoaded; + QShader m_stages[6]; + + QVector<QString> m_uniformsNames; + QVector<int> m_uniformsNamesIds; + QVector<int> m_standardUniformNamesIds; + QVector<ShaderUniform> m_uniforms; + + QVector<QString> m_attributesNames; + QVector<int> m_attributeNamesIds; + QVector<ShaderAttribute> m_attributes; + + QVector<QString> m_uniformBlockNames; + QVector<int> m_uniformBlockNamesIds; + QVector<ShaderUniformBlock> m_uniformBlocks; + QHash<int, QHash<QString, ShaderUniform>> m_uniformBlockIndexToShaderUniforms; + QSet<QString> m_unqualifiedUniformNames; + + QVector<QString> m_shaderStorageBlockNames; + QVector<int> m_shaderStorageBlockNamesIds; + QVector<ShaderStorageBlock> m_shaderStorageBlocks; + + QVector<QString> m_samplerNames; + QVector<int> m_samplerIds; + QVector<ShaderAttribute> m_samplers; + + QVector<QString> m_imageNames; + QVector<int> m_imageIds; + QVector<ShaderAttribute> m_images; + + QVector<QString> m_structNames; + QVector<int> m_structNamesIds; + + QHash<QString, int> m_fragOutputs; + QVector<QByteArray> m_shaderCode; + + // Private so that only SubmissionContext can call it + friend class SubmissionContext; + void initializeAttributes(const QVector<ShaderAttribute> &attributesDescription); + void initializeUniformBlocks(const QVector<ShaderUniformBlock> &uniformBlockDescription); + void + initializeShaderStorageBlocks(const QVector<ShaderStorageBlock> &shaderStorageBlockDescription); + void initializeSamplers(const QVector<ShaderAttribute> &samplerDescription); + void initializeImages(const QVector<ShaderAttribute> &imageDescription); + void recordAllUniforms(const QShaderDescription::BlockVariable &ubo, QString parentName); + + QVector<UBO_Member> m_uboMembers; + + mutable QMutex m_mutex; + QMetaObject::Connection m_contextConnection; +}; + +} // Rhi + +} // Render + +} // Qt3DRender + +QT_END_NAMESPACE + +#endif // QT3DRENDER_RENDER_RHI_GLSHADER_P_H diff --git a/src/plugins/renderers/rhi/renderer/shaderparameterpack.cpp b/src/plugins/renderers/rhi/renderer/shaderparameterpack.cpp new file mode 100644 index 000000000..5a28d28d4 --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/shaderparameterpack.cpp @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 <submissioncontext_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 { +namespace Rhi { + +ShaderParameterPack::~ShaderParameterPack() { } + +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 (NamedResource &texture : m_textures) { + if (texture.glslNameId != glslNameId || texture.uniformArrayIndex != uniformArrayIndex) + continue; + + texture.nodeId = texId; + return; + } + + m_textures.append(NamedResource(glslNameId, texId, uniformArrayIndex, NamedResource::Texture)); +} + +void ShaderParameterPack::setImage(const int glslNameId, int uniformArrayIndex, + Qt3DCore::QNodeId id) +{ + for (NamedResource &image : m_images) { + if (image.glslNameId != glslNameId || image.uniformArrayIndex != uniformArrayIndex) + continue; + + image.nodeId = id; + return; + } + + m_images.append(NamedResource(glslNameId, id, uniformArrayIndex, NamedResource::Image)); +} + +// 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(blockToSSBO); +} + +void ShaderParameterPack::setSubmissionUniform(const ShaderUniform &uniform) +{ + m_submissionUniforms.push_back(uniform); +} + +} // namespace Rhi +} // namespace Render +} // namespace Qt3DRender + +QT_END_NAMESPACE diff --git a/src/plugins/renderers/rhi/renderer/shaderparameterpack_p.h b/src/plugins/renderers/rhi/renderer/shaderparameterpack_p.h new file mode 100644 index 000000000..94c485a94 --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/shaderparameterpack_p.h @@ -0,0 +1,215 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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_RHI_SHADERPARAMETERPACK_P_H +#define QT3DRENDER_RENDER_RHI_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/uniform_p.h> +#include <shadervariables_p.h> + +QT_BEGIN_NAMESPACE + +class QOpenGLShaderProgram; + +namespace Qt3DCore { +class QFrameAllocator; +} + +namespace Qt3DRender { +namespace Render { +namespace Rhi { + +class GraphicsContext; + +struct BlockToUBO +{ + int m_blockIndex; + Qt3DCore::QNodeId m_bufferID; + bool m_needsUpdate; + QHash<QString, QVariant> m_updatedProperties; +}; +QT3D_DECLARE_TYPEINFO_3(Qt3DRender, Render, Rhi, BlockToUBO, Q_MOVABLE_TYPE) + +struct BlockToSSBO +{ + int m_blockIndex; + int m_bindingIndex; + Qt3DCore::QNodeId m_bufferID; +}; +QT3D_DECLARE_TYPEINFO_3(Qt3DRender, Render, Rhi, BlockToSSBO, Q_PRIMITIVE_TYPE) + +struct PackUniformHash +{ + QVector<int> keys; + QVector<UniformValue> values; + + PackUniformHash() + { + keys.reserve(10); + values.reserve(10); + } + + void insert(int key, const UniformValue &value) + { + const int idx = keys.indexOf(key); + if (idx != -1) { + values[idx] = value; + } else { + keys.push_back(key); + values.push_back(value); + } + } + + UniformValue value(int key) const + { + const int idx = keys.indexOf(key); + if (idx != -1) + return values.at(idx); + return UniformValue(); + } + + UniformValue &value(int key) + { + const int idx = keys.indexOf(key); + if (idx != -1) + return values[idx]; + insert(key, UniformValue()); + return value(key); + } + + void erase(int idx) + { + keys.removeAt(idx); + values.removeAt(idx); + } + + bool contains(int key) const { return keys.contains(key); } +}; + +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 setImage(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 NamedResource + { + enum Type { Texture = 0, Image }; + + NamedResource() { } + NamedResource(const int glslNameId, Qt3DCore::QNodeId texId, int uniformArrayIndex, + Type type) + : glslNameId(glslNameId), + nodeId(texId), + uniformArrayIndex(uniformArrayIndex), + type(type) + { + } + + int glslNameId; + Qt3DCore::QNodeId nodeId; + int uniformArrayIndex; + Type type; + + bool operator==(const NamedResource &other) const + { + return glslNameId == other.glslNameId && nodeId == other.nodeId + && uniformArrayIndex == other.uniformArrayIndex && type == other.type; + } + + bool operator!=(const NamedResource &other) const { return !(*this == other); } + }; + + inline QVector<NamedResource> textures() const { return m_textures; } + inline QVector<NamedResource> images() const { return m_images; } + 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<NamedResource> m_textures; + QVector<NamedResource> m_images; + QVector<BlockToUBO> m_uniformBuffers; + QVector<BlockToSSBO> m_shaderStorageBuffers; + QVector<ShaderUniform> m_submissionUniforms; + + friend class RenderView; +}; +QT3D_DECLARE_TYPEINFO_3(Qt3DRender, Render, Rhi, ShaderParameterPack::NamedResource, + Q_PRIMITIVE_TYPE) + +} // namespace Rhi +} // namespace Render +} // namespace Qt3DRender + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(Qt3DRender::Render::Rhi::ShaderParameterPack) + +#endif // QT3DRENDER_RENDER_RHI_SHADERPARAMETERPACK_P_H diff --git a/src/plugins/renderers/rhi/renderer/shadervariables_p.h b/src/plugins/renderers/rhi/renderer/shadervariables_p.h new file mode 100644 index 000000000..4e710a92c --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/shadervariables_p.h @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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_RHI_SHADERVARIABLES_P_H +#define QT3DRENDER_RENDER_RHI_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 <QtGui/private/qshaderdescription_p.h> +#include <QOpenGLContext> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { + +namespace Render { + +namespace Rhi { + +struct ShaderAttribute +{ + QString m_name; + int m_nameId { -1 }; + QShaderDescription::VariableType m_type {}; + int m_size {}; + int m_location { -1 }; +}; +QT3D_DECLARE_TYPEINFO_3(Qt3DRender, Render, Rhi, ShaderAttribute, Q_MOVABLE_TYPE) + +struct ShaderUniform +{ + QString m_name; + int m_nameId { -1 }; + QShaderDescription::VariableType m_type { QShaderDescription::Unknown }; + int m_size { 0 }; + int m_offset { -1 }; // -1 default, >= 0 if uniform defined in uniform block + int m_location { -1 }; // -1 if uniform defined in a uniform block + int m_blockIndex { -1 }; // -1 is the default, >= 0 if uniform defined in uniform block + int m_arrayStride { + -1 + }; // -1 is the default, >= 0 if uniform defined in uniform block and if it's an array + int m_matrixStride { + -1 + }; // -1 is the default, >= 0 uniform defined in uniform block and is a matrix + uint m_rawByteSize { 0 }; // contains byte size (size / type / strides) + // size, offset and strides are in bytes +}; +QT3D_DECLARE_TYPEINFO_3(Qt3DRender, Render, Rhi, ShaderUniform, Q_MOVABLE_TYPE) + +struct ShaderUniformBlock +{ + QString m_name; + int m_nameId { -1 }; + int m_index { -1 }; + int m_binding { -1 }; + int m_activeUniformsCount { 0 }; + int m_size { 0 }; +}; +QT3D_DECLARE_TYPEINFO_3(Qt3DRender, Render, Rhi, ShaderUniformBlock, Q_MOVABLE_TYPE) + +struct ShaderStorageBlock +{ + QString m_name; + int m_nameId { -1 }; + int m_index { -1 }; + int m_binding { -1 }; + int m_size { 0 }; + int m_activeVariablesCount { 0 }; +}; +QT3D_DECLARE_TYPEINFO_3(Qt3DRender, Render, Rhi, ShaderStorageBlock, Q_MOVABLE_TYPE) + +} // namespace Rhi + +} // namespace Render + +} // namespace Qt3DRender + +QT_END_NAMESPACE + +#endif // QT3DRENDER_RENDER_RHI_SHADERVARIABLES_P_H diff --git a/src/plugins/renderers/rhi/rhi.pri b/src/plugins/renderers/rhi/rhi.pri new file mode 100644 index 000000000..8ae4a11af --- /dev/null +++ b/src/plugins/renderers/rhi/rhi.pri @@ -0,0 +1,17 @@ + +include (renderer/renderer.pri) +include (jobs/jobs.pri) +include (io/io.pri) +include (textures/textures.pri) +include (graphicshelpers/graphicshelpers.pri) +include (managers/managers.pri) + +INCLUDEPATH += $$PWD + +# Qt3D is free of Q_FOREACH - make sure it stays that way: +DEFINES += QT_NO_FOREACH + +gcov { + QMAKE_CXXFLAGS += -fprofile-arcs -ftest-coverage + QMAKE_LFLAGS += -fprofile-arcs -ftest-coverage +} diff --git a/src/plugins/renderers/rhi/rhi.pro b/src/plugins/renderers/rhi/rhi.pro new file mode 100644 index 000000000..786749a71 --- /dev/null +++ b/src/plugins/renderers/rhi/rhi.pro @@ -0,0 +1,32 @@ +TARGET = rhirenderer + +PLUGIN_TYPE = renderers +PLUGIN_CLASS_NAME = RhiRendererPlugin +load(qt_plugin) + +QT += core-private gui-private 3dcore 3dcore-private 3drender 3drender-private shadertools shadertools-private + +# Qt3D is free of Q_FOREACH - make sure it stays that way: +DEFINES += QT_NO_FOREACH + +# We use QT_AUTOTEST_EXPORT to test the plug-ins, which needs QT_BUILDING_QT +DEFINES += QT_BUILDING_QT + +SOURCES += \ + main.cpp + +DISTFILES += \ + rhirenderer.json + +include(rhi.pri) + +qtConfig(qt3d-simd-avx2) { + CONFIG += simd + QMAKE_CXXFLAGS += $$QMAKE_CFLAGS_AVX2 +} + +qtConfig(qt3d-simd-sse2):!qtConfig(qt3d-simd-avx2) { + CONFIG += simd + QMAKE_CXXFLAGS += $$QMAKE_CFLAGS_SSE2 +} + diff --git a/src/plugins/renderers/rhi/rhirenderer.json b/src/plugins/renderers/rhi/rhirenderer.json new file mode 100644 index 000000000..145b20636 --- /dev/null +++ b/src/plugins/renderers/rhi/rhirenderer.json @@ -0,0 +1,3 @@ +{ + "Keys": ["rhi"] +} diff --git a/src/plugins/renderers/rhi/textures/renderbuffer.cpp b/src/plugins/renderers/rhi/textures/renderbuffer.cpp new file mode 100644 index 000000000..8df256ad1 --- /dev/null +++ b/src/plugins/renderers/rhi/textures/renderbuffer.cpp @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://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 "renderbuffer_p.h" +#include <QtGui/QOpenGLContext> +#include <QtGui/QOpenGLFunctions> + +QT_BEGIN_NAMESPACE + +namespace Qt3DRender { +namespace Render { +namespace Rhi { + +RenderBuffer::RenderBuffer(int width, int height, QAbstractTexture::TextureFormat format) + : m_size(width, height), m_format(format), m_renderBuffer(0), m_context(nullptr) +{ + QOpenGLContext *ctx = QOpenGLContext::currentContext(); + if (!ctx) { + qWarning("Renderbuffer requires an OpenGL context"); + return; + } + + m_context = ctx; + QOpenGLFunctions *f = ctx->functions(); + f->glGenRenderbuffers(1, &m_renderBuffer); + if (!m_renderBuffer) + return; + + f->glBindRenderbuffer(GL_RENDERBUFFER, m_renderBuffer); + while (f->glGetError() != GL_NO_ERROR) { } + f->glRenderbufferStorage(GL_RENDERBUFFER, format, width, height); + GLint err = f->glGetError(); + if (err != GL_NO_ERROR) + qWarning("Failed to set renderbuffer storage: error 0x%x", err); + f->glBindRenderbuffer(GL_RENDERBUFFER, 0); +} + +RenderBuffer::~RenderBuffer() +{ + if (m_renderBuffer) { + QOpenGLContext *ctx = QOpenGLContext::currentContext(); + + // Ignore the fact that renderbuffers are sharable resources and let's + // just expect that the context is the same as when the resource was + // created. QOpenGLTexture suffers from the same limitation anyway, and + // this is unlikely to become an issue within Qt 3D. + if (ctx == m_context) { + ctx->functions()->glDeleteRenderbuffers(1, &m_renderBuffer); + } else { + qWarning("Wrong current context; renderbuffer not destroyed"); + } + } +} + +void RenderBuffer::bind() +{ + if (!m_renderBuffer) + return; + + m_context->functions()->glBindRenderbuffer(GL_RENDERBUFFER, m_renderBuffer); +} + +void RenderBuffer::release() +{ + if (!m_context) + return; + + m_context->functions()->glBindRenderbuffer(GL_RENDERBUFFER, 0); +} + +} // namespace Rhi +} // namespace Render +} // namespace Qt3DRender + +QT_END_NAMESPACE diff --git a/src/plugins/renderers/rhi/textures/renderbuffer_p.h b/src/plugins/renderers/rhi/textures/renderbuffer_p.h new file mode 100644 index 000000000..ed043366e --- /dev/null +++ b/src/plugins/renderers/rhi/textures/renderbuffer_p.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://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_RHI_RENDERBUFFER_P_H +#define QT3DRENDER_RENDER_RHI_RENDERBUFFER_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/qabstracttexture.h> + +QT_BEGIN_NAMESPACE + +class QOpenGLContext; + +namespace Qt3DRender { +namespace Render { +namespace Rhi { + +class Q_AUTOTEST_EXPORT RenderBuffer +{ +public: + RenderBuffer(int width, int height, QAbstractTexture::TextureFormat format); + ~RenderBuffer(); + + int width() const { return m_size.width(); } + int height() const { return m_size.height(); } + QSize size() const { return m_size; } + QAbstractTexture::TextureFormat format() const { return m_format; } + GLuint renderBufferId() const { return m_renderBuffer; } + + void bind(); + void release(); + +private: + QSize m_size; + QAbstractTexture::TextureFormat m_format; + GLuint m_renderBuffer; + QOpenGLContext *m_context; +}; + +} // namespace Rhi +} // namespace Render +} // namespace Qt3DRender + +QT_END_NAMESPACE + +#endif // QT3DRENDER_RENDER_RHI_RENDERBUFFER_P_H diff --git a/src/plugins/renderers/rhi/textures/texture.cpp b/src/plugins/renderers/rhi/textures/texture.cpp new file mode 100644 index 000000000..53f9e62ca --- /dev/null +++ b/src/plugins/renderers/rhi/textures/texture.cpp @@ -0,0 +1,918 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 <QtCore/qhash.h> +#include "texture_p.h" + +#include <private/qdebug_p.h> +#include <private/qrhi_p.h> +#include <QDebug> +#include <Qt3DRender/qtexture.h> +#include <Qt3DRender/qtexturedata.h> +#include <Qt3DRender/qtextureimagedata.h> +#include <Qt3DRender/private/managers_p.h> +#include <Qt3DRender/private/qabstracttexture_p.h> +#include <Qt3DRender/private/qtextureimagedata_p.h> +#include <renderbuffer_p.h> +#include <submissioncontext_p.h> + +QT_BEGIN_NAMESPACE + +using namespace Qt3DCore; + +namespace Qt3DRender { +namespace Render { +namespace Rhi { + +namespace { + +bool issRGBFormat(QAbstractTexture::TextureFormat format) noexcept +{ + switch (format) { + case QAbstractTexture::SRGB8: + case QAbstractTexture::SRGB8_ETC2: + case QAbstractTexture::SRGB8_PunchThrough_Alpha1_ETC2: + return true; + default: + return false; + } +} + +QRhiTexture::Format rhiFormatFromTextureFormat(QAbstractTexture::TextureFormat format) noexcept +{ + switch (format) { + case QAbstractTexture::RGBA8_UNorm: + case QAbstractTexture::SRGB8: + return QRhiTexture::RGBA8; + case QAbstractTexture::R8_UNorm: + return QRhiTexture::R8; + case QAbstractTexture::R16_UNorm: + return QRhiTexture::R16; + case QAbstractTexture::RGBA16F: + return QRhiTexture::RGBA16F; + case QAbstractTexture::RGBA32F: + return QRhiTexture::RGBA32F; + case QAbstractTexture::R16F: + return QRhiTexture::R16F; + case QAbstractTexture::R32F: + return QRhiTexture::R32F; + case QAbstractTexture::D16: + return QRhiTexture::D16; + case QAbstractTexture::D32F: + return QRhiTexture::D32F; + case QAbstractTexture::RGB_DXT1: + case QAbstractTexture::RGBA_DXT1: + return QRhiTexture::BC1; + case QAbstractTexture::RGBA_DXT3: + return QRhiTexture::BC2; + case QAbstractTexture::RGBA_DXT5: + return QRhiTexture::BC3; + case QAbstractTexture::RGB8_ETC2: + case QAbstractTexture::SRGB8_ETC2: + return QRhiTexture::ETC2_RGB8; + case QAbstractTexture::RGB8_PunchThrough_Alpha1_ETC2: + case QAbstractTexture::SRGB8_PunchThrough_Alpha1_ETC2: + return QRhiTexture::ETC2_RGB8A1; + case QAbstractTexture::RGBA8_ETC2_EAC: + return QRhiTexture::ETC2_RGBA8; + default: + Q_UNREACHABLE(); + return QRhiTexture::UnknownFormat; + } +} + +QRhiSampler::Filter rhiFilterFromTextureFilter(QAbstractTexture::Filter filter) noexcept +{ + switch (filter) { + case QAbstractTexture::Nearest: + case QAbstractTexture::NearestMipMapNearest: + case QAbstractTexture::NearestMipMapLinear: + return QRhiSampler::Nearest; + case QAbstractTexture::Linear: + case QAbstractTexture::LinearMipMapNearest: + case QAbstractTexture::LinearMipMapLinear: + return QRhiSampler::Linear; + default: + Q_UNREACHABLE(); + return QRhiSampler::Nearest; + } +} + +QRhiSampler::Filter rhiMipMapFilterFromTextureFilter(QAbstractTexture::Filter filter) noexcept +{ + switch (filter) { + case QAbstractTexture::NearestMipMapNearest: + case QAbstractTexture::LinearMipMapNearest: + return QRhiSampler::Nearest; + case QAbstractTexture::Linear: + case QAbstractTexture::NearestMipMapLinear: + case QAbstractTexture::LinearMipMapLinear: + return QRhiSampler::Linear; + default: + Q_UNREACHABLE(); + return QRhiSampler::None; + } +} + +std::tuple<QRhiSampler::AddressMode, QRhiSampler::AddressMode, QRhiSampler::AddressMode> +rhiWrapModeFromTextureWrapMode(QTextureWrapMode::WrapMode x, QTextureWrapMode::WrapMode y, + QTextureWrapMode::WrapMode z) noexcept +{ + auto toRhiAddress = [](QTextureWrapMode::WrapMode mode) noexcept { + switch (mode) { + case Qt3DRender::QTextureWrapMode::Repeat: + return QRhiSampler::Repeat; + case Qt3DRender::QTextureWrapMode::ClampToEdge: + case Qt3DRender::QTextureWrapMode::ClampToBorder: + return QRhiSampler::ClampToEdge; + case Qt3DRender::QTextureWrapMode::MirroredRepeat: + return QRhiSampler::Mirror; + default: + Q_UNREACHABLE(); + } + }; + + return { toRhiAddress(x), toRhiAddress(y), toRhiAddress(z) }; +} + +QRhiSampler::CompareOp +rhiCompareOpFromTextureCompareOp(QAbstractTexture::ComparisonFunction mode) noexcept +{ + switch (mode) { + case QAbstractTexture::CompareLessEqual: + return QRhiSampler::LessOrEqual; + case QAbstractTexture::CompareGreaterEqual: + return QRhiSampler::GreaterOrEqual; + case QAbstractTexture::CompareLess: + return QRhiSampler::Less; + case QAbstractTexture::CompareGreater: + return QRhiSampler::Greater; + case QAbstractTexture::CompareEqual: + return QRhiSampler::Equal; + case QAbstractTexture::CommpareNotEqual: + return QRhiSampler::NotEqual; + case QAbstractTexture::CompareAlways: + return QRhiSampler::Always; + case QAbstractTexture::CompareNever: + return QRhiSampler::Never; + default: + return QRhiSampler::Always; + } +} + +// This uploadGLData where the data is a fullsize subimage +// as QOpenGLTexture doesn't allow partial subimage uploads +QRhiTextureUploadEntry createUploadEntry(int level, int layer, const QByteArray &bytes) noexcept +{ + QRhiTextureSubresourceUploadDescription description; + description.setData(bytes); + return QRhiTextureUploadEntry(layer, level, description); +} + +template<typename F> +void filterLayersAndFaces(const QTextureImageData &data, F f) +{ + const int layers = data.layers(); + const int faces = data.faces(); + const int miplevels = data.mipLevels(); + + if (layers == 1 && faces == 1) { + for (int level = 0; level < miplevels; level++) { + f(createUploadEntry(level, 0, data.data(0, 0, level))); + } + } else if (layers > 1 && faces == 1) { + qWarning() << Q_FUNC_INFO << "Unsupported case, see QTBUG-83343"; + /* + for (int layer = 0; layer < data.layers(); layer++) { + for (int level = 0; level < mipLevels; level++) { + f(createUploadEntry(level, layer, data.data(layer, 0, level))); + } + } + */ + } else if (faces > 1 && layers == 1) { + // Mip levels do not seem to be supported by cubemaps... + for (int face = 0; face < data.faces(); face++) { + f(createUploadEntry(0, face, data.data(0, face, 0))); + } + } else { + qWarning() << Q_FUNC_INFO << "Unsupported case"; + } +} + +template<typename F> +void filterLayerAndFace(int layer, int face, F f) +{ + if (layer == 0 && face == 0) { + f(0); + } else if (layer > 0 && face == 0) { + qWarning() << Q_FUNC_INFO << "Unsupported case, see QTBUG-83343"; + // f(layer); + } else if (layer == 0 && face > 0) { + f(face); + } else { + qWarning() << Q_FUNC_INFO << "Unsupported case"; + } +} + +// For partial sub image uploads +QRhiTextureUploadEntry createUploadEntry(int mipLevel, int layer, int xOffset, int yOffset, + int zOffset, const QByteArray &bytes, + const QTextureImageDataPtr &data) noexcept +{ + QRhiTextureSubresourceUploadDescription description; + description.setData(bytes); + description.setSourceTopLeft(QPoint(xOffset, yOffset)); + return QRhiTextureUploadEntry(layer, mipLevel, description); +} + +} // anonymous + +RHITexture::RHITexture() + : m_dirtyFlags(None), + m_rhi(nullptr), + m_rhiSampler(nullptr), + m_renderBuffer(nullptr), + m_dataFunctor(), + m_pendingDataFunctor(nullptr), + m_sharedTextureId(-1), + m_externalRendering(false), + m_wasTextureRecreated(false) +{ +} + +RHITexture::~RHITexture() { } + +// Must be called from RenderThread with active GL context +void RHITexture::destroy() +{ + delete m_rhi; + m_rhi = nullptr; + delete m_rhiSampler; + m_rhiSampler = nullptr; + delete m_renderBuffer; + m_renderBuffer = nullptr; + + m_dirtyFlags = None; + m_sharedTextureId = -1; + m_externalRendering = false; + m_wasTextureRecreated = false; + m_dataFunctor.reset(); + m_pendingDataFunctor = nullptr; + + m_properties = {}; + m_parameters = {}; + m_textureData.reset(); + m_images.clear(); + m_imageData.clear(); + m_pendingTextureDataUpdates.clear(); +} + +bool RHITexture::loadTextureDataFromGenerator() +{ + m_textureData = m_dataFunctor->operator()(); + // if there is a texture generator, most properties will be defined by it + if (m_textureData) { + const QAbstractTexture::Target target = m_textureData->target(); + + // If both target and functor return Automatic we are still + // probably loading the texture, return false + if (m_properties.target == QAbstractTexture::TargetAutomatic + && target == QAbstractTexture::TargetAutomatic) { + m_textureData.reset(); + return false; + } + + if (m_properties.target != QAbstractTexture::TargetAutomatic + && target != QAbstractTexture::TargetAutomatic && m_properties.target != target) { + qWarning() << Q_FUNC_INFO + << "Generator and Properties not requesting the same texture target"; + m_textureData.reset(); + return false; + } + + // We take target type from generator if it wasn't explicitly set by the user + if (m_properties.target == QAbstractTexture::TargetAutomatic) + m_properties.target = target; + m_properties.width = m_textureData->width(); + m_properties.height = m_textureData->height(); + m_properties.depth = m_textureData->depth(); + m_properties.layers = m_textureData->layers(); + m_properties.format = m_textureData->format(); + + const QVector<QTextureImageDataPtr> imageData = m_textureData->imageData(); + + if (!imageData.empty()) { + // Set the mips level based on the first image if autoMipMapGeneration is disabled + if (!m_properties.generateMipMaps) + m_properties.mipLevels = imageData.first()->mipLevels(); + } + } + return !m_textureData.isNull(); +} + +void RHITexture::loadTextureDataFromImages() +{ + int maxMipLevel = 0; + for (const Image &img : qAsConst(m_images)) { + const QTextureImageDataPtr imgData = img.generator->operator()(); + // imgData may be null in the following cases: + // - Texture is created with TextureImages which have yet to be + // loaded (skybox where you don't yet know the path, source set by + // a property binding, queued connection ...) + // - TextureImage whose generator failed to return a valid data + // (invalid url, error opening file...) + if (imgData.isNull()) + continue; + + m_imageData.push_back(imgData); + maxMipLevel = qMax(maxMipLevel, img.mipLevel); + + // If the texture doesn't have a texture generator, we will + // derive some properties from the first TextureImage (layer=0, miplvl=0, face=0) + if (!m_textureData && img.layer == 0 && img.mipLevel == 0 + && img.face == QAbstractTexture::CubeMapPositiveX) { + if (imgData->width() != -1 && imgData->height() != -1 && imgData->depth() != -1) { + m_properties.width = imgData->width(); + m_properties.height = imgData->height(); + m_properties.depth = imgData->depth(); + } + // Set the format of the texture if the texture format is set to Automatic + if (m_properties.format == QAbstractTexture::Automatic) { + m_properties.format = + static_cast<QAbstractTexture::TextureFormat>(imgData->format()); + } + setDirtyFlag(Properties, true); + } + } + + // make sure the number of mip levels is set when there is no texture data generator + if (!m_dataFunctor) { + m_properties.mipLevels = maxMipLevel + 1; + setDirtyFlag(Properties, true); + } +} + +// Called from RenderThread +RHITexture::TextureUpdateInfo RHITexture::createOrUpdateRhiTexture(SubmissionContext *ctx) +{ + TextureUpdateInfo textureInfo; + m_wasTextureRecreated = false; + + const bool hasSharedTextureId = m_sharedTextureId > 0; + // Only load texture data if we are not using a sharedTextureId + // Check if dataFunctor or images have changed + if (!hasSharedTextureId) { + // If dataFunctor exists and we have no data and it hasnĀ“t run yet + if (m_dataFunctor && !m_textureData && m_dataFunctor.get() != m_pendingDataFunctor) { + const bool successfullyLoadedTextureData = loadTextureDataFromGenerator(); + // If successful, m_textureData has content + if (successfullyLoadedTextureData) { + setDirtyFlag(Properties, true); + setDirtyFlag(TextureData, true); + } else { + if (m_pendingDataFunctor != m_dataFunctor.get()) { + qWarning() << "[Qt3DRender::RHITexture] No QTextureData generated from Texture " + "Generator yet. Texture will be invalid for this frame"; + m_pendingDataFunctor = m_dataFunctor.get(); + } + textureInfo.properties.status = QAbstractTexture::Loading; + return textureInfo; + } + } + + // If images have changed, clear previous images data + // and regenerate m_imageData for the images + if (testDirtyFlag(TextureImageData)) { + m_imageData.clear(); + loadTextureDataFromImages(); + // Mark for upload if we actually have something to upload + if (!m_imageData.empty()) { + setDirtyFlag(TextureData, true); + } + // Reset image flag + setDirtyFlag(TextureImageData, false); + } + + // Don't try to create the texture if the target or format was still not set + // Format should either be set by user or if Automatic + // by either the dataGenerator of the texture or the first Image + // Target should explicitly be set by the user or the dataGenerator + if (m_properties.target == QAbstractTexture::TargetAutomatic + || m_properties.format == QAbstractTexture::Automatic + || m_properties.format == QAbstractTexture::NoFormat) { + textureInfo.properties.status = QAbstractTexture::Error; + return textureInfo; + } + } + + // If the properties changed or texture has become a shared texture from a + // 3rd party engine, we need to destroy and maybe re-allocate the texture + if (testDirtyFlag(Properties) || testDirtyFlag(SharedTextureId)) { + delete m_rhi; + m_rhi = nullptr; + textureInfo.wasUpdated = true; + // If we are destroyed because of some property change but still have (some) of + // our content data make sure we are marked for upload + // TO DO: We should actually check if the textureData is still correct + // in regard to the size, target and format of the texture though. + if (!testDirtyFlag(SharedTextureId) + && (m_textureData || !m_imageData.empty() || !m_pendingTextureDataUpdates.empty())) + setDirtyFlag(TextureData, true); + } + + m_properties.status = QAbstractTexture::Ready; + + if (testDirtyFlag(SharedTextureId) || hasSharedTextureId) { + // Update m_properties by doing introspection on the texture + if (hasSharedTextureId) + introspectPropertiesFromSharedTextureId(); + setDirtyFlag(SharedTextureId, false); + } else { + // We only build a QOpenGLTexture if we have no shared textureId set + if (!m_rhi) { + m_rhi = buildRhiTexture(ctx); + if (!m_rhi) { + qWarning() << "[Qt3DRender::RHITexture] failed to create texture"; + textureInfo.properties.status = QAbstractTexture::Error; + return textureInfo; + } + m_wasTextureRecreated = true; + } + + textureInfo.texture = m_rhi; + + // need to (re-)upload texture data? + const bool needsUpload = testDirtyFlag(TextureData); + if (needsUpload) { + uploadRhiTextureData(ctx); + setDirtyFlag(TextureData, false); + } + + // need to set texture parameters? + if (testDirtyFlag(Properties) || testDirtyFlag(Parameters)) { + updateRhiTextureParameters(ctx); + setDirtyFlag(Properties, false); + setDirtyFlag(Parameters, false); + } + } + + textureInfo.properties = m_properties; + + return textureInfo; +} + +RenderBuffer *RHITexture::getOrCreateRenderBuffer() +{ + if (m_dataFunctor && !m_textureData) { + m_textureData = m_dataFunctor->operator()(); + if (m_textureData) { + if (m_properties.target != QAbstractTexture::TargetAutomatic) + qWarning() << "[Qt3DRender::RHITexture] [renderbuffer] When a texture provides a " + "generator, it's target is expected to be TargetAutomatic"; + + m_properties.width = m_textureData->width(); + m_properties.height = m_textureData->height(); + m_properties.format = m_textureData->format(); + + setDirtyFlag(Properties); + } else { + if (m_pendingDataFunctor != m_dataFunctor.get()) { + qWarning() << "[Qt3DRender::RHITexture] [renderbuffer] No QTextureData generated " + "from Texture Generator yet. Texture will be invalid for this frame"; + m_pendingDataFunctor = m_dataFunctor.get(); + } + return nullptr; + } + } + + if (testDirtyFlag(Properties)) { + delete m_renderBuffer; + m_renderBuffer = nullptr; + } + + if (!m_renderBuffer) + m_renderBuffer = + new RenderBuffer(m_properties.width, m_properties.height, m_properties.format); + + setDirtyFlag(Properties, false); + setDirtyFlag(Parameters, false); + + return m_renderBuffer; +} + +// This must be called from the RenderThread +// So RHITexture release from the manager can only be done from that thread +void RHITexture::cleanup() +{ + destroy(); +} + +void RHITexture::setParameters(const TextureParameters ¶ms) +{ + if (m_parameters != params) { + m_parameters = params; + setDirtyFlag(Parameters); + } +} + +void RHITexture::setProperties(const TextureProperties &props) +{ + if (m_properties != props) { + m_properties = props; + setDirtyFlag(Properties); + } +} + +void RHITexture::setImages(const QVector<Image> &images) +{ + // check if something has changed at all + bool same = (images.size() == m_images.size()); + if (same) { + for (int i = 0; i < images.size(); i++) { + if (images[i] != m_images[i]) { + same = false; + break; + } + } + } + + if (!same) { + m_images = images; + requestImageUpload(); + } +} + +void RHITexture::setGenerator(const QTextureGeneratorPtr &generator) +{ + m_textureData.reset(); + m_dataFunctor = generator; + m_pendingDataFunctor = nullptr; + requestUpload(); +} + +void RHITexture::setSharedTextureId(int textureId) +{ + if (m_sharedTextureId != textureId) { + m_sharedTextureId = textureId; + setDirtyFlag(SharedTextureId); + } +} + +void RHITexture::addTextureDataUpdates(const QVector<QTextureDataUpdate> &updates) +{ + m_pendingTextureDataUpdates += updates; + requestUpload(); +} + +// Return nullptr if +// - context cannot be obtained +// - texture hasn't yet been loaded +QRhiTexture *RHITexture::buildRhiTexture(SubmissionContext *ctx) +{ + const QAbstractTexture::Target actualTarget = m_properties.target; + if (actualTarget == QAbstractTexture::TargetAutomatic) { + // If the target is automatic at this point, it means that the texture + // hasn't been loaded yet (case of remote urls) and that loading failed + // and that target format couldn't be deduced + return nullptr; + } + + const QRhiTexture::Format rhiFormat = rhiFormatFromTextureFormat(m_properties.format); + const QSize pixelSize(m_properties.width, m_properties.height); + QRhiTexture::Flags rhiFlags {}; + int sampleCount = 1; + + const bool issRGB8Format = issRGBFormat(m_properties.format); + if (issRGB8Format) + rhiFlags |= QRhiTexture::sRGB; + + if (actualTarget == QAbstractTexture::Target2DMultisample + || actualTarget == QAbstractTexture::Target2DMultisampleArray) { + // Set samples count if multisampled texture + // (multisampled textures don't have mipmaps) + sampleCount = m_properties.samples; + } + + switch (actualTarget) { + case QAbstractTexture::TargetCubeMap: + case QAbstractTexture::TargetCubeMapArray: { + rhiFlags |= QRhiTexture::CubeMap; + break; + } + default: { + // Mipmaps don't see to work with cubemaps at the moment + if (m_properties.generateMipMaps) { + rhiFlags |= QRhiTexture::UsedWithGenerateMips; + rhiFlags |= QRhiTexture::MipMapped; + } else if (m_properties.mipLevels > 1) { + rhiFlags |= QRhiTexture::MipMapped; + } + break; + } + } + + QRhiTexture *rhiTexture = ctx->rhi()->newTexture(rhiFormat, pixelSize, sampleCount, rhiFlags); + + if (!rhiTexture->build()) { + qWarning() << Q_FUNC_INFO << "creating QRhiTexture failed"; + delete rhiTexture; + return nullptr; + } + return rhiTexture; +} + +void RHITexture::uploadRhiTextureData(SubmissionContext *ctx) +{ + QVarLengthArray<QRhiTextureUploadEntry> uploadEntries; + + // Upload all QTexImageData set by the QTextureGenerator + if (m_textureData) { + const QVector<QTextureImageDataPtr> &imgData = m_textureData->imageData(); + + for (const QTextureImageDataPtr &data : imgData) { + const int mipLevels = data->mipLevels(); + Q_ASSERT(mipLevels <= ctx->rhi()->mipLevelsForSize({ data->width(), data->height() })); + + filterLayersAndFaces(*data, [&](QRhiTextureUploadEntry &&entry) { + uploadEntries.push_back(std::move(entry)); + }); + } + } + + // Upload all QTexImageData references by the TextureImages + for (int i = 0; i < std::min(m_images.size(), m_imageData.size()); i++) { + const QTextureImageDataPtr &imgData = m_imageData.at(i); + // Here the bytes in the QTextureImageData contain data for a single + // layer, face or mip level, unlike the QTextureGenerator case where + // they are in a single blob. Hence QTextureImageData::data() is not suitable. + const QByteArray bytes(QTextureImageDataPrivate::get(imgData.get())->m_data); + const int layer = m_images[i].layer; + const int face = m_images[i].face; + filterLayerAndFace(layer, face, [&](int rhiLayer) { + uploadEntries.push_back(createUploadEntry(m_images[i].mipLevel, rhiLayer, bytes)); + }); + } + + // Free up image data once content has been uploaded + // Note: if data functor stores the data, this won't really free anything though + m_imageData.clear(); + + // Update data from TextureUpdates + const QVector<QTextureDataUpdate> textureDataUpdates = std::move(m_pendingTextureDataUpdates); + for (const QTextureDataUpdate &update : textureDataUpdates) { + const QTextureImageDataPtr imgData = update.data(); + + if (!imgData) { + qWarning() << Q_FUNC_INFO << "QTextureDataUpdate no QTextureImageData set"; + continue; + } + + // TO DO: There's currently no way to check the depth of an existing QRhiTexture + const int xOffset = update.x(); + const int yOffset = update.y(); + const int xExtent = xOffset + imgData->width(); + const int yExtent = yOffset + imgData->height(); + + // Check update is compatible with our texture + if (xOffset >= m_rhi->pixelSize().width() || yOffset >= m_rhi->pixelSize().height() + || xExtent > m_rhi->pixelSize().width() || yExtent > m_rhi->pixelSize().height()) { + qWarning() << Q_FUNC_INFO << "QTextureDataUpdate incompatible with texture"; + continue; + } + + const QByteArray bytes = (QTextureImageDataPrivate::get(imgData.get())->m_data); + // Here the bytes in the QTextureImageData contain data for a single + // layer, face or mip level, unlike the QTextureGenerator case where + // they are in a single blob. Hence QTextureImageData::data() is not suitable. + + const int layer = update.layer(); + const int face = update.face(); + filterLayerAndFace(layer, face, [&](int rhiLayer) { + const QRhiTextureUploadEntry entry = createUploadEntry( + update.mipLevel(), rhiLayer, xOffset, yOffset, 0, bytes, imgData); + uploadEntries.push_back(entry); + }); + } + + QRhiTextureUploadDescription uploadDescription; + uploadDescription.setEntries(uploadEntries.begin(), uploadEntries.end()); + + ctx->m_currentUpdates->uploadTexture(m_rhi, uploadDescription); + if (m_properties.generateMipMaps) + ctx->m_currentUpdates->generateMips(m_rhi); +} + +void RHITexture::updateRhiTextureParameters(SubmissionContext *ctx) +{ + const QAbstractTexture::Target actualTarget = m_properties.target; + const bool isMultisampledTexture = + (actualTarget == QAbstractTexture::Target2DMultisample + || actualTarget == QAbstractTexture::Target2DMultisampleArray); + // Multisampled textures can only be accessed by texelFetch in shaders + // and don't support wrap modes and mig/mag filtes + if (isMultisampledTexture) + return; + + // TO DO: + if (m_rhiSampler) { + delete m_rhiSampler; + m_rhiSampler = nullptr; + } + + const QRhiSampler::Filter magFilter = + rhiFilterFromTextureFilter(m_parameters.magnificationFilter); + const QRhiSampler::Filter minFilter = + rhiFilterFromTextureFilter(m_parameters.minificationFilter); + const QRhiSampler::Filter mipMapFilter = + rhiMipMapFilterFromTextureFilter(m_parameters.magnificationFilter); + const auto wrapMode = rhiWrapModeFromTextureWrapMode( + m_parameters.wrapModeX, m_parameters.wrapModeY, m_parameters.wrapModeZ); + const QRhiSampler::CompareOp compareOp = + rhiCompareOpFromTextureCompareOp(m_parameters.comparisonFunction); + m_rhiSampler = ctx->rhi()->newSampler(magFilter, minFilter, mipMapFilter, std::get<0>(wrapMode), + std::get<1>(wrapMode), std::get<2>(wrapMode)); + + m_rhiSampler->setTextureCompareOp(compareOp); + + if (!m_rhiSampler->build()) { + qDebug("Could not build RHI texture sampler"); + } +} + +void RHITexture::introspectPropertiesFromSharedTextureId() +{ + // // We know that the context is active when this function is called + // QOpenGLContext *ctx = QOpenGLContext::currentContext(); + // if (!ctx) { + // qWarning() << Q_FUNC_INFO << "requires an OpenGL context"; + // return; + // } + // QOpenGLFunctions *gl = ctx->functions(); + + // // If the user has set the target format himself, we won't try to deduce it + // if (m_properties.target != QAbstractTexture::TargetAutomatic) + // return; + + // const QAbstractTexture::Target targets[] = { + // QAbstractTexture::Target2D, + // QAbstractTexture::TargetCubeMap, + //#ifndef QT_OPENGL_ES_2 + // QAbstractTexture::Target1D, + // QAbstractTexture::Target1DArray, + // QAbstractTexture::Target3D, + // QAbstractTexture::Target2DArray, + // QAbstractTexture::TargetCubeMapArray, + // QAbstractTexture::Target2DMultisample, + // QAbstractTexture::Target2DMultisampleArray, + // QAbstractTexture::TargetRectangle, + // QAbstractTexture::TargetBuffer, + //#endif + // }; + + //#ifndef QT_OPENGL_ES_2 + // // Try to find texture target with GL 4.5 functions + // const QPair<int, int> ctxGLVersion = ctx->format().version(); + // if (ctxGLVersion.first > 4 || (ctxGLVersion.first == 4 && ctxGLVersion.second >= 5)) { + // // Only for GL 4.5+ + //#ifdef GL_TEXTURE_TARGET + // QOpenGLFunctions_4_5_Core *gl5 = ctx->versionFunctions<QOpenGLFunctions_4_5_Core>(); + // if (gl5 != nullptr) + // gl5->glGetTextureParameteriv(m_sharedTextureId, GL_TEXTURE_TARGET, + // reinterpret_cast<int *>(&m_properties.target)); + //#endif + // } + //#endif + + // // If GL 4.5 function unavailable or not working, try a slower way + // if (m_properties.target == QAbstractTexture::TargetAutomatic) { + // // // OpenGL offers no proper way of querying for the target of a texture given its + // id gl->glActiveTexture(GL_TEXTURE0); + + // const GLenum targetBindings[] = { + // GL_TEXTURE_BINDING_2D, + // GL_TEXTURE_BINDING_CUBE_MAP, + //#ifndef QT_OPENGL_ES_2 + // GL_TEXTURE_BINDING_1D, + // GL_TEXTURE_BINDING_1D_ARRAY, + // GL_TEXTURE_BINDING_3D, + // GL_TEXTURE_BINDING_2D_ARRAY, + // GL_TEXTURE_BINDING_CUBE_MAP_ARRAY, + // GL_TEXTURE_BINDING_2D_MULTISAMPLE, + // GL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY, + // GL_TEXTURE_BINDING_RECTANGLE, + // GL_TEXTURE_BINDING_BUFFER + //#endif + // }; + + // Q_ASSERT(sizeof(targetBindings) / sizeof(targetBindings[0] == sizeof(targets) / + // sizeof(targets[0]))); + + // for (uint i = 0; i < sizeof(targetBindings) / sizeof(targetBindings[0]); ++i) { + // const int target = targets[i]; + // gl->glBindTexture(target, m_sharedTextureId); + // int boundId = 0; + // gl->glGetIntegerv(targetBindings[i], &boundId); + // gl->glBindTexture(target, 0); + // if (boundId == m_sharedTextureId) { + // m_properties.target = static_cast<QAbstractTexture::Target>(target); + // break; + // } + // } + // } + + // // Return early if we weren't able to find texture target + // if (std::find(std::begin(targets), std::end(targets), m_properties.target) == + // std::end(targets)) { + // qWarning() << "Unable to determine texture target for shared GL texture"; + // return; + // } + + // // Bind texture once we know its target + // gl->glBindTexture(m_properties.target, m_sharedTextureId); + + // // TO DO: Improve by using glGetTextureParameters when available which + // // support direct state access + //#ifndef GL_TEXTURE_MAX_LEVEL + //#define GL_TEXTURE_MAX_LEVEL 0x813D + //#endif + + //#ifndef GL_TEXTURE_WRAP_R + //#define GL_TEXTURE_WRAP_R 0x8072 + //#endif + + // gl->glGetTexParameteriv(int(m_properties.target), GL_TEXTURE_MAX_LEVEL, + // reinterpret_cast<int *>(&m_properties.mipLevels)); + // gl->glGetTexParameteriv(int(m_properties.target), GL_TEXTURE_MIN_FILTER, + // reinterpret_cast<int *>(&m_parameters.minificationFilter)); + // gl->glGetTexParameteriv(int(m_properties.target), GL_TEXTURE_MAG_FILTER, + // reinterpret_cast<int *>(&m_parameters.magnificationFilter)); + // gl->glGetTexParameteriv(int(m_properties.target), GL_TEXTURE_WRAP_R, reinterpret_cast<int + // *>(&m_parameters.wrapModeX)); gl->glGetTexParameteriv(int(m_properties.target), + // GL_TEXTURE_WRAP_S, reinterpret_cast<int *>(&m_parameters.wrapModeY)); + // gl->glGetTexParameteriv(int(m_properties.target), GL_TEXTURE_WRAP_T, reinterpret_cast<int + // *>(&m_parameters.wrapModeZ)); + + //#ifndef QT_OPENGL_ES_2 + // // Try to retrieve dimensions (not available on ES 2.0) + // if (!ctx->isOpenGLES()) { + // QOpenGLFunctions_3_1 *gl3 = ctx->versionFunctions<QOpenGLFunctions_3_1>(); + // if (!gl3) { + // qWarning() << "Failed to retrieve shared texture dimensions"; + // return; + // } + + // gl3->glGetTexLevelParameteriv(int(m_properties.target), 0, GL_TEXTURE_WIDTH, + // reinterpret_cast<int *>(&m_properties.width)); + // gl3->glGetTexLevelParameteriv(int(m_properties.target), 0, GL_TEXTURE_HEIGHT, + // reinterpret_cast<int *>(&m_properties.height)); + // gl3->glGetTexLevelParameteriv(int(m_properties.target), 0, GL_TEXTURE_DEPTH, + // reinterpret_cast<int *>(&m_properties.depth)); + // gl3->glGetTexLevelParameteriv(int(m_properties.target), 0, GL_TEXTURE_INTERNAL_FORMAT, + // reinterpret_cast<int *>(&m_properties.format)); + // } + //#endif + + // gl->glBindTexture(m_properties.target, 0); +} + +} // namespace Rhi +} // namespace Render +} // namespace Qt3DRender + +QT_END_NAMESPACE diff --git a/src/plugins/renderers/rhi/textures/texture_p.h b/src/plugins/renderers/rhi/textures/texture_p.h new file mode 100644 index 000000000..536cb962e --- /dev/null +++ b/src/plugins/renderers/rhi/textures/texture_p.h @@ -0,0 +1,249 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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_RHI_GLTEXTURE_H +#define QT3DRENDER_RENDER_RHI_GLTEXTURE_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/qtexture.h> +#include <Qt3DRender/qtextureimagedata.h> +#include <Qt3DRender/qtexturegenerator.h> +#include <Qt3DRender/private/backendnode_p.h> +#include <Qt3DRender/private/handle_types_p.h> +#include <Qt3DRender/private/texture_p.h> +#include <QFlags> +#include <QMutex> +#include <QSize> + +QT_BEGIN_NAMESPACE + +class QRhiTexture; +class QRhiSampler; + +namespace Qt3DRender { +namespace Render { + +template<class APITexture, class APITextureImage> +class APITextureManager; + +class TextureImageManager; +class TextureDataManager; +class TextureImageDataManager; + +namespace Rhi { +class RenderBuffer; +class SubmissionContext; + +/** + * @brief + * Actual implementation of the OpenGL texture object. Makes sure the + * QOpenGLTexture is up-to-date with the generators, properties and parameters + * that were set for this RHITexture. + * + * Can be shared among multiple QTexture backend nodes through the + * RHITextureManager, which will make sure that there are no two GLTextures + * sharing the same texture data. + * + * A RHITexture can be unique though. In that case, it will not be shared + * between QTextures, but private to one QTexture only. + * + * A RHITexture can also represent an OpenGL renderbuffer object. This is used + * only in certain special cases, mainly to provide a packed depth-stencil + * renderbuffer suitable as an FBO attachment with OpenGL ES 3.1 and earlier. + * Such a RHITexture will have no texture object under the hood, and therefore + * the only valid operation is getOrCreateRenderBuffer(). + */ +class Q_AUTOTEST_EXPORT RHITexture +{ +public: + RHITexture(); + ~RHITexture(); + + enum DirtyFlag { + None = 0, + TextureData = (1 << 0), // texture data needs uploading to GPU + Properties = (1 << 1), // texture needs to be (re-)created + Parameters = (1 << 2), // texture parameters need to be (re-)set + SharedTextureId = (1 << 3), // texture id from shared context + TextureImageData = (1 << 4) // texture image data needs uploading + }; + + /** + * Helper class to hold the defining properties of TextureImages + */ + struct Image + { + QTextureImageDataGeneratorPtr generator; + int layer; + int mipLevel; + QAbstractTexture::CubeMapFace face; + + inline bool operator==(const Image &o) const + { + bool sameGenerators = (generator == o.generator) + || (!generator.isNull() && !o.generator.isNull() && *generator == *o.generator); + return sameGenerators && layer == o.layer && mipLevel == o.mipLevel && face == o.face; + } + inline bool operator!=(const Image &o) const { return !(*this == o); } + }; + + inline TextureProperties properties() const { return m_properties; } + inline TextureParameters parameters() const { return m_parameters; } + inline QTextureGeneratorPtr textureGenerator() const { return m_dataFunctor; } + inline int sharedTextureId() const { return m_sharedTextureId; } + inline QVector<Image> images() const { return m_images; } + + inline QSize size() const { return QSize(m_properties.width, m_properties.height); } + inline QRhiTexture *getRhiTexture() const { return m_rhi; } + inline QRhiSampler *getRhiSampler() const { return m_rhiSampler; } + + /** + * @brief + * Returns the QOpenGLTexture for this RHITexture. If necessary, + * the GL texture will be created from the TextureImageDatas associated + * with the texture and image functors. If no functors are provided, + * the texture will be created without images. + * + * If the texture properties or parameters have changed, these changes + * will be applied to the resulting OpenGL texture. + */ + struct TextureUpdateInfo + { + QRhiTexture *texture = nullptr; + bool wasUpdated = false; + TextureProperties properties; + }; + + TextureUpdateInfo createOrUpdateRhiTexture(SubmissionContext *ctx); + + /** + * @brief + * Returns the RenderBuffer for this RHITexture. If this is the first + * call, the OpenGL renderbuffer object will be created. + */ + RenderBuffer *getOrCreateRenderBuffer(); + + void destroy(); + + void cleanup(); + + bool isDirty() const { return m_dirtyFlags != None; } + + bool hasTextureData() const { return !m_textureData.isNull(); } + bool hasImagesData() const { return !m_imageData.isEmpty(); } + + QFlags<DirtyFlag> dirtyFlags() const { return m_dirtyFlags; } + + QMutex *externalRenderingLock() { return &m_externalRenderingMutex; } + + void setExternalRenderingEnabled(bool enable) { m_externalRendering = enable; } + + bool isExternalRenderingEnabled() const { return m_externalRendering; } + + // Purely for unit testing purposes + bool wasTextureRecreated() const { return m_wasTextureRecreated; } + + void setParameters(const TextureParameters ¶ms); + void setProperties(const TextureProperties &props); + void setImages(const QVector<Image> &images); + void setGenerator(const QTextureGeneratorPtr &generator); + void setSharedTextureId(int textureId); + void addTextureDataUpdates(const QVector<QTextureDataUpdate> &updates); + + QVector<QTextureDataUpdate> textureDataUpdates() const { return m_pendingTextureDataUpdates; } + QTextureGeneratorPtr dataGenerator() const { return m_dataFunctor; } + +private: + void requestImageUpload() { m_dirtyFlags |= TextureImageData; } + + void requestUpload() { m_dirtyFlags |= TextureData; } + + bool testDirtyFlag(DirtyFlag flag) { return m_dirtyFlags.testFlag(flag); } + + void setDirtyFlag(DirtyFlag flag, bool value = true) { m_dirtyFlags.setFlag(flag, value); } + + QRhiTexture *buildRhiTexture(SubmissionContext *ctx); + bool loadTextureDataFromGenerator(); + void loadTextureDataFromImages(); + void uploadRhiTextureData(SubmissionContext *ctx); + void updateRhiTextureParameters(SubmissionContext *ctx); + void introspectPropertiesFromSharedTextureId(); + void destroyResources(); + + QFlags<DirtyFlag> m_dirtyFlags; + QMutex m_externalRenderingMutex; + QRhiTexture *m_rhi; + QRhiSampler *m_rhiSampler; + RenderBuffer *m_renderBuffer; + + // target which is actually used for GL texture + TextureProperties m_properties; + TextureParameters m_parameters; + + QTextureGeneratorPtr m_dataFunctor; + QTextureGenerator *m_pendingDataFunctor; + QVector<Image> m_images; + + // cache actual image data generated by the functors + QTextureDataPtr m_textureData; + QVector<QTextureImageDataPtr> m_imageData; + QVector<QTextureDataUpdate> m_pendingTextureDataUpdates; + + int m_sharedTextureId; + bool m_externalRendering; + bool m_wasTextureRecreated; +}; + +} // namespace Rhi +} // namespace Render +} // namespace Qt3DRender + +QT_END_NAMESPACE + +#endif // QT3DRENDER_RENDER_RHI_GLTEXTURE_H diff --git a/src/plugins/renderers/rhi/textures/textures.pri b/src/plugins/renderers/rhi/textures/textures.pri new file mode 100644 index 000000000..dd9e6404f --- /dev/null +++ b/src/plugins/renderers/rhi/textures/textures.pri @@ -0,0 +1,9 @@ +INCLUDEPATH += $$PWD + +SOURCES += \ + $$PWD/renderbuffer.cpp \ + $$PWD/texture.cpp + +HEADERS += \ + $$PWD/renderbuffer_p.h \ + $$PWD/texture_p.h |