Revision control
++ // Copyright (C) 2020 Klaralvdalens Datakonsult AB (KDAB).
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include "pipelineuboset_p.h"
++ #include <rendercommand_p.h>
++ #include <renderview_p.h>
++ #include <rhibuffer_p.h>
++ #include <shaderparameterpack_p.h>
++ #include <shadervariables_p.h>
++ #include <rhigraphicspipeline_p.h>
++ #include <rhiresourcemanagers_p.h>
++ #include <submissioncontext_p.h>
++ #include <rhi/qrhi.h>
++ #include <Qt3DRender/private/nodemanagers_p.h>
++ #include <Qt3DRender/private/buffermanager_p.h>
++ #include <Qt3DRender/private/stringtoint_p.h>
++ #include <Qt3DRender/private/buffer_p.h>
++ #include <Qt3DCore/private/vector_helper_p.h>
++
++ QT_BEGIN_NAMESPACE
++
++ namespace Qt3DRender {
++
++ namespace Render {
++
++ namespace Rhi {
++
++ namespace {
++ // This is the minimum max UBO size requirements with GL
++ // therefore safe to assume that is also the baseline for other graphics APIs
++ constexpr size_t MaxUBOByteSize = 16384;
++ }
++
++ PipelineUBOSet::PipelineUBOSet()
++ {
++ }
++
++ PipelineUBOSet::~PipelineUBOSet()
++ {
++ // Resource release is performed by RHIGraphicsPipeline::cleanup()
++ }
++
++ void PipelineUBOSet::clear()
++ {
++ m_renderCommands.clear();
++ }
++
++ void PipelineUBOSet::addRenderCommand(const RenderCommand &cmd)
++ {
++ m_renderCommands.push_back(&cmd);
++ }
++
++ void PipelineUBOSet::setResourceManager(RHIResourceManagers *manager)
++ {
++ m_resourceManagers = manager;
++ }
++
++ void PipelineUBOSet::setNodeManagers(NodeManagers *managers)
++ {
++ m_nodeManagers = managers;
++ }
++
++ void PipelineUBOSet::initializeLayout(SubmissionContext *ctx, RHIShader *shader)
++ {
++ // We should only be called with a clean Pipeline
++ Q_ASSERT(m_rvUBO.buffer.isNull());
++
++ m_rvUBO.binding = 0;
++ m_rvUBO.blockSize = sizeof(RenderViewUBO);
++
++ m_commandsUBO.binding = 1;
++ m_commandsUBO.blockSize = sizeof(CommandUBO);
++ m_commandsUBO.alignedBlockSize = ctx->rhi()->ubufAligned((m_commandsUBO.blockSize));
++ m_commandsUBO.alignment = size_t(ctx->rhi()->ubufAlignment());
++ m_commandsUBO.commandsPerUBO = MaxUBOByteSize / m_commandsUBO.alignedBlockSize;
++
++ // For UBO, we try to create a single large UBO that will contain frontend
++ // Qt3D UBO data at various offsets
++ const std::vector<ShaderUniformBlock> &uniformBlocks = shader->uniformBlocks();
++ for (const ShaderUniformBlock &block : uniformBlocks) {
++ if (block.m_binding > 1) { // Binding 0 and 1 are for RV and Command UBOs
++ const size_t alignedBlockSize = size_t(ctx->rhi()->ubufAligned(block.m_size));
++ m_materialsUBOs.push_back(
++ { block.m_binding,
++ block.m_size,
++ alignedBlockSize,
++ size_t(ctx->rhi()->ubufAlignment()),
++ MaxUBOByteSize / alignedBlockSize,
++ {} });
++ }
++ }
++
++ // For SSBO, we have one SSBO per frontend Qt3D SSBO
++ m_storageBlocks = shader->storageBlocks();
++ }
++
++ void PipelineUBOSet::releaseResources()
++ {
++ Q_ASSERT(m_resourceManagers);
++ RHIBufferManager *bufferManager = m_resourceManagers->rhiBufferManager();
++
++ bufferManager->release(m_rvUBO.buffer);
++
++ for (const HRHIBuffer &hBuf : m_commandsUBO.buffers)
++ bufferManager->release(hBuf);
++
++ m_rvUBO = {};
++ m_commandsUBO = {};
++
++ if (!m_materialsUBOs.empty()) {
++ for (const MultiUBOBufferWithBindingAndBlockSize &ubo : m_materialsUBOs) {
++ for (const HRHIBuffer &hBuf : ubo.buffers)
++ bufferManager->release(hBuf);
++ }
++ m_materialsUBOs.clear();
++ }
++ }
++
++ bool PipelineUBOSet::allocateUBOs(SubmissionContext *ctx)
++ {
++ RHIBufferManager *bufferManager = m_resourceManagers->rhiBufferManager();
++ // Note: RHIBuffer only releases/recreates when it needs more size than what it
++ // can currently hold
++ Q_ASSERT(m_resourceManagers);
++ const bool dynamic = true;
++ const size_t commandCount = std::max(m_renderCommands.size(), size_t(1));
++
++ if (m_rvUBO.buffer.isNull())
++ m_rvUBO.buffer = bufferManager->allocateResource();
++
++ // RHIBuffer only reallocates if size is < than required
++ m_rvUBO.buffer->allocate(QByteArray(m_rvUBO.blockSize, '\0'), dynamic);
++ // Binding buffer ensure underlying RHI resource is created
++ m_rvUBO.buffer->bind(ctx, RHIBuffer::UniformBuffer);
++
++ auto allocateMultiUBOsForCommands = [&] (MultiUBOBufferWithBindingAndBlockSize &ubo) {
++ // Round up
++ const size_t uboCount = std::ceil(float(commandCount) / ubo.commandsPerUBO);
++
++ if (ubo.buffers.size() < uboCount)
++ ubo.buffers.resize(uboCount);
++
++ for (HRHIBuffer &buf : ubo.buffers) {
++ if (buf.isNull())
++ buf = bufferManager->allocateResource();
++ // We need to take into account any minimum alignment requirement for dynamic offsets
++ buf->allocate(QByteArray(MaxUBOByteSize, '\0'), dynamic);
++ buf->bind(ctx, RHIBuffer::UniformBuffer);
++ }
++ };
++
++ // Commands UBOs
++ allocateMultiUBOsForCommands(m_commandsUBO);
++
++ // Material UBOs
++ for (MultiUBOBufferWithBindingAndBlockSize &ubo : m_materialsUBOs) {
++ if (ubo.binding > 1) // Binding 0 and 1 are for RV and Command UBOs
++ allocateMultiUBOsForCommands(ubo);
++ }
++
++ // SSBO are using RHIBuffer directly, nothing we need to handle ourselves
++
++ return true;
++ }
++
++ size_t PipelineUBOSet::distanceToCommand(const RenderCommand &cmd) const
++ {
++ const auto it = std::find(m_renderCommands.begin(), m_renderCommands.end(),
++ &cmd);
++ if (Q_UNLIKELY(it == m_renderCommands.end())) {
++ qCWarning(Backend) << "Command not found in UBOSet";
++ return 0;
++ }
++ return std::distance(m_renderCommands.begin(), it);
++ }
++
++ std::vector<QRhiCommandBuffer::DynamicOffset> PipelineUBOSet::offsets(const RenderCommand &cmd) const
++ {
++ std::vector<QRhiCommandBuffer::DynamicOffset> offsets;
++ offsets.reserve(1 + m_materialsUBOs.size());
++
++ // RenderCommand offset
++ // binding, offset
++ const size_t dToCmd = distanceToCommand(cmd);
++ {
++ // Compute offset relative to the select UBO in the subset
++ const auto localOffset = m_commandsUBO.localOffsetInBufferForCommand(dToCmd);
++ offsets.push_back({1, quint32(localOffset)});
++ }
++
++ for (const MultiUBOBufferWithBindingAndBlockSize &materialUBO : m_materialsUBOs) {
++ // Compute offset relative to the select UBO in the subset
++ const size_t localOffset = materialUBO.localOffsetInBufferForCommand(dToCmd);
++ offsets.push_back({materialUBO.binding, quint32(localOffset)});
++ }
++
++ return offsets;
++ }
++
++ // Note: this function is currently the same as resourceBindings However, once
++ // https://codereview.qt-project.org/c/qt/qtbase/+/307472 lands we can get rid
++ // of passing the RenderCommand here as well as having to provide the actual
++ // Resources in that function which means that one will be about providing the
++ // Pipeline resourceLayout only and resourceBindings will be about providing
++ // the actual resources for a RenderCommand
++ std::vector<QRhiShaderResourceBinding> PipelineUBOSet::resourceLayout(const RHIShader *shader)
++ {
++ const QRhiShaderResourceBinding::StageFlags stages = QRhiShaderResourceBinding::VertexStage|QRhiShaderResourceBinding::FragmentStage;
++ std::vector<QRhiShaderResourceBinding> bindings = {
++ QRhiShaderResourceBinding::uniformBuffer(0, stages, nullptr),
++ QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(1, stages, nullptr, sizeof(CommandUBO))
++ };
++
++ // TO DO: Handle Parameters that directly define a UBO or SSBO
++ // const auto &blockToUBOs = parameterPack.uniformBuffers();
++ // const auto &blockToSSBOs = parameterPack.shaderStorageBuffers();
++
++ // Create additional empty UBO Buffer for UBO with binding point > 1 (since
++ // we assume 0 and 1 and for Qt3D standard values)
++ for (const MultiUBOBufferWithBindingAndBlockSize &ubo : m_materialsUBOs)
++ bindings.push_back(
++ QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(
++ ubo.binding, stages, nullptr, ubo.blockSize));
++
++ // Samplers
++ for (const ShaderAttribute &samplerAttribute : shader->samplers()) {
++ bindings.push_back(QRhiShaderResourceBinding::sampledTexture(
++ samplerAttribute.m_location,
++ stages, nullptr, nullptr));
++ }
++
++ // SSBO
++ for (const ShaderStorageBlock &b : m_storageBlocks) {
++ bindings.push_back(QRhiShaderResourceBinding::bufferLoadStore(b.m_binding,
++ stages|QRhiShaderResourceBinding::ComputeStage,
++ nullptr));
++ }
++
++ return bindings;
++ }
++
++ std::vector<QRhiShaderResourceBinding> PipelineUBOSet::resourceBindings(const RenderCommand &command)
++ {
++ RHITextureManager *textureManager = m_resourceManagers->rhiTextureManager();
++ RHIShader *shader = command.m_rhiShader;
++ const QRhiShaderResourceBinding::StageFlags stages = QRhiShaderResourceBinding::ComputeStage|QRhiShaderResourceBinding::VertexStage|QRhiShaderResourceBinding::FragmentStage;
++ std::vector<QRhiShaderResourceBinding> bindings = {
++ QRhiShaderResourceBinding::uniformBuffer(0, stages, m_rvUBO.buffer->rhiBuffer()),
++ };
++
++ const size_t dToCmd = distanceToCommand(command);
++
++ // CommandUBO
++ {
++ const HRHIBuffer &commandUBO = m_commandsUBO.bufferForCommand(dToCmd);
++ bindings.push_back(
++ QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(1,
++ stages,
++ commandUBO->rhiBuffer(),
++ int(m_commandsUBO.alignedBlockSize)));
++ }
++
++ // Create additional empty UBO Buffer for UBO with binding point > 1 (since
++ // we assume 0 and 1 and for Qt3D standard values)
++ // Note: if a Parameter provides a UBO Buffer, its content will have been
++ // copied at right offset into the matching materials UBO
++ for (const MultiUBOBufferWithBindingAndBlockSize &ubo : m_materialsUBOs) {
++ const HRHIBuffer &materialUBO = ubo.bufferForCommand(dToCmd);
++ bindings.push_back(
++ QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(
++ ubo.binding, stages, materialUBO->rhiBuffer(), int(ubo.alignedBlockSize)));
++ }
++
++ // Samplers
++ const std::vector<ShaderAttribute> &shaderSamplers = shader->samplers();
++ std::vector<int> samplersSetIds;
++ for (const ShaderParameterPack::NamedResource &textureParameter : command.m_parameterPack.textures()) {
++ const HRHITexture &handle = textureManager->getOrAcquireHandle(textureParameter.nodeId);
++ const RHITexture *textureData = handle.data();
++
++ for (const ShaderAttribute &samplerAttribute : shaderSamplers) {
++ if (samplerAttribute.m_nameId == textureParameter.glslNameId) {
++ const auto rhiTexture = textureData->getRhiTexture();
++ const auto rhiSampler = textureData->getRhiSampler();
++ if (rhiTexture && rhiSampler) {
++ bindings.push_back(QRhiShaderResourceBinding::sampledTexture(
++ samplerAttribute.m_location,
++ stages, rhiTexture, rhiSampler));
++ samplersSetIds.push_back(samplerAttribute.m_nameId);
++ }
++ }
++ }
++ }
++
++ // Some backends (Vulkan) expect the number of bindings to be exactly the
++ // same between the set used to create the pipeline in resourceLayout and
++ // the set used to draw with said pipeline generated here.
++ // This can be problematic if no parameter is provided for that resource
++ const std::vector<ShaderAttribute> unsetSamplers = [&] () {
++ std::vector<ShaderAttribute> unset;
++ for (const ShaderAttribute &attr : shaderSamplers) {
++ const bool matchingSetNameIdFound =
++ std::find_if(samplersSetIds.begin(),
++ samplersSetIds.end(),
++ [&attr] (int nameId) {
++ return attr.m_nameId == nameId;
++ }) != samplersSetIds.end();
++ if (!matchingSetNameIdFound)
++ unset.push_back(attr);
++ }
++ return unset;
++ }();
++
++ // Generate an empty binding for unset samplers
++ const int envLightIrradianceNameId = StringToInt::lookupId(QStringLiteral("envLight_irradiance"));
++ const int envLightSpecularNameId = StringToInt::lookupId(QStringLiteral("envLight_specular"));
++ for (const ShaderAttribute &sampler : unsetSamplers) {
++ if (sampler.m_nameId != envLightSpecularNameId && sampler.m_nameId != envLightIrradianceNameId)
++ qCWarning(Backend) << "Sampler" << sampler.m_name << "wasn't set on material. Rendering might not work as expected";
++ }
++
++ // SSBO
++ for (const BlockToSSBO &ssbo : command.m_parameterPack.shaderStorageBuffers()) {
++ RHIBuffer *buffer = m_resourceManagers->rhiBufferManager()->lookupResource(ssbo.m_bufferID);
++ if (buffer) {
++ Q_ASSERT(stages & QRhiShaderResourceBinding::ComputeStage);
++ bindings.push_back(QRhiShaderResourceBinding::bufferLoadStore(
++ ssbo.m_bindingIndex,
++ stages,
++ buffer->rhiBuffer()));
++ }
++ }
++
++ return bindings;
++ }
++
++ void PipelineUBOSet::uploadUBOs(SubmissionContext *ctx, RenderView *rv)
++ {
++ // Update UBO data for RV and RC data
++ m_rvUBO.buffer->update(QByteArray::fromRawData(reinterpret_cast<const char *>(rv->renderViewUBO()),
++ sizeof(RenderViewUBO)));
++ int distance = 0;
++ for (const RenderCommand *command : m_renderCommands) {
++ uploadUBOsForCommand(*command, distance);
++ ++distance;
++ }
++
++ // Trigger actual upload to GPU
++ m_rvUBO.buffer->bind(ctx, RHIBuffer::UniformBuffer);
++ for (const HRHIBuffer &ubo : m_commandsUBO.buffers)
++ ubo->bind(ctx, RHIBuffer::UniformBuffer);
++ for (const MultiUBOBufferWithBindingAndBlockSize &multiUbo : m_materialsUBOs) {
++ for (const HRHIBuffer &ubo : multiUbo.buffers)
++ ubo->bind(ctx, RHIBuffer::UniformBuffer);
++ }
++ }
++
++ namespace {
++ /*
++ void printUpload(const UniformValue &value, const QShaderDescription::BlockVariable &member, int arrayOffset)
++ {
++ switch (member.type) {
++ case QShaderDescription::VariableType::Int:
++ qDebug() << "Updating" << member.name << "with int data: " << *value.constData<int>()
++ << " (offset: " << member.offset + arrayOffset << ", size: " << member.size << ")";
++ break;
++ case QShaderDescription::VariableType::Float:
++ qDebug() << "Updating" << member.name << "with float data: " << *value.constData<float>()
++ << " (offset: " << member.offset + arrayOffset << ", size: " << member.size << ")";
++ break;
++ case QShaderDescription::VariableType::Vec2:
++ qDebug() << "Updating" << member.name << "with vec2 data: " << value.constData<float>()[0]
++ << ", " << value.constData<float>()[1] << " (offset: " << member.offset
++ << ", size: " << member.size + arrayOffset << ")";
++ break;
++ case QShaderDescription::VariableType::Vec3:
++ qDebug() << "Updating" << member.name << "with vec3 data: " << value.constData<float>()[0]
++ << ", " << value.constData<float>()[1] << ", " << value.constData<float>()[2]
++ << " (offset: " << member.offset + arrayOffset << ", size: " << member.size << ")";
++ break;
++ case QShaderDescription::VariableType::Vec4:
++ qDebug() << "Updating" << member.name << "with vec4 data: " << value.constData<float>()[0]
++ << ", " << value.constData<float>()[1] << ", " << value.constData<float>()[2]
++ << ", " << value.constData<float>()[3] << " (offset: " << member.offset + arrayOffset
++ << ", size: " << member.size << ")";
++ break;
++ default:
++ qDebug() << "Updating" << member.name << "with data: " << value.constData<char>();
++ break;
++ }
++ }
++ //*/
++
++ inline void uploadDataToUBO(const QByteArray rawData,
++ const PipelineUBOSet::MultiUBOBufferWithBindingAndBlockSize *ubo,
++ const RHIShader::UBO_Member &member,
++ size_t distanceToCommand, int arrayOffset = 0)
++ {
++ const HRHIBuffer &buffer = ubo->bufferForCommand(distanceToCommand);
++ const size_t localOffset = ubo->localOffsetInBufferForCommand(distanceToCommand);
++ buffer->update(rawData, int(localOffset) + member.blockVariable.offset + arrayOffset);
++ }
++
++ QByteArray rawDataForUniformValue(const QShaderDescription::BlockVariable &blockVariable,
++ const UniformValue &value,
++ bool requiresCopy)
++ {
++ const QByteArray rawData = requiresCopy
++ ? QByteArray(value.constData<char>(), std::min(value.byteSize(), blockVariable.size))
++ : QByteArray::fromRawData(value.constData<char>(), std::min(value.byteSize(), blockVariable.size));
++ const int matrixStride = blockVariable.matrixStride;
++ int arrayStride = blockVariable.arrayStride;
++ const int firstArrayDim = !blockVariable.arrayDims.empty() ? blockVariable.arrayDims.first() : 0;
++
++ if (blockVariable.arrayDims.size() > 1)
++ qCWarning(Backend) << "Multi Dimension arrays not handled yet";
++
++ if (arrayStride != 0 && matrixStride != 0)
++ qCWarning(Backend) << "Arrays of matrices not handled yet";
++
++ // Special cases for arrays
++ if (firstArrayDim > 0) {
++ // It appears that for a float myArray[8]; stride is reported as being
++ // 0 but expected size 128 bytes which means 16 bytes per float
++ // In that case we compute the arrayStride ourselves
++ if (arrayStride == 0)
++ arrayStride = blockVariable.size / firstArrayDim;
++ if (arrayStride != 0) {
++ QByteArray newRawData(firstArrayDim * arrayStride, '\0');
++ const int byteSizePerEntry = value.elementByteSize();
++ for (int i = 0, m = std::min(firstArrayDim, value.elementCount()); i < m; ++i) {
++ std::memcpy(newRawData.data() + i * arrayStride,
++ rawData.constData() + i * byteSizePerEntry,
++ byteSizePerEntry);
++ }
++ return newRawData;
++ }
++ }
++
++ // Special cases for matrices which might have to be aligned to a vec4 stride
++ if (matrixStride != 0 && value.byteSize() % matrixStride != 0) {
++ // Find number of rows
++ const int rows = blockVariable.size / matrixStride;
++ QByteArray newRawData = QByteArray(rows * matrixStride, '\0');
++ const int dataSizePerRow = value.byteSize() / rows;
++ for (int i = 0; i < rows; ++i) {
++ std::memcpy(newRawData.data() + i * matrixStride,
++ rawData.constData() + i * dataSizePerRow, dataSizePerRow);
++ }
++ return newRawData;
++ }
++
++ // Note: we currently don't handle array of matrices
++
++ return rawData;
++ }
++
++ void uploadUniform(const PackUniformHash &uniforms,
++ const PipelineUBOSet::MultiUBOBufferWithBindingAndBlockSize *ubo,
++ const RHIShader::UBO_Member &member,
++ size_t distanceToCommand, int arrayOffset = 0)
++ {
++ if (!uniforms.contains(member.nameId))
++ return;
++
++ const UniformValue &value = uniforms.value(member.nameId);
++ // Textures / Images / Buffers can't be UBO members
++ if (value.valueType() != UniformValue::ScalarValue)
++ return;
++
++ // We can avoid the copies since the raw data copes from the UniformValue
++ // which remain alive until the frame has been submitted
++ const bool requiresCopy = false;
++ const QByteArray rawData = rawDataForUniformValue(member.blockVariable,
++ value,
++ requiresCopy);
++ uploadDataToUBO(rawData, ubo, member, distanceToCommand, arrayOffset);
++
++ // printUpload(value, member.blockVariable, arrayOffset);
++ }
++
++ } // anonymous
++
++ void PipelineUBOSet::uploadShaderDataProperty(const ShaderData *shaderData,
++ const PipelineUBOSet::MultiUBOBufferWithBindingAndBlockSize *ubo,
++ const RHIShader::UBO_Member &uboMemberInstance,
++ size_t distanceToCommand, int arrayOffset)
++ {
++ const std::vector<RHIShader::UBO_Member> &structMembers = uboMemberInstance.structMembers;
++ const QHash<QString, ShaderData::PropertyValue> &properties = shaderData->properties();
++ const int structBaseOffset = uboMemberInstance.blockVariable.offset;
++ for (const RHIShader::UBO_Member &member : structMembers) {
++ // TO DO: Use nameIds instead
++ const auto it = properties.find(member.blockVariable.name);
++ if (it != properties.end()) {
++ const ShaderData::PropertyValue &value = *it;
++ if (value.isNode) {
++ // Nested ShaderData
++ const ShaderData *child = m_nodeManagers->shaderDataManager()->lookupResource(value.value.value<Qt3DCore::QNodeId>());
++ if (child)
++ uploadShaderDataProperty(child, ubo, member,
++ distanceToCommand,
++ structBaseOffset + arrayOffset);
++ continue;
++ }
++ if (value.isTransformed) {
++ // TO DO: Handle this
++ qWarning() << "ShaderData transformed properties not handled yet";
++ // QVariant transformedValue = shaderData->getTransformedProperty(&value, viewMatrix);
++ }
++
++ // Value is a Scalar or a Scalar Array
++ const UniformValue v = UniformValue::fromVariant(value.value);
++ Q_ASSERT(v.valueType() == UniformValue::ScalarValue);
++
++ // We have to make a copy here
++ const bool requiresCopy = true;
++ const QByteArray rawData = rawDataForUniformValue(member.blockVariable,
++ v,
++ requiresCopy);
++ uploadDataToUBO(rawData, ubo, member, distanceToCommand, structBaseOffset + arrayOffset);
++
++ // printUpload(v, member.blockVariable, structBaseOffset + arrayOffset);
++ }
++ }
++ }
++
++ void PipelineUBOSet::uploadUBOsForCommand(const RenderCommand &command,
++ const size_t distanceToCommand)
++ {
++ Q_ASSERT(m_nodeManagers);
++ RHIShader *shader = command.m_rhiShader;
++ if (!shader)
++ return;
++
++ {
++ const HRHIBuffer &commandUBOBuffer = m_commandsUBO.bufferForCommand(distanceToCommand);
++ const size_t localOffset = m_commandsUBO.localOffsetInBufferForCommand(distanceToCommand);
++ commandUBOBuffer->update(QByteArray::fromRawData(
++ reinterpret_cast<const char *>(&command.m_commandUBO),
++ sizeof(CommandUBO)),
++ int(localOffset));
++ }
++
++ const std::vector<RHIShader::UBO_Block> &uboBlocks = shader->uboBlocks();
++ const ShaderParameterPack ¶meterPack = command.m_parameterPack;
++ const PackUniformHash &uniforms = parameterPack.uniforms();
++
++ // Update Buffer CPU side data based on uniforms being set
++
++ auto findMaterialUBOForBlock = [this] (const RHIShader::UBO_Block &uboBlock)
++ -> PipelineUBOSet::MultiUBOBufferWithBindingAndBlockSize* {
++ auto it = std::find_if(m_materialsUBOs.begin(), m_materialsUBOs.end(),
++ [&uboBlock] (const PipelineUBOSet::MultiUBOBufferWithBindingAndBlockSize &materialUBO) {
++ return materialUBO.binding == uboBlock.block.m_binding;
++ });
++
++ if (it == m_materialsUBOs.end())
++ return nullptr;
++ return &*it;
++ };
++
++ auto findUboBlockForBinding = [&uboBlocks] (int blockBinding) -> const RHIShader::UBO_Block * {
++ auto it = std::find_if(uboBlocks.begin(), uboBlocks.end(), [&blockBinding] (const RHIShader::UBO_Block &block) {
++ return block.block.m_binding == blockBinding;
++ });
++ if (it == uboBlocks.end())
++ return nullptr;
++ return &*it;
++ };
++
++ // Scalar / Texture Parameter to UBO
++ for (const RHIShader::UBO_Block &uboBlock : uboBlocks) {
++
++ // No point in trying to update Bindings 0 or 1 which are reserved
++ // for Qt3D default uniforms
++ const ShaderUniformBlock &block = uboBlock.block;
++ if (block.m_binding <= 1)
++ continue;
++
++ // Update UBO with uniform value
++ const PipelineUBOSet::MultiUBOBufferWithBindingAndBlockSize *ubo = findMaterialUBOForBlock(uboBlock);
++ if (ubo == nullptr)
++ continue;
++
++ for (const RHIShader::UBO_Member &member : std::as_const(uboBlock.members)) {
++ const QShaderDescription::BlockVariable &blockVariable = member.blockVariable;
++
++ // Array
++ if (!blockVariable.arrayDims.empty()) {
++ if (!blockVariable.structMembers.empty()) { // Array of structs
++ // we treat structMembers as arrayMembers when we are dealing with an array of structs´
++ const size_t arr0 = size_t(blockVariable.arrayDims[0]);
++ const size_t m = std::max(arr0, member.structMembers.size());
++ for (size_t i = 0; i < m; ++i) {
++ const RHIShader::UBO_Member &arrayMember = member.structMembers[i];
++ for (const RHIShader::UBO_Member &arrayStructMember : arrayMember.structMembers) {
++ uploadUniform(uniforms, ubo,
++ arrayStructMember,
++ distanceToCommand,
++ int(i * blockVariable.size / arr0));
++ }
++ }
++ } else { // Array of scalars
++ uploadUniform(uniforms, ubo,
++ member, distanceToCommand);
++ }
++ } else {
++ if (!blockVariable.structMembers.empty()) { // Struct
++ for (const RHIShader::UBO_Member &structMember : member.structMembers) {
++ uploadUniform(uniforms, ubo,
++ structMember,
++ distanceToCommand);
++ }
++ } else { // Scalar
++ uploadUniform(uniforms, ubo,
++ member, distanceToCommand);
++ }
++ }
++ }
++ }
++
++ // User provided UBO
++ // Since we create per Material UBO, if we directly provide a UBO as a parameter
++ // we will have to copy content of the parameter's UBO into the Material UBO at the
++ // property offset
++ for (const BlockToUBO &ubo : parameterPack.uniformBuffers()) {
++ // Find RHIShader::UBO_Block for UBO
++ const RHIShader::UBO_Block *block = findUboBlockForBinding(ubo.m_bindingIndex);
++ if (block == nullptr)
++ continue;
++ // Find Material UBO for Block
++ PipelineUBOSet::MultiUBOBufferWithBindingAndBlockSize *materialsUBO = findMaterialUBOForBlock(*block);
++ if (materialsUBO == nullptr)
++ continue;
++
++ // Copy content of UBO buffer to materialUBO at offset
++ Buffer *uboBuffer = m_nodeManagers->bufferManager()->lookupResource(ubo.m_bufferID);
++ if (!uboBuffer)
++ continue;
++
++ const HRHIBuffer &materialBuffer = materialsUBO->bufferForCommand(distanceToCommand);
++ const size_t localOffsetIntoBuffer = materialsUBO->localOffsetInBufferForCommand(distanceToCommand);
++ materialBuffer->update(uboBuffer->data(), int(localOffsetIntoBuffer));
++ }
++
++ // ShaderData -> convenience for filling a struct member of a UBO
++ // Note: we could use setDefaultUniformBlockShaderDataValue to unpack all values as individual uniforms
++ // but setting the whole ShaderData properties at once is a lot more efficient
++ for (const ShaderDataForUBO &shaderDataForUbo : parameterPack.shaderDatasForUBOs()) {
++ // Find ShaderData backend
++ const ShaderData *shaderData = m_nodeManagers->shaderDataManager()->lookupResource(shaderDataForUbo.m_shaderDataID);
++ if (shaderData == nullptr)
++ continue;
++ // Find RHIShader::UBO_Block for ShaderData
++ const RHIShader::UBO_Block *block = findUboBlockForBinding(shaderDataForUbo.m_bindingIndex);
++ if (block == nullptr)
++ continue;
++ // Find Material UBO for Block
++ PipelineUBOSet::MultiUBOBufferWithBindingAndBlockSize *materialsUBO = findMaterialUBOForBlock(*block);
++ if (materialsUBO == nullptr)
++ continue;
++
++ // Upload ShaderData property that match members of each UBO block instance
++ for (const RHIShader::UBO_Member &uboInstance : std::as_const(block->members)) {
++ uploadShaderDataProperty(shaderData, materialsUBO,
++ uboInstance, distanceToCommand);
++ }
++ }
++
++ // Note: There's nothing to do for SSBO as those are directly uploaded to the GPU, no extracting
++ // required
++ }
++
++ HRHIBuffer PipelineUBOSet::MultiUBOBufferWithBindingAndBlockSize::bufferForCommand(size_t distanceToCommand) const
++ {
++ const size_t uboIdx = distanceToCommand / commandsPerUBO;
++ return buffers[uboIdx];
++ }
++
++ size_t PipelineUBOSet::MultiUBOBufferWithBindingAndBlockSize::localOffsetInBufferForCommand(size_t distanceToCommand) const
++ {
++ return (distanceToCommand % commandsPerUBO) * alignedBlockSize;
++ }
++
++ } // Rhi
++
++ } // Render
++
++ } // Qt3DRender
++
++ QT_END_NAMESPACE