diff options
author | Jean-Michaƫl Celerier <jean-michael.celerier@kdab.com> | 2019-12-26 17:55:20 +0100 |
---|---|---|
committer | Mike Krus <mike.krus@kdab.com> | 2020-03-16 15:38:59 +0000 |
commit | 2f3145d14cf9c13eca96776a05754699e1151678 (patch) | |
tree | 831b3ac7a0b9a71e217919bc98cb60782e0b4613 | |
parent | 8b122b9b53687285953140b2130be6541a938737 (diff) |
Initial creation of RHI plug-in and refactoring
Change-Id: Ifbb51003e15f798798040597b5f7224641e8147c
Reviewed-by: Paul Lemire <paul.lemire@kdab.com>
74 files changed, 14340 insertions, 30 deletions
diff --git a/src/core/qpostman_p.h b/src/core/qpostman_p.h index 3e5f91742..1e31940b8 100644 --- a/src/core/qpostman_p.h +++ b/src/core/qpostman_p.h @@ -69,7 +69,7 @@ public: virtual bool shouldNotifyFrontend(const QSceneChangePtr &change) = 0; }; -class Q_AUTOTEST_EXPORT QPostman final +class Q_3DCORE_PRIVATE_EXPORT QPostman final : public QObject , public QAbstractPostman { 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/renderview.cpp b/src/plugins/renderers/opengl/renderer/renderview.cpp index 288673a9b..8167e2d5d 100644 --- a/src/plugins/renderers/opengl/renderer/renderview.cpp +++ b/src/plugins/renderers/opengl/renderer/renderview.cpp @@ -75,6 +75,7 @@ #include <Qt3DCore/qentity.h> #include <QtGui/qsurface.h> #include <algorithm> +#include <atomic> #include <QDebug> #if defined(QT3D_RENDER_VIEW_JOB_TIMINGS) @@ -113,7 +114,7 @@ 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 @@ -263,10 +264,10 @@ 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) { diff --git a/src/plugins/renderers/opengl/renderer/renderviewbuilder_p.h b/src/plugins/renderers/opengl/renderer/renderviewbuilder_p.h index 54fc98352..1a94cbf2b 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 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..1f243e37b --- /dev/null +++ b/src/plugins/renderers/rhi/graphicshelpers/submissioncontext.cpp @@ -0,0 +1,1720 @@ +/**************************************************************************** +** +** 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 <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> +#include <QVulkanInstance> +#endif + +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) +#if QT_CONFIG(vulkan) + , m_vkInstance(nullptr) +#endif +#ifndef QT_NO_OPENGL + , m_fallbackSurface(nullptr) +#endif +{ + static_contexts[m_id] = this; + m_contextInfo.m_api = QGraphicsApiFilter::RHI; +} + +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) { + if (!m_vkInstance) { + m_vkInstance = new QVulkanInstance(); + m_ownsVkInstance = true; + +#ifndef Q_OS_ANDROID + m_vkInstance->setLayers({ "VK_LAYER_LUNARG_standard_validation" }); +#else + m_vkInstance->setLayers(QByteArrayList() + << "VK_LAYER_GOOGLE_threading" + << "VK_LAYER_LUNARG_parameter_validation" + << "VK_LAYER_LUNARG_object_tracker" + << "VK_LAYER_LUNARG_core_validation" + << "VK_LAYER_LUNARG_image" + << "VK_LAYER_LUNARG_swapchain" + << "VK_LAYER_GOOGLE_unique_objects"); +#endif + + if (!m_vkInstance->create()) { + qWarning("Failed to create Vulkan instance"); + } + } + + Q_ASSERT(m_vkInstance); + if (m_vkInstance->isValid()) { + QRhiVulkanInitParams params; + params.inst = m_vkInstance; + 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 = m_swapChains[surface]; + QRhiSwapChain *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 + swapChain->buildOrResize(); + + swapChainInfo.swapChain = swapChain; + swapChainInfo.renderBuffer = renderBuffer; + swapChainInfo.renderPassDescriptor = renderPassDescriptor; + } + + // TO DO: Check if that's required all the time + { + // Rebuild RenderPassDescriptor + swapChainInfo.renderBuffer->setPixelSize(surface->size()); + swapChainInfo.renderBuffer->build(); + + // Resize swapchain if needed + if (m_surface->size() != swapChain->surfacePixelSize()) + swapChain->buildOrResize(); + } + + m_currentSwapChain = swapChain; + m_currentRenderPassDescriptor = swapChainInfo.renderPassDescriptor; + + // Begin Frame + m_rhi->beginFrame(m_currentSwapChain); + + // Update Resource Update Batch + m_currentUpdates = m_rhi->nextResourceUpdateBatch(); + + return true; +} + +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; + +#if QT_CONFIG(vulkan) + if (m_ownsVkInstance) { + delete m_vkInstance; + m_vkInstance = nullptr; + } +#endif +#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; +} + +QRhiCommandBuffer *SubmissionContext::currentFrameCommandBuffer() const +{ + return m_currentSwapChain->currentFrameCommandBuffer(); +} + +QRhiRenderTarget *SubmissionContext::currentFrameRenderTarget() const +{ + return m_currentSwapChain->currentFrameRenderTarget(); +} + +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); +//* } +} + +// Called by GL Command Thread +SubmissionContext::ShaderCreationInfo SubmissionContext::createShaderProgram(RHIShader *shader) +{ +#ifndef QT_NO_OPENGL + const QSurfaceFormat& format = this->format(); + + const QHash<std::pair<int, int>, int> glVersionToGLSLVersion = { + { {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}, 140 }, + { {3, 1}, 130 }, + }; + const int targetGLSLVersion = glVersionToGLSLVersion.value({format.majorVersion(), + format.minorVersion()}, + 120); +#endif + // Compile shaders + const auto& shaderCode = shader->shaderCode(); + QShaderBaker b; + b.setGeneratedShaders({ + {QShader::SpirvShader, 100}, +#ifndef QT_NO_OPENGL + {QShader::GlslShader, targetGLSLVersion}, // Only GLSL version supported by RHI right now. +#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(); + logs += b.errorMessage(); + success = false; + } + shader->m_stages[rhiStage] = std::move(bakedShader); + } + } + + // Perform shader introspection + 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 + rhiShader->setShaderCode(shaderNode->shaderCode()); + 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..dba99e505 --- /dev/null +++ b/src/plugins/renderers/rhi/graphicshelpers/submissioncontext_p.h @@ -0,0 +1,280 @@ +/**************************************************************************** +** +** 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 + +#if QT_CONFIG(vulkan) +class QVulkanInstance; +#endif + +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; + + + QRhiResourceUpdateBatch *m_currentUpdates{}; + + QRhi *rhi() const { return m_rhi; } + QRhiCommandBuffer *currentFrameCommandBuffer() const; + QRhiRenderTarget *currentFrameRenderTarget() const; + QRhiRenderPassDescriptor *currentRenderPassDescriptor() 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; + + struct SwapChainInfo + { + QRhiSwapChain *swapChain = nullptr; + QRhiRenderBuffer *renderBuffer = nullptr; + QRhiRenderPassDescriptor *renderPassDescriptor = nullptr; + }; + + QHash<QSurface *, SwapChainInfo> m_swapChains; + QRhiSwapChain *m_currentSwapChain; + QRhiRenderPassDescriptor *m_currentRenderPassDescriptor; + +#if QT_CONFIG(vulkan) + QVulkanInstance *m_vkInstance; + bool m_ownsVkInstance{}; +#endif +#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..a10cbab03 --- /dev/null +++ b/src/plugins/renderers/rhi/io/rhibuffer.cpp @@ -0,0 +1,205 @@ +/**************************************************************************** +** +** 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..69f05b447 --- /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..649b67eb3 --- /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..909100075 --- /dev/null +++ b/src/plugins/renderers/rhi/jobs/materialparametergathererjob.cpp @@ -0,0 +1,135 @@ +/**************************************************************************** +** +** 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..177f5a0c5 --- /dev/null +++ b/src/plugins/renderers/rhi/jobs/materialparametergathererjob_p.h @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** 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..ee063bf77 --- /dev/null +++ b/src/plugins/renderers/rhi/jobs/renderviewcommandbuilderjob.cpp @@ -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$ +** +****************************************************************************/ + +#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..d8fcc3aaf 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,22 @@ 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) + , 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 +75,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..614e02f34 --- /dev/null +++ b/src/plugins/renderers/rhi/jobs/renderviewcommandupdaterjob_p.h @@ -0,0 +1,108 @@ +ļ»æ/**************************************************************************** +** +** 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..3af0d89cc --- /dev/null +++ b/src/plugins/renderers/rhi/jobs/renderviewinitializerjob.cpp @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** 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..d28b10a95 --- /dev/null +++ b/src/plugins/renderers/rhi/jobs/renderviewinitializerjob_p.h @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** 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..730a0060a --- /dev/null +++ b/src/plugins/renderers/rhi/jobs/renderviewjobutils.cpp @@ -0,0 +1,569 @@ +/**************************************************************************** +** +** 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(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(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..2fb441ef9 --- /dev/null +++ b/src/plugins/renderers/rhi/jobs/renderviewjobutils_p.h @@ -0,0 +1,191 @@ +/**************************************************************************** +** +** 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(ShaderData *currentShaderData, + const QString &blockName, + const QString &qmlPropertyName, + const QVariant &value); + void buildActiveUniformNameValueMapStructHelper(ShaderData *rShaderData, + const QString &blockName, + const QString &qmlPropertyName = QString()); + + bool updatedPropertiesOnly; + QHash<QString, ShaderUniform> 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..b8d3e4890 --- /dev/null +++ b/src/plugins/renderers/rhi/main.cpp @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** 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..946d94a38 --- /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..32d1318e4 --- /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..2a5040ef5 --- /dev/null +++ b/src/plugins/renderers/rhi/managers/rhiresourcemanagers_p.h @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** 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..d4f1a7482 --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/commandexecuter.cpp @@ -0,0 +1,394 @@ +/**************************************************************************** +** +** 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..5d311480a --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/rendercommand.cpp @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** 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" + +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 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..e848ae593 --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/rendercommand_p.h @@ -0,0 +1,209 @@ +/**************************************************************************** +** +** 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; + +class QOpenGLVertexArrayObject; + +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 inverseModelViewMatrix[16]; + float mvp[16]; + float inverseModelViewProjectionMatrix[16]; +}; +static_assert(sizeof(CommandUBO) == 6 * (16 * sizeof(float)), "UBO doesn't match std140"); + +class Q_AUTOTEST_EXPORT RenderCommand +{ +public: + RenderCommand(); + + 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..a17d9b115 --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/renderer.cpp @@ -0,0 +1,2398 @@ +/**************************************************************************** +** +** 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> +#include <QShaderBaker> + +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 + } +} + +QShader getShader(const QString &name) +{ + QFile f(name); + if (f.open(QIODevice::ReadOnly)) + return QShader::fromSerialized(f.readAll()); + + return QShader(); +} + +// 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; + + // 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()) { + { // Scoped to destroy surfaceLock + QSurface *surface = nullptr; + for (const RenderView *rv: renderViews) { + surface = rv->surface(); + if (surface) + break; + } + + SurfaceLocker surfaceLock(surface); + const bool surfaceIsValid = (surface && surfaceLock.isSurfaceValid()); + if (surfaceIsValid) { + beganDrawing = m_submissionContext->beginDrawing(surface); + if (beganDrawing) { + // 1) Execute commands for buffer uploads, texture updates, shader loading first + updateResources(); + // 2) Update Pipelines and copy data into commands to allow concurrent submission + rhiPassesInfo = prepareCommandsSubmission(renderViews); + preprocessingComplete = true; + + // Purge shader which aren't used any longer + static int callCount = 0; + ++callCount; + const int shaderPurgePeriod = 600; + if (callCount % shaderPurgePeriod == 0) + m_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 (preprocessingComplete) { + submissionStatsPart1.end(submissionStatsPart2.restart()); + + // 3) Submit the render commands for frame n (making sure we never reference something that could be changing) + // Render using current device state and renderer configuration + submissionData = submitRenderViews(rhiPassesInfo); + + // Perform any required cleanup of the Graphics resources (Buffers deleted, Shader deleted...) + cleanGraphicsResources(); + } + } + + // 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); + } +} + +// 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) + 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 + const GraphicsPipelineIdentifier pipelineKey { cmd.m_geometry, cmd.m_shaderId, renderViewIndex }; + RHIGraphicsPipeline *graphicsPipeline = m_RHIResourceManagers->rhiGraphicsPipelineManager()->getOrCreateResource(pipelineKey); + // TO DO: Ensure we find a way to know when the state is dirty to trigger a rebuild + + // 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; + + // 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(m_submissionContext->currentRenderPassDescriptor()); + + graphicsPipeline->setAttributesToBindingHash(attributeNameToBinding); + + + // Render States + m_submissionContext->applyStateSet(renderState, pipeline); + + ok = pipeline->build(); + assert(ok); + } + + // Record RHIGraphicsPipeline into command for later use + 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) + 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) { + RHI_UNIMPLEMENTED; + 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()) { + qDebug() << pack.m_bufferID; + Buffer *cpuBuffer = nodeManagers()->bufferManager()->lookupResource(pack.m_bufferID); + RHIBuffer *ubo = m_submissionContext->rhiBufferForRenderBuffer(cpuBuffer); + ubo->bind(&*m_submissionContext, RHIBuffer::UniformBuffer); + } + + return true; +} + +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) { + const QVector<QShaderDescription::BlockVariable> members = uboMember.members; + for (const QShaderDescription::BlockVariable &member : members) { + const int uniformNameId = StringToInt::lookupId(member.name); + + + // Check if key is found in one of the custom UBOs + if (uniforms.contains(uniformNameId)) { + 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(m_submissionContext.data(), rawData, member.offset); + qDebug() << "Updating" << member.name << "with" << rawData; + } + } + } + // 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 (const HRHIGraphicsPipeline &pipelineHandle : graphicsPipelinesHandles) { + RHIGraphicsPipeline *pipeline = pipelineManager->data(pipelineHandle); + pipeline->decreaseScore(); + // Pipeline wasn't used recently, let's destroy it + if (pipeline->score() < 0) + pipelineManager->release(pipelineHandle); + } + + // 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..a337f267b --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/renderer_p.h @@ -0,0 +1,433 @@ +/**************************************************************************** +** +** 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..f2ca76148 --- /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..ae4145c76 --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/renderqueue.cpp @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** 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..1898a0346 --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/renderview.cpp @@ -0,0 +1,1193 @@ +/**************************************************************************** +** +** 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]; + +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]; + +std::atomic_bool wasInitialized{}; + +} // anonymous namespace + +RenderView::StandardUniformsNameToTypeHash RenderView::ms_standardUniformSetters; + + +RenderView::StandardUniformsNameToTypeHash RenderView::initializeStandardUniformSetters() +{ + RenderView::StandardUniformsNameToTypeHash setters; + + setters.insert(Shader::modelMatrixNameId, ModelMatrix); + setters.insert(Shader::viewMatrixNameId, ViewMatrix); + setters.insert(Shader::projectionMatrixNameId, ProjectionMatrix); + setters.insert(Shader::modelViewMatrixNameId, ModelViewMatrix); + setters.insert(Shader::viewProjectionMatrixNameId, ViewProjectionMatrix); + setters.insert(Shader::modelViewProjectionNameId, ModelViewProjectionMatrix); + setters.insert(Shader::mvpNameId, ModelViewProjectionMatrix); + setters.insert(Shader::inverseModelMatrixNameId, InverseModelMatrix); + setters.insert(Shader::inverseViewMatrixNameId, InverseViewMatrix); + setters.insert(Shader::inverseProjectionMatrixNameId, InverseProjectionMatrix); + setters.insert(Shader::inverseModelViewNameId, InverseModelViewMatrix); + setters.insert(Shader::inverseViewProjectionMatrixNameId, InverseViewProjectionMatrix); + setters.insert(Shader::inverseModelViewProjectionNameId, InverseModelViewProjectionMatrix); + setters.insert(Shader::modelNormalMatrixNameId, ModelNormalMatrix); + setters.insert(Shader::modelViewNormalNameId, ModelViewNormalMatrix); + setters.insert(Shader::viewportMatrixNameId, ViewportMatrix); + setters.insert(Shader::inverseViewportMatrixNameId, InverseViewportMatrix); + setters.insert(Shader::textureTransformMatrixNameId, TextureTransformMatrix); + setters.insert(Shader::aspectRatioNameId, AspectRatio); + setters.insert(Shader::exposureNameId, Exposure); + setters.insert(Shader::gammaNameId, Gamma); + setters.insert(Shader::timeNameId, Time); + setters.insert(Shader::eyePositionNameId, EyePosition); + setters.insert(Shader::skinningPaletteNameId, SkinningPalette); + + return setters; +} + +// TODO: Move this somewhere global where GraphicsContext::setViewport() can use it too +static QRectF resolveViewport(const QRectF &fractionalViewport, const QSize &surfaceSize) +{ + return QRectF(fractionalViewport.x() * surfaceSize.width(), + (1.0 - fractionalViewport.y() - fractionalViewport.height()) * surfaceSize.height(), + fractionalViewport.width() * surfaceSize.width(), + fractionalViewport.height() * surfaceSize.height()); +} + +static Matrix4x4 getProjectionMatrix(const CameraLens *lens) +{ + if (!lens) + qWarning() << "[Qt3D Renderer] No Camera Lens found. Add a CameraSelector to your Frame Graph or make sure that no entities will be rendered."; + return lens ? lens->projection() : Matrix4x4(); +} + +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); + + 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); + } + } +} + +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) + { + 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; + }); + } +}; + +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); + + // 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 Matrix4x4 projectionMatrix = getProjectionMatrix(m_data.m_renderCameraLens); + const Matrix4x4 inverseViewMatrix = m_data.m_viewMatrix.inverted(); + const Matrix4x4 inversedProjectionMatrix = getProjectionMatrix(m_data.m_renderCameraLens).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 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)); + 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, + 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, + 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, RHIShader *shader, ShaderData *shaderData, const QString &structName) const +{ + UniformBlockValueBuilder *builder = m_localData.localData(); + builder->activeUniformNamesToValue.clear(); + + // Set the view matrix to be used to transform "Transformed" properties in the ShaderData + builder->viewMatrix = m_data.m_viewMatrix; + // Force to update the whole block + builder->updatedPropertiesOnly = false; + // Retrieve names and description of each active uniforms in the uniform block + builder->uniforms = shader->activeUniformsForUniformBlock(-1); + // Build name-value map for the block + builder->buildActiveUniformNameValueMapStructHelper(shaderData, structName); + // Set uniform values for each entrie of the block name-value map + QHash<int, QVariant>::const_iterator activeValuesIt = builder->activeUniformNamesToValue.constBegin(); + const QHash<int, QVariant>::const_iterator activeValuesEnd = builder->activeUniformNamesToValue.constEnd(); + + // TO DO: Make the ShaderData store UniformValue + while (activeValuesIt != activeValuesEnd) { + setUniformValue(uniformPack, activeValuesIt.key(), UniformValue::fromVariant(activeValuesIt.value())); + ++activeValuesIt; + } +} + +void RenderView::setShaderAndUniforms(RenderCommand *command, + 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 + const QVector<int> uniformNamesIds = shader->uniformsNamesIds(); + const QVector<int> uniformBlockNamesIds = shader->uniformBlockNamesIds(); + const QVector<int> shaderStorageBlockNamesIds = shader->storageBlockNamesIds(); + const QVector<int> attributeNamesIds = shader->attributeNamesIds(); + + // Set fragData Name and index + // Later on we might want to relink the shader if attachments have changed + // But for now we set them once and for all + 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 (!uniformNamesIds.isEmpty() || + !attributeNamesIds.isEmpty() || + !shaderStorageBlockNamesIds.isEmpty() || !attributeNamesIds.isEmpty()) { + + // Set default attributes + command->m_activeAttributes = 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) { + Parameter *param = m_manager->data<Parameter, ParameterManager>(it->handle); + const UniformValue &uniformValue = param->uniformValue(); + 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 + setUniformValue(command->m_parameterPack, it->nameId, uniformValue); +// ShaderData *shaderData = nullptr; +// if (uniformValue.valueType() == UniformValue::NodeId && +// (shaderData = m_manager->shaderDataManager()->lookupResource(*uniformValue.constData<Qt3DCore::QNodeId>())) != nullptr) { +// // Try to check if we have a struct or array matching a QShaderData parameter +// setDefaultUniformBlockShaderDataValue(command->m_parameterPack, shader, shaderData, StringToInt::lookupString(it->nameId)); +// } + // Otherwise: param unused by current shader + } + ++it; + } + + // Lights + + int lightIdx = 0; + for (const LightSource &lightSource : activeLightSources) { + if (lightIdx == MAX_LIGHTS) + break; + Entity *lightEntity = lightSource.entity; + const 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; + } + } + + 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); + } + + // Environment Light + int envLightCount = 0; + if (environmentLight && environmentLight->isEnabled()) { + ShaderData *shaderData = m_manager->shaderDataManager()->lookupResource(environmentLight->shaderData()); + if (shaderData) { + setDefaultUniformBlockShaderDataValue(command->m_parameterPack, shader, shaderData, QStringLiteral("envLight")); + envLightCount = 1; + } + } else { + // with some drivers, samplers (like the envbox sampler) need to be bound even though + // they may not be actually used, otherwise draw calls can fail + static const int irradianceId = StringToInt::lookupId(QLatin1String("envLight.irradiance")); + static const int specularId = StringToInt::lookupId(QLatin1String("envLight.specular")); +// setUniformValue(command->m_parameterPack, irradianceId, m_renderer->submissionContext()->maxTextureUnitsCount()); +// setUniformValue(command->m_parameterPack, specularId, m_renderer->submissionContext()->maxTextureUnitsCount()); + } + 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..c770eaaec --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/renderview_p.h @@ -0,0 +1,405 @@ +/**************************************************************************** +** +** 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 pad1; + float aspectRatio; float pad2[3]; + float gamma; float pad3[3]; + float exposure; float pad4[3]; + float time; float pad5[3]; +}; +static_assert(sizeof(RenderViewUBO) == sizeof(float) * (8 * 16 + 6 * 4), "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; + + enum StandardUniform + { + ModelMatrix, + ViewMatrix, + ProjectionMatrix, + ModelViewMatrix, + ViewProjectionMatrix, + ModelViewProjectionMatrix, + InverseModelMatrix, + InverseViewMatrix, + InverseProjectionMatrix, + InverseModelViewMatrix, + InverseViewProjectionMatrix, + InverseModelViewProjectionMatrix, + ModelNormalMatrix, + ModelViewNormalMatrix, + ViewportMatrix, + InverseViewportMatrix, + TextureTransformMatrix, + AspectRatio, + Time, + Exposure, + Gamma, + EyePosition, + SkinningPalette + }; + + typedef QHash<int, StandardUniform> StandardUniformsNameToTypeHash; + static StandardUniformsNameToTypeHash ms_standardUniformSetters; + static StandardUniformsNameToTypeHash initializeStandardUniformSetters(); + + UniformValue standardUniformValue(StandardUniform standardUniformType, + Entity *entity, + const Matrix4x4 &model) const; + + void setUniformValue(ShaderParameterPack &uniformPack, int nameId, const UniformValue &value) const; + void setUniformBlockValue(ShaderParameterPack &uniformPack, + RHIShader *shader, + const ShaderUniformBlock &block, + const UniformValue &value) const; + void setShaderStorageValue(ShaderParameterPack &uniformPack, + RHIShader *shader, + const ShaderStorageBlock &block, + const UniformValue &value) const; + void setDefaultUniformBlockShaderDataValue(ShaderParameterPack &uniformPack, + RHIShader *shader, + ShaderData *shaderData, + const QString &structName) const; +}; + +} // 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..f3d470765 --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/renderviewbuilder.cpp @@ -0,0 +1,812 @@ +/**************************************************************************** +** +** 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..6ca904a24 --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/renderviewbuilder_p.h @@ -0,0 +1,151 @@ +/**************************************************************************** +** +** 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..62da8aa12 --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/rhigraphicspipeline.cpp @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** 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_rvUbo; + delete m_cmdUbo; + delete m_pipeline; + m_rvUbo = nullptr; + m_cmdUbo = nullptr; + m_pipeline = nullptr; +} + +} // 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..61f0fd2a8 --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/rhigraphicspipeline_p.h @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** 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..fe8fb6961 --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/rhishader.cpp @@ -0,0 +1,519 @@ +/**************************************************************************** +** +** 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> + +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; +} + +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; +} +void RHIShader::introspect() +{ + 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}); + + // 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) { + qDebug() << member.name << member.offset << member.size << member.structMembers.size() << member.type; + namesIds << StringToInt::lookupId(member.name); + } + 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) +{ + 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) +{ + 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) +{ + 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) +{ + 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) +{ + 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) +{ + for (int i = 0, m = m_shaderStorageBlockNames.size(); i < m; ++i) { + if (m_shaderStorageBlocks[i].m_name == blockName) + return m_shaderStorageBlocks[i]; + } + return ShaderStorageBlock(); +} + +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::initializeUniforms(const QVector<ShaderUniform> &uniformsDescription) +{ + m_uniforms = uniformsDescription; + m_uniformsNames.resize(uniformsDescription.size()); + m_uniformsNamesIds.reserve(uniformsDescription.size()); + m_standardUniformNamesIds.reserve(5); + QHash<QString, ShaderUniform> activeUniformsInDefaultBlock; + + static const QVector<int> standardUniformNameIds = { + Shader::modelMatrixNameId, + Shader::viewMatrixNameId, + Shader::projectionMatrixNameId, + Shader::modelViewMatrixNameId, + Shader::viewProjectionMatrixNameId, + Shader::modelViewProjectionNameId, + Shader::mvpNameId, + Shader::inverseModelMatrixNameId, + Shader::inverseViewMatrixNameId, + Shader::inverseProjectionMatrixNameId, + Shader::inverseModelViewNameId, + Shader::inverseViewProjectionMatrixNameId, + Shader::inverseModelViewProjectionNameId, + Shader::modelNormalMatrixNameId, + Shader::modelViewNormalNameId, + Shader::viewportMatrixNameId, + Shader::inverseViewportMatrixNameId, + Shader::textureTransformMatrixNameId, + Shader::aspectRatioNameId, + Shader::exposureNameId, + Shader::gammaNameId, + Shader::timeNameId, + Shader::eyePositionNameId, + Shader::skinningPaletteNameId, + }; + + 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? + if (standardUniformNameIds.contains(nameId)) + m_standardUniformNamesIds.push_back(nameId); + else + m_uniformsNamesIds.push_back(nameId); + + if (uniformsDescription[i].m_blockIndex == -1) { // Uniform is in default block + qCDebug(Shaders) << "Active Uniform in Default Block " << uniformsDescription[i].m_name << uniformsDescription[i].m_blockIndex; + activeUniformsInDefaultBlock.insert(uniformsDescription[i].m_name, uniformsDescription[i]); + } + } + m_uniformBlockIndexToShaderUniforms.insert(-1, activeUniformsInDefaultBlock); +} + +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..8d23a6f28 --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/rhishader_p.h @@ -0,0 +1,184 @@ +/**************************************************************************** +** +** 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); + ShaderUniformBlock uniformBlockForBlockNameId(int blockIndex); + ShaderUniformBlock uniformBlockForBlockName(const QString &blockName); + + ShaderStorageBlock storageBlockForBlockIndex(int blockIndex); + ShaderStorageBlock storageBlockForBlockNameId(int blockNameId); + ShaderStorageBlock storageBlockForBlockName(const QString &blockName); + + 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; } + + 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; + + 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; + + QHash<QString, int> m_fragOutputs; + QVector<QByteArray> m_shaderCode; + + // Private so that only SubmissionContext can call it + friend class SubmissionContext; + void initializeUniforms(const QVector<ShaderUniform> &uniformsDescription); + void initializeAttributes(const QVector<ShaderAttribute> &attributesDescription); + void initializeUniformBlocks(const QVector<ShaderUniformBlock> &uniformBlockDescription); + void initializeSamplers(const QVector<ShaderAttribute> &samplerDescription); + void initializeImages(const QVector<ShaderAttribute> &imageDescription); + + QVector<UBO_Member> m_uboMembers; + + void initializeShaderStorageBlocks(const QVector<ShaderStorageBlock> &shaderStorageBlockDescription); + + 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..ffe96c326 --- /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..5aa6ed695 --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/shaderparameterpack_p.h @@ -0,0 +1,223 @@ +/**************************************************************************** +** +** 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..40f906b18 --- /dev/null +++ b/src/plugins/renderers/rhi/renderer/shadervariables_p.h @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** 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..d4786e632 --- /dev/null +++ b/src/plugins/renderers/rhi/textures/renderbuffer.cpp @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** 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..47a2d8025 --- /dev/null +++ b/src/plugins/renderers/rhi/textures/texture.cpp @@ -0,0 +1,861 @@ +/**************************************************************************** +** +** 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: + 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: + 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; + } + }; + + 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 uploadRhiData(int level, int layer, QOpenGLTexture::CubeMapFace face, + const QByteArray &bytes, const QTextureImageDataPtr &data) noexcept +{ + QRhiTextureSubresourceUploadDescription description; + description.setData(bytes); + return QRhiTextureUploadEntry(layer, level, description); +} + +// For partial sub image uploads +QRhiTextureUploadEntry uploadRhiData(int mipLevel, int layer, QOpenGLTexture::CubeMapFace cubeFace, + 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; + + if (m_properties.generateMipMaps) { + rhiFlags |= QRhiTexture::UsedWithGenerateMips; + rhiFlags |= QRhiTexture::MipMapped; + } + + const bool issRGB8Format = issRGBFormat(m_properties.format); + if (issRGB8Format) + rhiFlags |= QRhiTexture::sRGB; + +// // Set layers count if texture array +// if (actualTarget == QAbstractTexture::Target1DArray || +// actualTarget == QAbstractTexture::Target2DArray || +// actualTarget == QAbstractTexture::Target2DMultisampleArray || +// actualTarget == QAbstractTexture::TargetCubeMapArray) { +// glTex->setLayers(m_properties.layers); +// } + + if (actualTarget == QAbstractTexture::Target2DMultisample || + actualTarget == QAbstractTexture::Target2DMultisampleArray) { + // Set samples count if multisampled texture + // (multisampled textures don't have mipmaps) + sampleCount = m_properties.samples; + } + + 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) +{ + QVector<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 = m_properties.generateMipMaps ? 1 : data->mipLevels(); + + for (int layer = 0; layer < data->layers(); layer++) { + for (int face = 0; face < data->faces(); face++) { + for (int level = 0; level < mipLevels; level++) { + // ensure we don't accidentally cause a detach / copy of the raw bytes + const QByteArray bytes(data->data(layer, face, level)); + const QRhiTextureUploadEntry entry = uploadRhiData(level, layer, + static_cast<QOpenGLTexture::CubeMapFace>(QOpenGLTexture::CubeMapPositiveX + face), + bytes, data); + uploadEntries.push_back(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 QRhiTextureUploadEntry entry = uploadRhiData(m_images[i].mipLevel, m_images[i].layer, + static_cast<QOpenGLTexture::CubeMapFace>(m_images[i].face), + bytes, imgData); + uploadEntries.push_back(entry); + } + // 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 QRhiTextureUploadEntry entry = uploadRhiData(update.mipLevel(), update.layer(), + static_cast<QOpenGLTexture::CubeMapFace>(update.face()), + xOffset, yOffset, 0, + bytes, imgData); + uploadEntries.push_back(entry); + } + + QRhiTextureUploadDescription uploadDescription; + uploadDescription.setEntries(uploadEntries.begin(), uploadEntries.end()); + + ctx->m_currentUpdates->uploadTexture(m_rhi, uploadDescription); +} + +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..589abcb4d --- /dev/null +++ b/src/plugins/renderers/rhi/textures/texture_p.h @@ -0,0 +1,275 @@ +/**************************************************************************** +** +** 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 diff --git a/src/render/backend/abstractrenderer_p.h b/src/render/backend/abstractrenderer_p.h index 4fa9dca55..c148c3a68 100644 --- a/src/render/backend/abstractrenderer_p.h +++ b/src/render/backend/abstractrenderer_p.h @@ -92,7 +92,6 @@ class BackendNode; class OffscreenSurfaceHelper; class Shader; class RenderBackendResourceAccessor; - class Q_3DRENDERSHARED_PRIVATE_EXPORT AbstractRenderer { public: diff --git a/src/render/configure.json b/src/render/configure.json index 02a6e3747..6e4e07404 100644 --- a/src/render/configure.json +++ b/src/render/configure.json @@ -8,7 +8,8 @@ "commandline": { "options": { - "qt3d-opengl-renderer": "boolean" + "qt3d-opengl-renderer": "boolean", + "qt3d-rhi-renderer": "boolean" } }, @@ -18,6 +19,13 @@ "purpose": "Use the OpenGL renderer", "section": "Qt 3D Renderers", "output": [ "privateFeature" ] + }, + "qt3d-rhi-renderer": { + "label": "RHI Renderer", + "purpose": "Use the RHI renderer", + "section": "Qt 3D Renderers", + "autoDetect": false, + "output": [ "privateFeature" ] } }, @@ -28,7 +36,8 @@ { "section": "Qt 3D Renderers", "entries": [ - "qt3d-opengl-renderer" + "qt3d-opengl-renderer", + "qt3d-rhi-renderer" ] } ] diff --git a/src/render/frontend/qrendersettings.h b/src/render/frontend/qrendersettings.h index 1b4d68de9..3732e9640 100644 --- a/src/render/frontend/qrendersettings.h +++ b/src/render/frontend/qrendersettings.h @@ -43,9 +43,9 @@ #include <Qt3DCore/qcomponent.h> #include <Qt3DRender/qt3drender_global.h> #include <Qt3DRender/qpickingsettings.h> +#include <QtGui/qtguiglobal.h> QT_BEGIN_NAMESPACE - namespace Qt3DRender { class QFrameGraphNode; diff --git a/src/render/materialsystem/qgraphicsapifilter.h b/src/render/materialsystem/qgraphicsapifilter.h index 337193673..80cfe026e 100644 --- a/src/render/materialsystem/qgraphicsapifilter.h +++ b/src/render/materialsystem/qgraphicsapifilter.h @@ -67,7 +67,8 @@ public: OpenGLES = QSurfaceFormat::OpenGLES, // 2 OpenGL = QSurfaceFormat::OpenGL, // 1 Vulkan = 3, // 3 - DirectX // 4 + DirectX, // 4 + RHI, // 5 }; Q_ENUM(Api) // LCOV_EXCL_LINE diff --git a/src/render/materialsystem/shader.cpp b/src/render/materialsystem/shader.cpp index 1e8568bf4..43cf61cd3 100644 --- a/src/render/materialsystem/shader.cpp +++ b/src/render/materialsystem/shader.cpp @@ -74,6 +74,7 @@ const int Shader::modelNormalMatrixNameId = StringToInt::lookupId(QLatin1String( const int Shader::modelViewNormalNameId = StringToInt::lookupId(QLatin1String("modelViewNormal")); const int Shader::viewportMatrixNameId = StringToInt::lookupId(QLatin1String("viewportMatrix")); const int Shader::inverseViewportMatrixNameId = StringToInt::lookupId(QLatin1String("inverseViewportMatrix")); +const int Shader::textureTransformMatrixNameId = StringToInt::lookupId(QLatin1String("textureTransformMatrix")); const int Shader::aspectRatioNameId = StringToInt::lookupId(QLatin1String("aspectRatio")); const int Shader::exposureNameId = StringToInt::lookupId(QLatin1String("exposure")); const int Shader::gammaNameId = StringToInt::lookupId(QLatin1String("gamma")); diff --git a/src/render/materialsystem/shader_p.h b/src/render/materialsystem/shader_p.h index 7af6ffe9d..60495d52b 100644 --- a/src/render/materialsystem/shader_p.h +++ b/src/render/materialsystem/shader_p.h @@ -86,6 +86,7 @@ public: static const int modelViewNormalNameId; static const int viewportMatrixNameId; static const int inverseViewportMatrixNameId; + static const int textureTransformMatrixNameId; static const int aspectRatioNameId; static const int exposureNameId; static const int gammaNameId; diff --git a/src/render/renderstates/qblendequationarguments.h b/src/render/renderstates/qblendequationarguments.h index e454dbae5..4c881c3ae 100644 --- a/src/render/renderstates/qblendequationarguments.h +++ b/src/render/renderstates/qblendequationarguments.h @@ -66,8 +66,8 @@ public: One = 1, SourceColor = 0x0300, SourceAlpha = 0x0302, - Source1Alpha, - Source1Color, + Source1Alpha, // ### Qt 6: Fix -> has same value as OneMinusSourceAlpha + Source1Color, // ### Qt 6: Fix -> has same value as DestinationAlpha DestinationColor = 0x0306, DestinationAlpha = 0x0304, SourceAlphaSaturate = 0x0308, diff --git a/src/render/renderstates/renderstateset.cpp b/src/render/renderstates/renderstateset.cpp index 6b66d2dd7..5b9c4e8b5 100644 --- a/src/render/renderstates/renderstateset.cpp +++ b/src/render/renderstates/renderstateset.cpp @@ -102,7 +102,7 @@ StateMaskSet RenderStateSet::stateMask() const // This modifies our state to add states from others // if we don't already contain a state with that type set -void RenderStateSet::merge(RenderStateSet *other) +void RenderStateSet::merge(const RenderStateSet *other) { m_stateMask |= other->stateMask(); const QVector<StateVariant> otherStates = other->states(); diff --git a/src/render/renderstates/renderstateset_p.h b/src/render/renderstates/renderstateset_p.h index 119f1edca..667c2614d 100644 --- a/src/render/renderstates/renderstateset_p.h +++ b/src/render/renderstates/renderstateset_p.h @@ -89,9 +89,10 @@ public: int changeCost(RenderStateSet* previousState); StateMaskSet stateMask() const; - void merge(RenderStateSet *other); + void merge(const RenderStateSet *other); - QVector<StateVariant> states() const { return m_states; } + const QVector<StateVariant>& states() const noexcept { return m_states; } + QVector<StateVariant>& states() noexcept { return m_states; } bool canAddStateOfType(StateMask type) const; diff --git a/src/src.pro b/src/src.pro index 047e67bf2..0a16af003 100644 --- a/src/src.pro +++ b/src/src.pro @@ -114,7 +114,7 @@ qtHaveModule(quick) { SUBDIRS += src_core -QT_FOR_CONFIG += 3dcore +QT_FOR_CONFIG += 3dcore shadertools include($$OUT_PWD/core/qt3dcore-config.pri) qtConfig(qt3d-input): SUBDIRS += src_input diff --git a/tests/manual/manual.pro b/tests/manual/manual.pro index 119c4f653..633c18006 100644 --- a/tests/manual/manual.pro +++ b/tests/manual/manual.pro @@ -11,7 +11,8 @@ SUBDIRS += \ deferred-renderer-cpp \ raster-cpp \ qtbug-72236 \ - manual-renderloop + manual-renderloop \ + rhi qtHaveModule(multimedia): { diff --git a/tests/manual/rhi/main.cpp b/tests/manual/rhi/main.cpp new file mode 100644 index 000000000..914ebb21b --- /dev/null +++ b/tests/manual/rhi/main.cpp @@ -0,0 +1,441 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QGuiApplication> + +#include <Qt3DCore/QEntity> +#include <Qt3DRender/QCamera> +#include <Qt3DRender/QCameraLens> +#include <Qt3DCore/QTransform> +#include <Qt3DCore/QAspectEngine> + +#include <Qt3DInput/QInputAspect> + +#include <Qt3DRender/QRenderStateSet> +#include <Qt3DRender/QRenderAspect> +#include <Qt3DExtras/QForwardRenderer> +#include <Qt3DExtras/QPerVertexColorMaterial> + +#include <Qt3DRender/QGeometryRenderer> +#include <Qt3DRender/QGeometry> +#include <Qt3DRender/QAttribute> +#include <Qt3DRender/QBuffer> + +#include <QPropertyAnimation> +#include <Qt3DExtras/qt3dwindow.h> +#include <Qt3DExtras/qorbitcameracontroller.h> +#include <Qt3DRender/QParameter> +#include <Qt3DRender/QEffect> +#include <Qt3DRender/QTechnique> +#include <Qt3DRender/QAbstractTexture> +#include <Qt3DRender/QShaderProgram> +#include <Qt3DRender/QRenderPass> +#include <Qt3DRender/QBlendEquation> +#include <Qt3DRender/QBlendEquationArguments> +#include <Qt3DRender/QFilterKey> +#include <Qt3DRender/QGraphicsApiFilter> +#include <Qt3DRender/QRenderSurfaceSelector> +#include <Qt3DRender/QViewport> +#include <Qt3DRender/QCameraSelector> +#include <Qt3DRender/QNoDraw> +#include <QColor> +#include <QVector2D> +#include <QUrl> +#include <QTimer> +#include <Qt3DRender/QMaterial> +#include <Qt3DRender/QFilterKey> +#include <Qt3DRender/QTechnique> +#include <Qt3DRender/QMaterial> +#include <Qt3DRender/QTexture> +#include <qmath.h> + +static const constexpr auto vertex_shader = R"_(#version 450 + +layout(location = 0) in vec3 vertexPosition; +layout(location = 1) in vec3 vertexColor; +layout(location = 0) out vec3 color; + +layout(std140, binding = 0) uniform qt3d_render_view_uniforms { + mat4 viewMatrix; + mat4 projectionMatrix; + mat4 viewProjectionMatrix; + mat4 inverseViewMatrix; + mat4 inverseProjectionMatrix; + mat4 inverseViewProjectionMatrix; + mat4 viewportMatrix; + mat4 inverseViewportMatrix; + vec4 textureTransformMatrix; + vec3 eyePosition; + float aspectRatio; + float gamma; + float exposure; + float time; +}; +layout(std140, binding = 1) uniform qt3d_command_uniforms { + mat4 modelMatrix; + mat4 inverseModelMatrix; + mat4 modelViewMatrix; + mat4 inverseModelViewMatrix; + mat4 mvp; + mat4 inverseModelViewProjectionMatrix; +}; +void main() +{ + color = vertexColor; + gl_Position = mvp * vec4(vertexPosition, 1.0); +} +)_"; + +static const constexpr auto fragment_shader = R"_(#version 450 + +layout(location = 0) out vec4 fragColor; +layout(location = 0) in vec3 color; + +layout(std140, binding = 0) uniform qt3d_render_view_uniforms { + mat4 viewMatrix; + mat4 projectionMatrix; + mat4 viewProjectionMatrix; + mat4 inverseViewMatrix; + mat4 inverseProjectionMatrix; + mat4 inverseViewProjectionMatrix; + mat4 viewportMatrix; + mat4 inverseViewportMatrix; + vec4 textureTransformMatrix; + vec3 eyePosition; + float aspectRatio; + float gamma; + float exposure; + float time; +}; +layout(std140, binding = 1) uniform qt3d_command_uniforms { + mat4 modelMatrix; + mat4 inverseModelMatrix; + mat4 modelViewMatrix; + mat4 inverseModelViewMatrix; + mat4 mvp; + mat4 inverseModelViewProjectionMatrix; +}; +layout(std140, binding = 2) uniform custom_ubo { + vec3 colorFactor; +}; + +layout(binding = 3) uniform sampler2D myTexture; +void main() +{ + vec2 texCoord = color.xz; + vec2 rhiTexCoord = textureTransformMatrix.xy * texCoord+ textureTransformMatrix.zw; + fragColor = vec4(color * colorFactor, 1.0); + + fragColor *= texture(myTexture, rhiTexCoord); +} + +)_"; + +class Material : public Qt3DRender::QMaterial +{ +public: + explicit Material(Qt3DCore::QNode *parent = nullptr) + : QMaterial(parent) + , m_effect(new Qt3DRender::QEffect(this)) +{ + setEffect(m_effect); + + m_testParam = new Qt3DRender::QParameter(QStringLiteral("example"), float(0.5)); + + m_effect->addParameter(m_testParam); + + m_filter = new Qt3DRender::QFilterKey(this); + m_filter->setName(QStringLiteral("renderingStyle")); + m_filter->setValue(QStringLiteral("forward")); + + m_technique = new Qt3DRender::QTechnique(m_effect); + m_technique->addFilterKey(m_filter); + + m_effect->addTechnique(m_technique); + + m_program = new Qt3DRender::QShaderProgram(m_effect); + m_program->setVertexShaderCode(vertex_shader); + m_program->setFragmentShaderCode(fragment_shader); + + m_renderPass = new Qt3DRender::QRenderPass(m_effect); + + m_renderPass->setShaderProgram(m_program); + + m_technique->addRenderPass(m_renderPass); + + m_technique->graphicsApiFilter()->setApi(Qt3DRender::QGraphicsApiFilter::RHI); +} +private: + Qt3DRender::QEffect *m_effect{}; + Qt3DRender::QParameter *m_testParam{}; + Qt3DRender::QFilterKey *m_filter{}; + Qt3DRender::QTechnique *m_technique{}; + Qt3DRender::QShaderProgram *m_program{}; + Qt3DRender::QRenderPass *m_renderPass{}; +}; + +int main(int argc, char* argv[]) +{ + qputenv("QT3D_RENDERER", "rhi"); + QGuiApplication app(argc, argv); + + auto api = Qt3DRender::API::OpenGL; + if (argc >= 2) { + +#ifdef Q_OS_WIN + if (argv[1] == QByteArrayLiteral("--d3d11")) api = Qt3DRender::API::DirectX; +#endif + +#if QT_CONFIG(vulkan) + if (argv[1] == QByteArrayLiteral("--vulkan")) api = Qt3DRender::API::Vulkan; +#endif + +#if defined(Q_OS_MACOS) || defined(Q_OS_IOS) + if (argv[1] == QByteArrayLiteral("--metal")) api = Qt3DRender::API::Metal; +#endif + } + + Qt3DExtras::Qt3DWindow view{nullptr, api}; + + // Root entity + Qt3DCore::QEntity *rootEntity = new Qt3DCore::QEntity(); + + // Camera + Qt3DRender::QCamera *topViewCamera = new Qt3DRender::QCamera(rootEntity); + topViewCamera->setPosition(QVector3D(0, 40, 0)); + topViewCamera->setViewCenter(QVector3D(0, 0, 0)); + topViewCamera->setUpVector(QVector3D(0, 0, 1)); + topViewCamera->lens()->setPerspectiveProjection(45.0f, 16.0f/9.0f, 0.1f, 1000.0f); + + Qt3DRender::QCamera *cameraEntity = view.camera(); + cameraEntity->lens()->setPerspectiveProjection(45.0f, 16.0f/9.0f, 0.1f, 1000.0f); + cameraEntity->setPosition(QVector3D(0, 0, 40.0f)); + cameraEntity->setUpVector(QVector3D(0, 1, 0)); + cameraEntity->setViewCenter(QVector3D(0, 0, 0)); + + { + // Custom FG + auto *surfaceSelector = new Qt3DRender::QRenderSurfaceSelector(); + surfaceSelector->setSurface(&view); + + // RV 1 + auto *clearBuffer = new Qt3DRender::QClearBuffers(surfaceSelector); + clearBuffer->setBuffers(Qt3DRender::QClearBuffers::ColorDepthBuffer); + clearBuffer->setClearColor(QColor::fromRgbF(0.1, 0.5, 0.0, 1.0)); + auto *noDraw = new Qt3DRender::QNoDraw(clearBuffer); + + // RV 2 + auto *cameraSelector1 = new Qt3DRender::QCameraSelector(surfaceSelector); + cameraSelector1->setCamera(view.camera()); + auto *viewport1 = new Qt3DRender::QViewport(cameraSelector1); + viewport1->setNormalizedRect(QRectF(0.0f, 0.0f, 0.5f, 0.5f)); + + // RV3 + auto *cameraSelector2 = new Qt3DRender::QCameraSelector(surfaceSelector); + cameraSelector2->setCamera(topViewCamera); + auto *viewport2 = new Qt3DRender::QViewport(cameraSelector2); + viewport2->setNormalizedRect(QRectF(0.5f, 0.5f, 0.5f, 0.5f)); + + view.setActiveFrameGraph(surfaceSelector); + } + + QTimer *cameraAnimationTimer = new QTimer(&view); + QObject::connect(cameraAnimationTimer, &QTimer::timeout, + [cameraEntity] { + static int angle = 0; + const float radius = 40.0f; + const float anglef = qDegreesToRadians(float(angle)); + cameraEntity->setPosition(QVector3D(qSin(anglef), 0.0f, qCos(anglef)) * radius); + angle += 1; + }); + cameraAnimationTimer->start(16); + + // For camera controls + Qt3DExtras::QOrbitCameraController *camController = new Qt3DExtras::QOrbitCameraController(rootEntity); + camController->setCamera(cameraEntity); + + // Material + Qt3DRender::QMaterial *material = new Material(rootEntity); + Qt3DRender::QParameter *parameter = new Qt3DRender::QParameter(QStringLiteral("colorFactor"), QColor(Qt::white)); + material->addParameter(parameter); + + Qt3DRender::QTextureLoader *textureLoader = new Qt3DRender::QTextureLoader{}; + textureLoader->setSource(QUrl{QStringLiteral("qrc:///qtlogo.png")}); + Qt3DRender::QParameter *texture = new Qt3DRender::QParameter(QStringLiteral("myTexture"), textureLoader); + material->addParameter(texture); + + QTimer *parameterAnimationTimer = new QTimer(&view); + QObject::connect(parameterAnimationTimer, &QTimer::timeout, + [parameter] { + static int angle = 0; + const float anglef = qDegreesToRadians(float(angle)); + parameter->setValue(QColor::fromRgbF(fabs(qCos(anglef)), fabs(qSin(anglef)), 1.0f)); + angle += 10; + }); + parameterAnimationTimer->start(16); + + // Torus + Qt3DCore::QEntity *customMeshEntity = new Qt3DCore::QEntity(rootEntity); + + // Transform + Qt3DCore::QTransform *transform = new Qt3DCore::QTransform; + transform->setScale(8.0f); + + // Custom Mesh (TetraHedron) + Qt3DRender::QGeometryRenderer *customMeshRenderer = new Qt3DRender::QGeometryRenderer; + Qt3DRender::QGeometry *customGeometry = new Qt3DRender::QGeometry(customMeshRenderer); + + Qt3DRender::QBuffer *vertexDataBuffer = new Qt3DRender::QBuffer(customGeometry); + Qt3DRender::QBuffer *indexDataBuffer = new Qt3DRender::QBuffer(customGeometry); + + // 5 vertices of 3 vertices + 3 colors + QByteArray vertexBufferData; + vertexBufferData.resize(5 * (3 + 3) * sizeof(float)); + + // Vertices + QVector3D v0(-1.0f, 0.0f, -1.0f); + QVector3D v1(1.0f, 0.0f, -1.0f); + QVector3D v2(-1.0f, 0.0f, 1.0f); + QVector3D v3(1.0f, 0.0f, 1.0f); + QVector3D v4(0.0f, 2.0f, 0.0f); + + QVector3D red(1.0f, 0.0f, 0.0f); + QVector3D green(0.0f, 1.0f, 0.0f); + QVector3D blue(0.0f, 0.0f, 1.0f); + QVector3D white(1.0f, 1.0f, 1.0f); + QVector3D grey(0.5f, 0.5f, 0.5f); + + const QVector<QVector3D> vertices = QVector<QVector3D>() + << v0 << red + << v1 << green + << v2 << blue + << v3 << grey + << v4 << white; + + memcpy(vertexBufferData.data(), vertices.constData(), vertices.size() * sizeof(QVector3D)); + vertexDataBuffer->setData(vertexBufferData); + + QByteArray indexBufferData; + // 6 triangle faces + indexBufferData.resize(6 * 3 * sizeof(ushort)); + ushort *rawIndexArray = reinterpret_cast<ushort *>(indexBufferData.data()); + + rawIndexArray[0] = 0; + rawIndexArray[1] = 1; + rawIndexArray[2] = 2; + + rawIndexArray[3] = 2; + rawIndexArray[4] = 1; + rawIndexArray[5] = 3; + + rawIndexArray[6] = 2; + rawIndexArray[7] = 3; + rawIndexArray[8] = 4; + + rawIndexArray[9] = 3; + rawIndexArray[10] = 1; + rawIndexArray[11] = 4; + + rawIndexArray[12] = 1; + rawIndexArray[13] = 0; + rawIndexArray[14] = 4; + + rawIndexArray[15] = 0; + rawIndexArray[16] = 2; + rawIndexArray[17] = 4; + + indexDataBuffer->setData(indexBufferData); + + // Attributes + Qt3DRender::QAttribute *positionAttribute = new Qt3DRender::QAttribute(); + positionAttribute->setAttributeType(Qt3DRender::QAttribute::VertexAttribute); + positionAttribute->setBuffer(vertexDataBuffer); + positionAttribute->setVertexBaseType(Qt3DRender::QAttribute::Float); + positionAttribute->setVertexSize(3); + positionAttribute->setByteOffset(0); + positionAttribute->setByteStride(6 * sizeof(float)); + positionAttribute->setCount(5); + positionAttribute->setName(Qt3DRender::QAttribute::defaultPositionAttributeName()); + + Qt3DRender::QAttribute *colorAttribute = new Qt3DRender::QAttribute(); + colorAttribute->setAttributeType(Qt3DRender::QAttribute::VertexAttribute); + colorAttribute->setBuffer(vertexDataBuffer); + colorAttribute->setVertexBaseType(Qt3DRender::QAttribute::Float); + colorAttribute->setVertexSize(3); + colorAttribute->setByteOffset(3 * sizeof(float)); + colorAttribute->setByteStride(6 * sizeof(float)); + colorAttribute->setCount(5); + colorAttribute->setName(Qt3DRender::QAttribute::defaultColorAttributeName()); + + Qt3DRender::QAttribute *indexAttribute = new Qt3DRender::QAttribute(); + indexAttribute->setAttributeType(Qt3DRender::QAttribute::IndexAttribute); + indexAttribute->setBuffer(indexDataBuffer); + indexAttribute->setVertexBaseType(Qt3DRender::QAttribute::UnsignedShort); + indexAttribute->setVertexSize(1); + indexAttribute->setByteOffset(0); + indexAttribute->setByteStride(0); + indexAttribute->setCount(18); + + customGeometry->addAttribute(positionAttribute); + customGeometry->addAttribute(colorAttribute); + customGeometry->addAttribute(indexAttribute); + + customMeshRenderer->setPrimitiveType(Qt3DRender::QGeometryRenderer::Triangles); + customMeshRenderer->setGeometry(customGeometry); + + customMeshEntity->addComponent(customMeshRenderer); + customMeshEntity->addComponent(transform); + customMeshEntity->addComponent(material); + + view.setRootEntity(rootEntity); + view.show(); + + return app.exec(); +} diff --git a/tests/manual/rhi/qtlogo.png b/tests/manual/rhi/qtlogo.png Binary files differnew file mode 100644 index 000000000..19eecfabf --- /dev/null +++ b/tests/manual/rhi/qtlogo.png diff --git a/tests/manual/rhi/rhi.pro b/tests/manual/rhi/rhi.pro new file mode 100644 index 000000000..94e560391 --- /dev/null +++ b/tests/manual/rhi/rhi.pro @@ -0,0 +1,10 @@ +!include( ../manual.pri ) { + error( "Couldn't find the manual.pri file!" ) +} + +QT += 3dcore 3drender 3dinput 3dextras + +SOURCES += \ + main.cpp + +RESOURCES += qtlogo.png |