/**************************************************************************** ** ** 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 #include #include #include #include #include QT_BEGIN_NAMESPACE namespace Qt3DRender { namespace Render { namespace Rhi { RHIShader::RHIShader() : m_isLoaded(false) { m_shaderCode.resize(static_cast(QShaderProgram::Compute) + 1); } const std::vector &RHIShader::uniformsNames() const { return m_uniformsNames; } const std::vector &RHIShader::attributesNames() const { return m_attributesNames; } const std::vector &RHIShader::uniformBlockNames() const { return m_uniformBlockNames; } const std::vector &RHIShader::storageBlockNames() const { return m_shaderStorageBlockNames; } const std::vector &RHIShader::samplerNames() const { return m_samplerNames; } const std::vector &RHIShader::imagesNames() const { return m_imageNames; } const std::vector &RHIShader::shaderCode() const { return m_shaderCode; } namespace { 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 std::vector stableRemoveDuplicates(std::vector in, Pred predicate) { std::vector out; for (const auto &element : in) { if (std::none_of(out.begin(), out.end(), [&](T &other) { return predicate(element, other); })) out.push_back(element); } return out; } // Utility function to enumerate an array of dimensions // Given dims == [0, 3, 2] and maxs == [4, 4, 4] // changes dims into [0, 3, 3] // Given dims == [0, 3, 3] and maxs == [4, 4, 4] // changes dims into [1, 0, 0] bool incrementArray(QVarLengthArray &dims, const QList &maxs) { const int n = dims.size(); int i = n; for (; i-- > 0;) { if (dims[i] == maxs[i] - 1) { if (i == 0) { // we're done return false; } continue; } else { dims[i]++; for (int j = i + 1; j < n; j++) { dims[j] = 0; } return true; } } return false; } // Call a function with a string such as [0][3][2] // for all valable array values, given an array of dimension sizes. // Dimensions must all be >= 1 template void forEachArrayAccessor(const QList &maxs, F f) { if (std::any_of(maxs.begin(), maxs.end(), [](int v) { return v <= 0; })) return; QVarLengthArray dims; dims.resize(maxs.size()); // QVarLengthArray does not initialize ints std::fill(dims.begin(), dims.end(), 0); QString str; do { str.resize(0); for (int k : dims) { str += QStringLiteral("[%1]").arg(k); } f(str); } while (incrementArray(dims, maxs)); } } void RHIShader::recordAllUniforms(UBO_Member &uboMember, QString parentName) { const QShaderDescription::BlockVariable &member = uboMember.blockVariable; const bool isStruct = !member.structMembers.empty(); const bool isArray = !member.arrayDims.empty(); // "foo.bar" const QString fullMemberName = parentName + member.name; m_unqualifiedUniformNames << fullMemberName; if (isStruct && !isArray) { m_structNames.push_back(fullMemberName); m_structNamesIds.push_back(StringToInt::lookupId(fullMemberName)); for (const QShaderDescription::BlockVariable& bv : member.structMembers) { UBO_Member innerMember {StringToInt::lookupId(fullMemberName), bv, {}}; // recordAllUniforms("baz", "foo.bar.") const QString structMemberNamePrefix = fullMemberName + QLatin1Char('.'); recordAllUniforms(innerMember, structMemberNamePrefix); uboMember.structMembers.push_back(innerMember); } } else if (!isStruct && isArray) { // We iterate through all the [l][n][m] by building [0][0][0] and incrementing forEachArrayAccessor(member.arrayDims, [&](const QString &str) { // "foo.bar[1][2]" const QString unqualifiedMemberName = (fullMemberName + str); m_unqualifiedUniformNames << unqualifiedMemberName; // Record as an individual uniform m_uniformsNames.push_back(unqualifiedMemberName); const int nameId = StringToInt::lookupId(unqualifiedMemberName); m_uniformsNamesIds.push_back(nameId); // Question : does it make sense to also record foo[0], foo[0][0], etc... // if there are e.g. 3 dimensions ? }); } else if (isStruct && isArray) { // Record the struct names forEachArrayAccessor(member.arrayDims, [&] (const QString& str) { m_structNames.push_back(fullMemberName + str); m_structNamesIds.push_back(StringToInt::lookupId(m_structNames.back())); }); // Record the array times the struct members => entry[i].struct_member forEachArrayAccessor(member.arrayDims, [&] (const QString& str) { UBO_Member arrayMember {StringToInt::lookupId(fullMemberName + str), {}, {}}; // Record all struct member into the array member[i] for (const QShaderDescription::BlockVariable& bv : member.structMembers) { //recordAllUniforms("baz", "foo.bar[1][2].") const QString structMemberNamePrefix = fullMemberName + str + QLatin1Char('.'); UBO_Member innerMember {StringToInt::lookupId(structMemberNamePrefix), bv, {}}; recordAllUniforms(innerMember, structMemberNamePrefix); arrayMember.structMembers.push_back(innerMember); } // When dealing with an array of structs, we treat structMembers as arrayMembers uboMember.structMembers.push_back(arrayMember); }); } else { // Final member (not array or struct) // Replace nameId with final nameId name uboMember.nameId = StringToInt::lookupId(fullMemberName); // Record as an individual uniform m_uniformsNames.push_back(fullMemberName); const int nameId = StringToInt::lookupId(fullMemberName); m_uniformsNamesIds.push_back(nameId); } } namespace { // Function to check whether the array conforms to the regex // "_[0-9]+" - except QRegularExpression does not support QByteArray bool isGeneratedUBOName(const QByteArray& arr) { if (arr.size() < 2) return false; if (!arr.startsWith('_')) return false; for (int i = 1, N = arr.size(); i < N; i++) { if (arr[i] < '0' || arr[i] > '9') return false; } return true; } } void RHIShader::introspect() { std::vector rhiUBO; std::vector rhiSSBO; std::vector uniformBlocks; std::vector storageBlocks; std::vector attributes; std::vector samplers; std::vector images; if (m_stages[QShader::ComputeStage].isValid()) { const QShaderDescription &comp = m_stages[QShader::ComputeStage].description(); Qt3DCore::append(rhiUBO, comp.uniformBlocks()); Qt3DCore::append(rhiSSBO, comp.storageBlocks()); } else { // 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 }); } Qt3DCore::append(rhiUBO, vtx.uniformBlocks()); Qt3DCore::append(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 }); } Qt3DCore::append(rhiUBO, frag.uniformBlocks()); Qt3DCore::append(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, int(ubo.members.size()), ubo.size }); const bool addUnqualifiedUniforms = isGeneratedUBOName(ubo.structName); // Parse Uniform Block members so that we can later on map a Parameter name to an actual m_uniformsNames.reserve(m_uniformsNames.size() + ubo.members.size()); m_uniformsNamesIds.reserve(m_uniformsNames.size()); std::vector uboMembers; uboMembers.reserve(ubo.members.size()); for (const QShaderDescription::BlockVariable &member : qAsConst(ubo.members)) { m_uniformsNames.push_back(member.name); const int nameId = StringToInt::lookupId(member.name); m_uniformsNamesIds.push_back(nameId); UBO_Member uboMember {nameId, member, {}}; if (addUnqualifiedUniforms) recordAllUniforms(uboMember, QStringLiteral("")); uboMembers.push_back(uboMember); } m_uboBlocks.push_back(UBO_Block{ uniformBlocks.back(), uboMembers }); } 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); qCDebug(Shaders) << "Block Names" << m_uniformBlockNames; qCDebug(Shaders) << "Uniform Names" << m_uniformsNames; qCDebug(Shaders) << "SSBO Names" << m_shaderStorageBlockNames; qCDebug(Shaders) << "Attribute Names" << m_attributesNames; } QHash RHIShader::activeUniformsForUniformBlock(int blockIndex) const { return m_uniformBlockIndexToShaderUniforms.value(blockIndex); } ShaderUniformBlock RHIShader::uniformBlockForBlockIndex(int blockIndex) const noexcept { for (size_t i = 0, m = m_uniformBlocks.size(); i < m; ++i) { if (m_uniformBlocks[i].m_index == blockIndex) { return m_uniformBlocks[i]; } } return ShaderUniformBlock(); } ShaderUniformBlock RHIShader::uniformBlockForBlockNameId(int blockNameId) const noexcept { for (size_t i = 0, m = m_uniformBlocks.size(); i < m; ++i) { if (m_uniformBlocks[i].m_nameId == blockNameId) { return m_uniformBlocks[i]; } } return ShaderUniformBlock(); } ShaderUniformBlock RHIShader::uniformBlockForBlockName(const QString &blockName) const noexcept { for (size_t i = 0, m = m_uniformBlocks.size(); i < m; ++i) { if (m_uniformBlocks[i].m_name == blockName) { return m_uniformBlocks[i]; } } return ShaderUniformBlock(); } ShaderUniformBlock RHIShader::uniformBlockForInstanceName(const QString &instanceName) const noexcept { return uniformBlockForInstanceNameId(StringToInt::lookupId(instanceName)); } ShaderUniformBlock RHIShader::uniformBlockForInstanceNameId(int instanceNameId) const noexcept { for (size_t i = 0, m = m_uboBlocks.size(); i < m; ++i) { const UBO_Block &b = m_uboBlocks[i]; for (const UBO_Member &member : b.members) { if (member.nameId == instanceNameId) return b.block; } } return ShaderUniformBlock(); } ShaderStorageBlock RHIShader::storageBlockForBlockIndex(int blockIndex) const noexcept { for (size_t i = 0, m = m_shaderStorageBlockNames.size(); i < m; ++i) { if (m_shaderStorageBlocks[i].m_index == blockIndex) return m_shaderStorageBlocks[i]; } return ShaderStorageBlock(); } ShaderStorageBlock RHIShader::storageBlockForBlockNameId(int blockNameId) const noexcept { for (size_t i = 0, m = m_shaderStorageBlockNames.size(); i < m; ++i) { if (m_shaderStorageBlocks[i].m_nameId == blockNameId) return m_shaderStorageBlocks[i]; } return ShaderStorageBlock(); } ShaderStorageBlock RHIShader::storageBlockForBlockName(const QString &blockName) const noexcept { for (size_t i = 0, m = m_shaderStorageBlockNames.size(); i < m; ++i) { if (m_shaderStorageBlocks[i].m_name == blockName) return m_shaderStorageBlocks[i]; } return ShaderStorageBlock(); } RHIShader::ParameterKind RHIShader::categorizeVariable(int nameId) const noexcept { if (Qt3DCore::contains(m_uniformBlockNamesIds, nameId)) return ParameterKind::UBO; else if (Qt3DCore::contains(m_shaderStorageBlockNamesIds, nameId)) return ParameterKind::SSBO; else if (Qt3DCore::contains(m_structNamesIds, nameId)) return ParameterKind::Struct; return ParameterKind::Uniform; } bool RHIShader::hasUniform(int nameId) const noexcept { return Qt3DCore::contains(m_uniformsNamesIds, nameId); } bool RHIShader::hasActiveVariables() const noexcept { return !m_attributeNamesIds.empty() || !m_uniformsNamesIds.empty() || !m_uniformBlockNamesIds.empty() || !m_shaderStorageBlockNamesIds.empty(); } void RHIShader::setShaderCode(const std::vector &shaderCode) { m_shaderCode.clear(); Qt3DCore::append(m_shaderCode, shaderCode); } void RHIShader::setFragOutputs(const QHash &fragOutputs) { { QMutexLocker lock(&m_mutex); m_fragOutputs = fragOutputs; } // updateDNA(); } const QHash RHIShader::fragOutputs() const { QMutexLocker lock(&m_mutex); return m_fragOutputs; } void RHIShader::initializeAttributes(const std::vector &attributesDescription) { m_attributes = attributesDescription; m_attributesNames.resize(attributesDescription.size()); m_attributeNamesIds.resize(attributesDescription.size()); for (size_t 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 std::vector &samplersDescription) { m_samplers = samplersDescription; m_samplerNames.resize(samplersDescription.size()); m_samplerIds.resize(samplersDescription.size()); for (size_t 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 std::vector &imagesDescription) { m_images = imagesDescription; m_imageNames.resize(imagesDescription.size()); m_imageIds.resize(imagesDescription.size()); for (size_t 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 std::vector &uniformBlockDescription) { m_uniformBlocks = uniformBlockDescription; m_uniformBlockNames.resize(uniformBlockDescription.size()); m_uniformBlockNamesIds.resize(uniformBlockDescription.size()); for (size_t 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 std::vector::const_iterator uniformsIt = m_uniforms.cbegin(); const std::vector::const_iterator uniformsEnd = m_uniforms.cend(); std::vector::const_iterator uniformNamesIt = m_uniformsNames.cbegin(); const std::vector::const_iterator uniformNamesEnd = m_uniformsNames.cend(); QHash 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 std::vector &shaderStorageBlockDescription) { m_shaderStorageBlocks = shaderStorageBlockDescription; m_shaderStorageBlockNames.resize(shaderStorageBlockDescription.size()); m_shaderStorageBlockNamesIds.resize(shaderStorageBlockDescription.size()); for (size_t 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