1// Copyright (C) 2020 Klaralvdalens Datakonsult AB (KDAB).
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "pipelineuboset_p.h"
5#include <rendercommand_p.h>
6#include <renderview_p.h>
7#include <rhibuffer_p.h>
8#include <shaderparameterpack_p.h>
9#include <shadervariables_p.h>
10#include <rhigraphicspipeline_p.h>
11#include <rhiresourcemanagers_p.h>
12#include <submissioncontext_p.h>
13#include <rhi/qrhi.h>
14#include <Qt3DRender/private/nodemanagers_p.h>
15#include <Qt3DRender/private/buffermanager_p.h>
16#include <Qt3DRender/private/stringtoint_p.h>
17#include <Qt3DRender/private/buffer_p.h>
18#include <Qt3DCore/private/vector_helper_p.h>
19
20QT_BEGIN_NAMESPACE
21
22namespace Qt3DRender {
23
24namespace Render {
25
26namespace Rhi {
27
28namespace {
29// This is the minimum max UBO size requirements with GL
30// therefore safe to assume that is also the baseline for other graphics APIs
31constexpr size_t MaxUBOByteSize = 16384;
32}
33
34PipelineUBOSet::PipelineUBOSet()
35{
36}
37
38PipelineUBOSet::~PipelineUBOSet()
39{
40 // Resource release is performed by RHIGraphicsPipeline::cleanup()
41}
42
43void PipelineUBOSet::clear()
44{
45 m_renderCommands.clear();
46}
47
48void PipelineUBOSet::addRenderCommand(const RenderCommand &cmd)
49{
50 m_renderCommands.push_back(x: &cmd);
51}
52
53void PipelineUBOSet::setResourceManager(RHIResourceManagers *manager)
54{
55 m_resourceManagers = manager;
56}
57
58void PipelineUBOSet::setNodeManagers(NodeManagers *managers)
59{
60 m_nodeManagers = managers;
61}
62
63void PipelineUBOSet::initializeLayout(SubmissionContext *ctx, RHIShader *shader)
64{
65 // We should only be called with a clean Pipeline
66 Q_ASSERT(m_rvUBO.buffer.isNull());
67
68 m_rvUBO.binding = 0;
69 m_rvUBO.blockSize = sizeof(RenderViewUBO);
70
71 m_commandsUBO.binding = 1;
72 m_commandsUBO.blockSize = sizeof(CommandUBO);
73 m_commandsUBO.alignedBlockSize = ctx->rhi()->ubufAligned(v: (m_commandsUBO.blockSize));
74 m_commandsUBO.alignment = size_t(ctx->rhi()->ubufAlignment());
75 m_commandsUBO.commandsPerUBO = MaxUBOByteSize / m_commandsUBO.alignedBlockSize;
76
77 // For UBO, we try to create a single large UBO that will contain frontend
78 // Qt3D UBO data at various offsets
79 const std::vector<ShaderUniformBlock> &uniformBlocks = shader->uniformBlocks();
80 for (const ShaderUniformBlock &block : uniformBlocks) {
81 if (block.m_binding > 1) { // Binding 0 and 1 are for RV and Command UBOs
82 const size_t alignedBlockSize = size_t(ctx->rhi()->ubufAligned(v: block.m_size));
83 m_materialsUBOs.push_back(
84 x: { .binding: block.m_binding,
85 .blockSize: block.m_size,
86 .alignedBlockSize: alignedBlockSize,
87 .alignment: size_t(ctx->rhi()->ubufAlignment()),
88 .commandsPerUBO: MaxUBOByteSize / alignedBlockSize,
89 .buffers: {} });
90 }
91 }
92
93 // For SSBO, we have one SSBO per frontend Qt3D SSBO
94 m_storageBlocks = shader->storageBlocks();
95}
96
97void PipelineUBOSet::releaseResources()
98{
99 Q_ASSERT(m_resourceManagers);
100 RHIBufferManager *bufferManager = m_resourceManagers->rhiBufferManager();
101
102 bufferManager->release(handle: m_rvUBO.buffer);
103
104 for (const HRHIBuffer &hBuf : m_commandsUBO.buffers)
105 bufferManager->release(handle: hBuf);
106
107 m_rvUBO = {};
108 m_commandsUBO = {};
109
110 if (!m_materialsUBOs.empty()) {
111 for (const MultiUBOBufferWithBindingAndBlockSize &ubo : m_materialsUBOs) {
112 for (const HRHIBuffer &hBuf : ubo.buffers)
113 bufferManager->release(handle: hBuf);
114 }
115 m_materialsUBOs.clear();
116 }
117}
118
119bool PipelineUBOSet::allocateUBOs(SubmissionContext *ctx)
120{
121 RHIBufferManager *bufferManager = m_resourceManagers->rhiBufferManager();
122 // Note: RHIBuffer only releases/recreates when it needs more size than what it
123 // can currently hold
124 Q_ASSERT(m_resourceManagers);
125 const bool dynamic = true;
126 const size_t commandCount = std::max(a: m_renderCommands.size(), b: size_t(1));
127
128 if (m_rvUBO.buffer.isNull())
129 m_rvUBO.buffer = bufferManager->allocateResource();
130
131 // RHIBuffer only reallocates if size is < than required
132 m_rvUBO.buffer->allocate(data: QByteArray(m_rvUBO.blockSize, '\0'), dynamic);
133 // Binding buffer ensure underlying RHI resource is created
134 m_rvUBO.buffer->bind(ctx, t: RHIBuffer::UniformBuffer);
135
136 auto allocateMultiUBOsForCommands = [&] (MultiUBOBufferWithBindingAndBlockSize &ubo) {
137 // Round up
138 const size_t uboCount = std::ceil(x: float(commandCount) / ubo.commandsPerUBO);
139
140 if (ubo.buffers.size() < uboCount)
141 ubo.buffers.resize(new_size: uboCount);
142
143 for (HRHIBuffer &buf : ubo.buffers) {
144 if (buf.isNull())
145 buf = bufferManager->allocateResource();
146 // We need to take into account any minimum alignment requirement for dynamic offsets
147 buf->allocate(data: QByteArray(MaxUBOByteSize, '\0'), dynamic);
148 buf->bind(ctx, t: RHIBuffer::UniformBuffer);
149 }
150 };
151
152 // Commands UBOs
153 allocateMultiUBOsForCommands(m_commandsUBO);
154
155 // Material UBOs
156 for (MultiUBOBufferWithBindingAndBlockSize &ubo : m_materialsUBOs) {
157 if (ubo.binding > 1) // Binding 0 and 1 are for RV and Command UBOs
158 allocateMultiUBOsForCommands(ubo);
159 }
160
161 // SSBO are using RHIBuffer directly, nothing we need to handle ourselves
162
163 return true;
164}
165
166size_t PipelineUBOSet::distanceToCommand(const RenderCommand &cmd) const
167{
168 const auto it = std::find(first: m_renderCommands.begin(), last: m_renderCommands.end(),
169 val: &cmd);
170 if (Q_UNLIKELY(it == m_renderCommands.end())) {
171 qCWarning(Backend) << "Command not found in UBOSet";
172 return 0;
173 }
174 return std::distance(first: m_renderCommands.begin(), last: it);
175}
176
177std::vector<QRhiCommandBuffer::DynamicOffset> PipelineUBOSet::offsets(const RenderCommand &cmd) const
178{
179 std::vector<QRhiCommandBuffer::DynamicOffset> offsets;
180 offsets.reserve(n: 1 + m_materialsUBOs.size());
181
182 // RenderCommand offset
183 // binding, offset
184 const size_t dToCmd = distanceToCommand(cmd);
185 {
186 // Compute offset relative to the select UBO in the subset
187 const auto localOffset = m_commandsUBO.localOffsetInBufferForCommand(distanceToCommand: dToCmd);
188 offsets.push_back(x: {1, quint32(localOffset)});
189 }
190
191 for (const MultiUBOBufferWithBindingAndBlockSize &materialUBO : m_materialsUBOs) {
192 // Compute offset relative to the select UBO in the subset
193 const size_t localOffset = materialUBO.localOffsetInBufferForCommand(distanceToCommand: dToCmd);
194 offsets.push_back(x: {materialUBO.binding, quint32(localOffset)});
195 }
196
197 return offsets;
198}
199
200// Note: this function is currently the same as resourceBindings However, once
201// https://codereview.qt-project.org/c/qt/qtbase/+/307472 lands we can get rid
202// of passing the RenderCommand here as well as having to provide the actual
203// Resources in that function which means that one will be about providing the
204// Pipeline resourceLayout only and resourceBindings will be about providing
205// the actual resources for a RenderCommand
206std::vector<QRhiShaderResourceBinding> PipelineUBOSet::resourceLayout(const RHIShader *shader)
207{
208 const QRhiShaderResourceBinding::StageFlags stages = QRhiShaderResourceBinding::VertexStage|QRhiShaderResourceBinding::FragmentStage;
209 std::vector<QRhiShaderResourceBinding> bindings = {
210 QRhiShaderResourceBinding::uniformBuffer(binding: 0, stage: stages, buf: nullptr),
211 QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(binding: 1, stage: stages, buf: nullptr, size: sizeof(CommandUBO))
212 };
213
214 // TO DO: Handle Parameters that directly define a UBO or SSBO
215 // const auto &blockToUBOs = parameterPack.uniformBuffers();
216 // const auto &blockToSSBOs = parameterPack.shaderStorageBuffers();
217
218 // Create additional empty UBO Buffer for UBO with binding point > 1 (since
219 // we assume 0 and 1 and for Qt3D standard values)
220 for (const MultiUBOBufferWithBindingAndBlockSize &ubo : m_materialsUBOs)
221 bindings.push_back(
222 x: QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(
223 binding: ubo.binding, stage: stages, buf: nullptr, size: ubo.blockSize));
224
225 // Samplers
226 for (const ShaderAttribute &samplerAttribute : shader->samplers()) {
227 bindings.push_back(x: QRhiShaderResourceBinding::sampledTexture(
228 binding: samplerAttribute.m_location,
229 stage: stages, tex: nullptr, sampler: nullptr));
230 }
231
232 // SSBO
233 for (const ShaderStorageBlock &b : m_storageBlocks) {
234 bindings.push_back(x: QRhiShaderResourceBinding::bufferLoadStore(binding: b.m_binding,
235 stage: stages|QRhiShaderResourceBinding::ComputeStage,
236 buf: nullptr));
237 }
238
239 return bindings;
240}
241
242std::vector<QRhiShaderResourceBinding> PipelineUBOSet::resourceBindings(const RenderCommand &command)
243{
244 RHITextureManager *textureManager = m_resourceManagers->rhiTextureManager();
245 RHIShader *shader = command.m_rhiShader;
246 const QRhiShaderResourceBinding::StageFlags stages = QRhiShaderResourceBinding::VertexStage|QRhiShaderResourceBinding::FragmentStage;
247 std::vector<QRhiShaderResourceBinding> bindings = {
248 QRhiShaderResourceBinding::uniformBuffer(binding: 0, stage: stages, buf: m_rvUBO.buffer->rhiBuffer()),
249 };
250
251 const size_t dToCmd = distanceToCommand(cmd: command);
252
253 // CommandUBO
254 {
255 const HRHIBuffer &commandUBO = m_commandsUBO.bufferForCommand(distanceToCommand: dToCmd);
256 bindings.push_back(
257 x: QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(binding: 1,
258 stage: stages,
259 buf: commandUBO->rhiBuffer(),
260 size: int(m_commandsUBO.alignedBlockSize)));
261 }
262
263 // Create additional empty UBO Buffer for UBO with binding point > 1 (since
264 // we assume 0 and 1 and for Qt3D standard values)
265 // Note: if a Parameter provides a UBO Buffer, its content will have been
266 // copied at right offset into the matching materials UBO
267 for (const MultiUBOBufferWithBindingAndBlockSize &ubo : m_materialsUBOs) {
268 const HRHIBuffer &materialUBO = ubo.bufferForCommand(distanceToCommand: dToCmd);
269 bindings.push_back(
270 x: QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(
271 binding: ubo.binding, stage: stages, buf: materialUBO->rhiBuffer(), size: int(ubo.alignedBlockSize)));
272 }
273
274 // Samplers
275 const std::vector<ShaderAttribute> &shaderSamplers = shader->samplers();
276 std::vector<int> samplersSetIds;
277 for (const ShaderParameterPack::NamedResource &textureParameter : command.m_parameterPack.textures()) {
278 const HRHITexture &handle = textureManager->getOrAcquireHandle(id: textureParameter.nodeId);
279 const RHITexture *textureData = handle.data();
280
281 for (const ShaderAttribute &samplerAttribute : shaderSamplers) {
282 if (samplerAttribute.m_nameId == textureParameter.glslNameId) {
283 const auto rhiTexture = textureData->getRhiTexture();
284 const auto rhiSampler = textureData->getRhiSampler();
285 if (rhiTexture && rhiSampler) {
286 bindings.push_back(x: QRhiShaderResourceBinding::sampledTexture(
287 binding: samplerAttribute.m_location,
288 stage: stages, tex: rhiTexture, sampler: rhiSampler));
289 samplersSetIds.push_back(x: samplerAttribute.m_nameId);
290 }
291 }
292 }
293 }
294
295 // Some backends (Vulkan) expect the number of bindings to be exactly the
296 // same between the set used to create the pipeline in resourceLayout and
297 // the set used to draw with said pipeline generated here.
298 // This can be problematic if no parameter is provided for that resource
299 const std::vector<ShaderAttribute> unsetSamplers = [&] () {
300 std::vector<ShaderAttribute> unset;
301 for (const ShaderAttribute &attr : shaderSamplers) {
302 const bool matchingSetNameIdFound =
303 std::find_if(first: samplersSetIds.begin(),
304 last: samplersSetIds.end(),
305 pred: [&attr] (int nameId) {
306 return attr.m_nameId == nameId;
307 }) != samplersSetIds.end();
308 if (!matchingSetNameIdFound)
309 unset.push_back(x: attr);
310 }
311 return unset;
312 }();
313
314 // Generate an empty binding for unset samplers
315 const int envLightIrradianceNameId = StringToInt::lookupId(QStringLiteral("envLight_irradiance"));
316 const int envLightSpecularNameId = StringToInt::lookupId(QStringLiteral("envLight_specular"));
317 for (const ShaderAttribute &sampler : unsetSamplers) {
318 if (sampler.m_nameId != envLightSpecularNameId && sampler.m_nameId != envLightIrradianceNameId)
319 qCWarning(Backend) << "Sampler" << sampler.m_name << "wasn't set on material. Rendering might not work as expected";
320 }
321
322 // SSBO
323 for (const BlockToSSBO &ssbo : command.m_parameterPack.shaderStorageBuffers()) {
324 RHIBuffer *buffer = m_resourceManagers->rhiBufferManager()->lookupResource(id: ssbo.m_bufferID);
325 if (buffer) {
326 bindings.push_back(x: QRhiShaderResourceBinding::bufferLoadStore(
327 binding: ssbo.m_bindingIndex,
328 stage: stages|QRhiShaderResourceBinding::ComputeStage,
329 buf: buffer->rhiBuffer()));
330 }
331 }
332
333 return bindings;
334}
335
336void PipelineUBOSet::uploadUBOs(SubmissionContext *ctx, RenderView *rv)
337{
338 // Update UBO data for RV and RC data
339 m_rvUBO.buffer->update(data: QByteArray::fromRawData(data: reinterpret_cast<const char *>(rv->renderViewUBO()),
340 size: sizeof(RenderViewUBO)));
341 int distance = 0;
342 for (const RenderCommand *command : m_renderCommands) {
343 uploadUBOsForCommand(command: *command, distanceToCommand: distance);
344 ++distance;
345 }
346
347 // Trigger actual upload to GPU
348 m_rvUBO.buffer->bind(ctx, t: RHIBuffer::UniformBuffer);
349 for (const HRHIBuffer &ubo : m_commandsUBO.buffers)
350 ubo->bind(ctx, t: RHIBuffer::UniformBuffer);
351 for (const MultiUBOBufferWithBindingAndBlockSize &multiUbo : m_materialsUBOs) {
352 for (const HRHIBuffer &ubo : multiUbo.buffers)
353 ubo->bind(ctx, t: RHIBuffer::UniformBuffer);
354 }
355}
356
357namespace {
358/*
359void printUpload(const UniformValue &value, const QShaderDescription::BlockVariable &member, int arrayOffset)
360{
361 switch (member.type) {
362 case QShaderDescription::VariableType::Int:
363 qDebug() << "Updating" << member.name << "with int data: " << *value.constData<int>()
364 << " (offset: " << member.offset + arrayOffset << ", size: " << member.size << ")";
365 break;
366 case QShaderDescription::VariableType::Float:
367 qDebug() << "Updating" << member.name << "with float data: " << *value.constData<float>()
368 << " (offset: " << member.offset + arrayOffset << ", size: " << member.size << ")";
369 break;
370 case QShaderDescription::VariableType::Vec2:
371 qDebug() << "Updating" << member.name << "with vec2 data: " << value.constData<float>()[0]
372 << ", " << value.constData<float>()[1] << " (offset: " << member.offset
373 << ", size: " << member.size + arrayOffset << ")";
374 break;
375 case QShaderDescription::VariableType::Vec3:
376 qDebug() << "Updating" << member.name << "with vec3 data: " << value.constData<float>()[0]
377 << ", " << value.constData<float>()[1] << ", " << value.constData<float>()[2]
378 << " (offset: " << member.offset + arrayOffset << ", size: " << member.size << ")";
379 break;
380 case QShaderDescription::VariableType::Vec4:
381 qDebug() << "Updating" << member.name << "with vec4 data: " << value.constData<float>()[0]
382 << ", " << value.constData<float>()[1] << ", " << value.constData<float>()[2]
383 << ", " << value.constData<float>()[3] << " (offset: " << member.offset + arrayOffset
384 << ", size: " << member.size << ")";
385 break;
386 default:
387 qDebug() << "Updating" << member.name << "with data: " << value.constData<char>();
388 break;
389 }
390}
391//*/
392
393inline void uploadDataToUBO(const QByteArray rawData,
394 const PipelineUBOSet::MultiUBOBufferWithBindingAndBlockSize *ubo,
395 const RHIShader::UBO_Member &member,
396 size_t distanceToCommand, int arrayOffset = 0)
397{
398 const HRHIBuffer &buffer = ubo->bufferForCommand(distanceToCommand);
399 const size_t localOffset = ubo->localOffsetInBufferForCommand(distanceToCommand);
400 buffer->update(data: rawData, offset: int(localOffset) + member.blockVariable.offset + arrayOffset);
401}
402
403QByteArray rawDataForUniformValue(const QShaderDescription::BlockVariable &blockVariable,
404 const UniformValue &value,
405 bool requiresCopy)
406{
407 const QByteArray rawData = requiresCopy
408 ? QByteArray(value.constData<char>(), std::min(a: value.byteSize(), b: blockVariable.size))
409 : QByteArray::fromRawData(data: value.constData<char>(), size: std::min(a: value.byteSize(), b: blockVariable.size));
410 const int matrixStride = blockVariable.matrixStride;
411 int arrayStride = blockVariable.arrayStride;
412 const int firstArrayDim = !blockVariable.arrayDims.empty() ? blockVariable.arrayDims.first() : 0;
413
414 if (blockVariable.arrayDims.size() > 1)
415 qCWarning(Backend) << "Multi Dimension arrays not handled yet";
416
417 if (arrayStride != 0 && matrixStride != 0)
418 qCWarning(Backend) << "Arrays of matrices not handled yet";
419
420 // Special cases for arrays
421 if (firstArrayDim > 0) {
422 // It appears that for a float myArray[8]; stride is reported as being
423 // 0 but expected size 128 bytes which means 16 bytes per float
424 // In that case we compute the arrayStride ourselves
425 if (arrayStride == 0)
426 arrayStride = blockVariable.size / firstArrayDim;
427 if (arrayStride != 0) {
428 QByteArray newRawData(firstArrayDim * arrayStride, '\0');
429 const int byteSizePerEntry = value.elementByteSize();
430 for (int i = 0, m = std::min(a: firstArrayDim, b: value.elementCount()); i < m; ++i) {
431 std::memcpy(dest: newRawData.data() + i * arrayStride,
432 src: rawData.constData() + i * byteSizePerEntry,
433 n: byteSizePerEntry);
434 }
435 return newRawData;
436 }
437 }
438
439 // Special cases for matrices which might have to be aligned to a vec4 stride
440 if (matrixStride != 0 && value.byteSize() % matrixStride != 0) {
441 // Find number of rows
442 const int rows = blockVariable.size / matrixStride;
443 QByteArray newRawData = QByteArray(rows * matrixStride, '\0');
444 const int dataSizePerRow = value.byteSize() / rows;
445 for (int i = 0; i < rows; ++i) {
446 std::memcpy(dest: newRawData.data() + i * matrixStride,
447 src: rawData.constData() + i * dataSizePerRow, n: dataSizePerRow);
448 }
449 return newRawData;
450 }
451
452 // Note: we currently don't handle array of matrices
453
454 return rawData;
455}
456
457void uploadUniform(const PackUniformHash &uniforms,
458 const PipelineUBOSet::MultiUBOBufferWithBindingAndBlockSize *ubo,
459 const RHIShader::UBO_Member &member,
460 size_t distanceToCommand, int arrayOffset = 0)
461{
462 if (!uniforms.contains(key: member.nameId))
463 return;
464
465 const UniformValue &value = uniforms.value(key: member.nameId);
466 // Textures / Images / Buffers can't be UBO members
467 if (value.valueType() != UniformValue::ScalarValue)
468 return;
469
470 // We can avoid the copies since the raw data copes from the UniformValue
471 // which remain alive until the frame has been submitted
472 const bool requiresCopy = false;
473 const QByteArray rawData = rawDataForUniformValue(blockVariable: member.blockVariable,
474 value,
475 requiresCopy);
476 uploadDataToUBO(rawData, ubo, member, distanceToCommand, arrayOffset);
477
478 // printUpload(value, member.blockVariable, arrayOffset);
479}
480
481} // anonymous
482
483void PipelineUBOSet::uploadShaderDataProperty(const ShaderData *shaderData,
484 const PipelineUBOSet::MultiUBOBufferWithBindingAndBlockSize *ubo,
485 const RHIShader::UBO_Member &uboMemberInstance,
486 size_t distanceToCommand, int arrayOffset)
487{
488 const std::vector<RHIShader::UBO_Member> &structMembers = uboMemberInstance.structMembers;
489 const QHash<QString, ShaderData::PropertyValue> &properties = shaderData->properties();
490 const int structBaseOffset = uboMemberInstance.blockVariable.offset;
491 for (const RHIShader::UBO_Member &member : structMembers) {
492 // TO DO: Use nameIds instead
493 const auto it = properties.find(key: member.blockVariable.name);
494 if (it != properties.end()) {
495 const ShaderData::PropertyValue &value = *it;
496 if (value.isNode) {
497 // Nested ShaderData
498 const ShaderData *child = m_nodeManagers->shaderDataManager()->lookupResource(id: value.value.value<Qt3DCore::QNodeId>());
499 if (child)
500 uploadShaderDataProperty(shaderData: child, ubo, uboMemberInstance: member,
501 distanceToCommand,
502 arrayOffset: structBaseOffset + arrayOffset);
503 continue;
504 }
505 if (value.isTransformed) {
506 // TO DO: Handle this
507 qWarning() << "ShaderData transformed properties not handled yet";
508 // QVariant transformedValue = shaderData->getTransformedProperty(&value, viewMatrix);
509 }
510
511 // Value is a Scalar or a Scalar Array
512 const UniformValue v = UniformValue::fromVariant(variant: value.value);
513 Q_ASSERT(v.valueType() == UniformValue::ScalarValue);
514
515 // We have to make a copy here
516 const bool requiresCopy = true;
517 const QByteArray rawData = rawDataForUniformValue(blockVariable: member.blockVariable,
518 value: v,
519 requiresCopy);
520 uploadDataToUBO(rawData, ubo, member, distanceToCommand, arrayOffset: structBaseOffset + arrayOffset);
521
522 // printUpload(v, member.blockVariable, structBaseOffset + arrayOffset);
523 }
524 }
525}
526
527void PipelineUBOSet::uploadUBOsForCommand(const RenderCommand &command,
528 const size_t distanceToCommand)
529{
530 Q_ASSERT(m_nodeManagers);
531 RHIShader *shader = command.m_rhiShader;
532 if (!shader)
533 return;
534
535 {
536 const HRHIBuffer &commandUBOBuffer = m_commandsUBO.bufferForCommand(distanceToCommand);
537 const size_t localOffset = m_commandsUBO.localOffsetInBufferForCommand(distanceToCommand);
538 commandUBOBuffer->update(data: QByteArray::fromRawData(
539 data: reinterpret_cast<const char *>(&command.m_commandUBO),
540 size: sizeof(CommandUBO)),
541 offset: int(localOffset));
542 }
543
544 const std::vector<RHIShader::UBO_Block> &uboBlocks = shader->uboBlocks();
545 const ShaderParameterPack &parameterPack = command.m_parameterPack;
546 const PackUniformHash &uniforms = parameterPack.uniforms();
547
548 // Update Buffer CPU side data based on uniforms being set
549
550 auto findMaterialUBOForBlock = [this] (const RHIShader::UBO_Block &uboBlock)
551 -> PipelineUBOSet::MultiUBOBufferWithBindingAndBlockSize* {
552 auto it = std::find_if(first: m_materialsUBOs.begin(), last: m_materialsUBOs.end(),
553 pred: [&uboBlock] (const PipelineUBOSet::MultiUBOBufferWithBindingAndBlockSize &materialUBO) {
554 return materialUBO.binding == uboBlock.block.m_binding;
555 });
556
557 if (it == m_materialsUBOs.end())
558 return nullptr;
559 return &*it;
560 };
561
562 auto findUboBlockForBinding = [&uboBlocks] (int blockBinding) -> const RHIShader::UBO_Block * {
563 auto it = std::find_if(first: uboBlocks.begin(), last: uboBlocks.end(), pred: [&blockBinding] (const RHIShader::UBO_Block &block) {
564 return block.block.m_binding == blockBinding;
565 });
566 if (it == uboBlocks.end())
567 return nullptr;
568 return &*it;
569 };
570
571 // Scalar / Texture Parameter to UBO
572 for (const RHIShader::UBO_Block &uboBlock : uboBlocks) {
573
574 // No point in trying to update Bindings 0 or 1 which are reserved
575 // for Qt3D default uniforms
576 const ShaderUniformBlock &block = uboBlock.block;
577 if (block.m_binding <= 1)
578 continue;
579
580 // Update UBO with uniform value
581 const PipelineUBOSet::MultiUBOBufferWithBindingAndBlockSize *ubo = findMaterialUBOForBlock(uboBlock);
582 if (ubo == nullptr)
583 continue;
584
585 for (const RHIShader::UBO_Member &member : std::as_const(t: uboBlock.members)) {
586 const QShaderDescription::BlockVariable &blockVariable = member.blockVariable;
587
588 // Array
589 if (!blockVariable.arrayDims.empty()) {
590 if (!blockVariable.structMembers.empty()) { // Array of structs
591 // we treat structMembers as arrayMembers when we are dealing with an array of structs´
592 const size_t arr0 = size_t(blockVariable.arrayDims[0]);
593 const size_t m = std::max(a: arr0, b: member.structMembers.size());
594 for (size_t i = 0; i < m; ++i) {
595 const RHIShader::UBO_Member &arrayMember = member.structMembers[i];
596 for (const RHIShader::UBO_Member &arrayStructMember : arrayMember.structMembers) {
597 uploadUniform(uniforms, ubo,
598 member: arrayStructMember,
599 distanceToCommand,
600 arrayOffset: int(i * blockVariable.size / arr0));
601 }
602 }
603 } else { // Array of scalars
604 uploadUniform(uniforms, ubo,
605 member, distanceToCommand);
606 }
607 } else {
608 if (!blockVariable.structMembers.empty()) { // Struct
609 for (const RHIShader::UBO_Member &structMember : member.structMembers) {
610 uploadUniform(uniforms, ubo,
611 member: structMember,
612 distanceToCommand);
613 }
614 } else { // Scalar
615 uploadUniform(uniforms, ubo,
616 member, distanceToCommand);
617 }
618 }
619 }
620 }
621
622 // User provided UBO
623 // Since we create per Material UBO, if we directly provide a UBO as a parameter
624 // we will have to copy content of the parameter's UBO into the Material UBO at the
625 // property offset
626 for (const BlockToUBO &ubo : parameterPack.uniformBuffers()) {
627 // Find RHIShader::UBO_Block for UBO
628 const RHIShader::UBO_Block *block = findUboBlockForBinding(ubo.m_bindingIndex);
629 if (block == nullptr)
630 continue;
631 // Find Material UBO for Block
632 PipelineUBOSet::MultiUBOBufferWithBindingAndBlockSize *materialsUBO = findMaterialUBOForBlock(*block);
633 if (materialsUBO == nullptr)
634 continue;
635
636 // Copy content of UBO buffer to materialUBO at offset
637 Buffer *uboBuffer = m_nodeManagers->bufferManager()->lookupResource(id: ubo.m_bufferID);
638 if (!uboBuffer)
639 continue;
640
641 const HRHIBuffer &materialBuffer = materialsUBO->bufferForCommand(distanceToCommand);
642 const size_t localOffsetIntoBuffer = materialsUBO->localOffsetInBufferForCommand(distanceToCommand);
643 materialBuffer->update(data: uboBuffer->data(), offset: int(localOffsetIntoBuffer));
644 }
645
646 // ShaderData -> convenience for filling a struct member of a UBO
647 // Note: we could use setDefaultUniformBlockShaderDataValue to unpack all values as individual uniforms
648 // but setting the whole ShaderData properties at once is a lot more efficient
649 for (const ShaderDataForUBO &shaderDataForUbo : parameterPack.shaderDatasForUBOs()) {
650 // Find ShaderData backend
651 const ShaderData *shaderData = m_nodeManagers->shaderDataManager()->lookupResource(id: shaderDataForUbo.m_shaderDataID);
652 if (shaderData == nullptr)
653 continue;
654 // Find RHIShader::UBO_Block for ShaderData
655 const RHIShader::UBO_Block *block = findUboBlockForBinding(shaderDataForUbo.m_bindingIndex);
656 if (block == nullptr)
657 continue;
658 // Find Material UBO for Block
659 PipelineUBOSet::MultiUBOBufferWithBindingAndBlockSize *materialsUBO = findMaterialUBOForBlock(*block);
660 if (materialsUBO == nullptr)
661 continue;
662
663 // Upload ShaderData property that match members of each UBO block instance
664 for (const RHIShader::UBO_Member &uboInstance : std::as_const(t: block->members)) {
665 uploadShaderDataProperty(shaderData, ubo: materialsUBO,
666 uboMemberInstance: uboInstance, distanceToCommand);
667 }
668 }
669
670 // Note: There's nothing to do for SSBO as those are directly uploaded to the GPU, no extracting
671 // required
672}
673
674HRHIBuffer PipelineUBOSet::MultiUBOBufferWithBindingAndBlockSize::bufferForCommand(size_t distanceToCommand) const
675{
676 const size_t uboIdx = distanceToCommand / commandsPerUBO;
677 return buffers[uboIdx];
678}
679
680size_t PipelineUBOSet::MultiUBOBufferWithBindingAndBlockSize::localOffsetInBufferForCommand(size_t distanceToCommand) const
681{
682 return (distanceToCommand % commandsPerUBO) * alignedBlockSize;
683}
684
685} // Rhi
686
687} // Render
688
689} // Qt3DRender
690
691QT_END_NAMESPACE
692

source code of qt3d/src/plugins/renderers/rhi/renderer/pipelineuboset.cpp