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::ComputeStage|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 Q_ASSERT(stages & QRhiShaderResourceBinding::ComputeStage);
327 bindings.push_back(x: QRhiShaderResourceBinding::bufferLoadStore(
328 binding: ssbo.m_bindingIndex,
329 stage: stages,
330 buf: buffer->rhiBuffer()));
331 }
332 }
333
334 return bindings;
335}
336
337void PipelineUBOSet::uploadUBOs(SubmissionContext *ctx, RenderView *rv)
338{
339 // Update UBO data for RV and RC data
340 m_rvUBO.buffer->update(data: QByteArray::fromRawData(data: reinterpret_cast<const char *>(rv->renderViewUBO()),
341 size: sizeof(RenderViewUBO)));
342 int distance = 0;
343 for (const RenderCommand *command : m_renderCommands) {
344 uploadUBOsForCommand(command: *command, distanceToCommand: distance);
345 ++distance;
346 }
347
348 // Trigger actual upload to GPU
349 m_rvUBO.buffer->bind(ctx, t: RHIBuffer::UniformBuffer);
350 for (const HRHIBuffer &ubo : m_commandsUBO.buffers)
351 ubo->bind(ctx, t: RHIBuffer::UniformBuffer);
352 for (const MultiUBOBufferWithBindingAndBlockSize &multiUbo : m_materialsUBOs) {
353 for (const HRHIBuffer &ubo : multiUbo.buffers)
354 ubo->bind(ctx, t: RHIBuffer::UniformBuffer);
355 }
356}
357
358namespace {
359/*
360void printUpload(const UniformValue &value, const QShaderDescription::BlockVariable &member, int arrayOffset)
361{
362 switch (member.type) {
363 case QShaderDescription::VariableType::Int:
364 qDebug() << "Updating" << member.name << "with int data: " << *value.constData<int>()
365 << " (offset: " << member.offset + arrayOffset << ", size: " << member.size << ")";
366 break;
367 case QShaderDescription::VariableType::Float:
368 qDebug() << "Updating" << member.name << "with float data: " << *value.constData<float>()
369 << " (offset: " << member.offset + arrayOffset << ", size: " << member.size << ")";
370 break;
371 case QShaderDescription::VariableType::Vec2:
372 qDebug() << "Updating" << member.name << "with vec2 data: " << value.constData<float>()[0]
373 << ", " << value.constData<float>()[1] << " (offset: " << member.offset
374 << ", size: " << member.size + arrayOffset << ")";
375 break;
376 case QShaderDescription::VariableType::Vec3:
377 qDebug() << "Updating" << member.name << "with vec3 data: " << value.constData<float>()[0]
378 << ", " << value.constData<float>()[1] << ", " << value.constData<float>()[2]
379 << " (offset: " << member.offset + arrayOffset << ", size: " << member.size << ")";
380 break;
381 case QShaderDescription::VariableType::Vec4:
382 qDebug() << "Updating" << member.name << "with vec4 data: " << value.constData<float>()[0]
383 << ", " << value.constData<float>()[1] << ", " << value.constData<float>()[2]
384 << ", " << value.constData<float>()[3] << " (offset: " << member.offset + arrayOffset
385 << ", size: " << member.size << ")";
386 break;
387 default:
388 qDebug() << "Updating" << member.name << "with data: " << value.constData<char>();
389 break;
390 }
391}
392//*/
393
394inline void uploadDataToUBO(const QByteArray rawData,
395 const PipelineUBOSet::MultiUBOBufferWithBindingAndBlockSize *ubo,
396 const RHIShader::UBO_Member &member,
397 size_t distanceToCommand, int arrayOffset = 0)
398{
399 const HRHIBuffer &buffer = ubo->bufferForCommand(distanceToCommand);
400 const size_t localOffset = ubo->localOffsetInBufferForCommand(distanceToCommand);
401 buffer->update(data: rawData, offset: int(localOffset) + member.blockVariable.offset + arrayOffset);
402}
403
404QByteArray rawDataForUniformValue(const QShaderDescription::BlockVariable &blockVariable,
405 const UniformValue &value,
406 bool requiresCopy)
407{
408 const QByteArray rawData = requiresCopy
409 ? QByteArray(value.constData<char>(), std::min(a: value.byteSize(), b: blockVariable.size))
410 : QByteArray::fromRawData(data: value.constData<char>(), size: std::min(a: value.byteSize(), b: blockVariable.size));
411 const int matrixStride = blockVariable.matrixStride;
412 int arrayStride = blockVariable.arrayStride;
413 const int firstArrayDim = !blockVariable.arrayDims.empty() ? blockVariable.arrayDims.first() : 0;
414
415 if (blockVariable.arrayDims.size() > 1)
416 qCWarning(Backend) << "Multi Dimension arrays not handled yet";
417
418 if (arrayStride != 0 && matrixStride != 0)
419 qCWarning(Backend) << "Arrays of matrices not handled yet";
420
421 // Special cases for arrays
422 if (firstArrayDim > 0) {
423 // It appears that for a float myArray[8]; stride is reported as being
424 // 0 but expected size 128 bytes which means 16 bytes per float
425 // In that case we compute the arrayStride ourselves
426 if (arrayStride == 0)
427 arrayStride = blockVariable.size / firstArrayDim;
428 if (arrayStride != 0) {
429 QByteArray newRawData(firstArrayDim * arrayStride, '\0');
430 const int byteSizePerEntry = value.elementByteSize();
431 for (int i = 0, m = std::min(a: firstArrayDim, b: value.elementCount()); i < m; ++i) {
432 std::memcpy(dest: newRawData.data() + i * arrayStride,
433 src: rawData.constData() + i * byteSizePerEntry,
434 n: byteSizePerEntry);
435 }
436 return newRawData;
437 }
438 }
439
440 // Special cases for matrices which might have to be aligned to a vec4 stride
441 if (matrixStride != 0 && value.byteSize() % matrixStride != 0) {
442 // Find number of rows
443 const int rows = blockVariable.size / matrixStride;
444 QByteArray newRawData = QByteArray(rows * matrixStride, '\0');
445 const int dataSizePerRow = value.byteSize() / rows;
446 for (int i = 0; i < rows; ++i) {
447 std::memcpy(dest: newRawData.data() + i * matrixStride,
448 src: rawData.constData() + i * dataSizePerRow, n: dataSizePerRow);
449 }
450 return newRawData;
451 }
452
453 // Note: we currently don't handle array of matrices
454
455 return rawData;
456}
457
458void uploadUniform(const PackUniformHash &uniforms,
459 const PipelineUBOSet::MultiUBOBufferWithBindingAndBlockSize *ubo,
460 const RHIShader::UBO_Member &member,
461 size_t distanceToCommand, int arrayOffset = 0)
462{
463 if (!uniforms.contains(key: member.nameId))
464 return;
465
466 const UniformValue &value = uniforms.value(key: member.nameId);
467 // Textures / Images / Buffers can't be UBO members
468 if (value.valueType() != UniformValue::ScalarValue)
469 return;
470
471 // We can avoid the copies since the raw data copes from the UniformValue
472 // which remain alive until the frame has been submitted
473 const bool requiresCopy = false;
474 const QByteArray rawData = rawDataForUniformValue(blockVariable: member.blockVariable,
475 value,
476 requiresCopy);
477 uploadDataToUBO(rawData, ubo, member, distanceToCommand, arrayOffset);
478
479 // printUpload(value, member.blockVariable, arrayOffset);
480}
481
482} // anonymous
483
484void PipelineUBOSet::uploadShaderDataProperty(const ShaderData *shaderData,
485 const PipelineUBOSet::MultiUBOBufferWithBindingAndBlockSize *ubo,
486 const RHIShader::UBO_Member &uboMemberInstance,
487 size_t distanceToCommand, int arrayOffset)
488{
489 const std::vector<RHIShader::UBO_Member> &structMembers = uboMemberInstance.structMembers;
490 const QHash<QString, ShaderData::PropertyValue> &properties = shaderData->properties();
491 const int structBaseOffset = uboMemberInstance.blockVariable.offset;
492 for (const RHIShader::UBO_Member &member : structMembers) {
493 // TO DO: Use nameIds instead
494 const auto it = properties.find(key: member.blockVariable.name);
495 if (it != properties.end()) {
496 const ShaderData::PropertyValue &value = *it;
497 if (value.isNode) {
498 // Nested ShaderData
499 const ShaderData *child = m_nodeManagers->shaderDataManager()->lookupResource(id: value.value.value<Qt3DCore::QNodeId>());
500 if (child)
501 uploadShaderDataProperty(shaderData: child, ubo, uboMemberInstance: member,
502 distanceToCommand,
503 arrayOffset: structBaseOffset + arrayOffset);
504 continue;
505 }
506 if (value.isTransformed) {
507 // TO DO: Handle this
508 qWarning() << "ShaderData transformed properties not handled yet";
509 // QVariant transformedValue = shaderData->getTransformedProperty(&value, viewMatrix);
510 }
511
512 // Value is a Scalar or a Scalar Array
513 const UniformValue v = UniformValue::fromVariant(variant: value.value);
514 Q_ASSERT(v.valueType() == UniformValue::ScalarValue);
515
516 // We have to make a copy here
517 const bool requiresCopy = true;
518 const QByteArray rawData = rawDataForUniformValue(blockVariable: member.blockVariable,
519 value: v,
520 requiresCopy);
521 uploadDataToUBO(rawData, ubo, member, distanceToCommand, arrayOffset: structBaseOffset + arrayOffset);
522
523 // printUpload(v, member.blockVariable, structBaseOffset + arrayOffset);
524 }
525 }
526}
527
528void PipelineUBOSet::uploadUBOsForCommand(const RenderCommand &command,
529 const size_t distanceToCommand)
530{
531 Q_ASSERT(m_nodeManagers);
532 RHIShader *shader = command.m_rhiShader;
533 if (!shader)
534 return;
535
536 {
537 const HRHIBuffer &commandUBOBuffer = m_commandsUBO.bufferForCommand(distanceToCommand);
538 const size_t localOffset = m_commandsUBO.localOffsetInBufferForCommand(distanceToCommand);
539 commandUBOBuffer->update(data: QByteArray::fromRawData(
540 data: reinterpret_cast<const char *>(&command.m_commandUBO),
541 size: sizeof(CommandUBO)),
542 offset: int(localOffset));
543 }
544
545 const std::vector<RHIShader::UBO_Block> &uboBlocks = shader->uboBlocks();
546 const ShaderParameterPack &parameterPack = command.m_parameterPack;
547 const PackUniformHash &uniforms = parameterPack.uniforms();
548
549 // Update Buffer CPU side data based on uniforms being set
550
551 auto findMaterialUBOForBlock = [this] (const RHIShader::UBO_Block &uboBlock)
552 -> PipelineUBOSet::MultiUBOBufferWithBindingAndBlockSize* {
553 auto it = std::find_if(first: m_materialsUBOs.begin(), last: m_materialsUBOs.end(),
554 pred: [&uboBlock] (const PipelineUBOSet::MultiUBOBufferWithBindingAndBlockSize &materialUBO) {
555 return materialUBO.binding == uboBlock.block.m_binding;
556 });
557
558 if (it == m_materialsUBOs.end())
559 return nullptr;
560 return &*it;
561 };
562
563 auto findUboBlockForBinding = [&uboBlocks] (int blockBinding) -> const RHIShader::UBO_Block * {
564 auto it = std::find_if(first: uboBlocks.begin(), last: uboBlocks.end(), pred: [&blockBinding] (const RHIShader::UBO_Block &block) {
565 return block.block.m_binding == blockBinding;
566 });
567 if (it == uboBlocks.end())
568 return nullptr;
569 return &*it;
570 };
571
572 // Scalar / Texture Parameter to UBO
573 for (const RHIShader::UBO_Block &uboBlock : uboBlocks) {
574
575 // No point in trying to update Bindings 0 or 1 which are reserved
576 // for Qt3D default uniforms
577 const ShaderUniformBlock &block = uboBlock.block;
578 if (block.m_binding <= 1)
579 continue;
580
581 // Update UBO with uniform value
582 const PipelineUBOSet::MultiUBOBufferWithBindingAndBlockSize *ubo = findMaterialUBOForBlock(uboBlock);
583 if (ubo == nullptr)
584 continue;
585
586 for (const RHIShader::UBO_Member &member : std::as_const(t: uboBlock.members)) {
587 const QShaderDescription::BlockVariable &blockVariable = member.blockVariable;
588
589 // Array
590 if (!blockVariable.arrayDims.empty()) {
591 if (!blockVariable.structMembers.empty()) { // Array of structs
592 // we treat structMembers as arrayMembers when we are dealing with an array of structs´
593 const size_t arr0 = size_t(blockVariable.arrayDims[0]);
594 const size_t m = std::max(a: arr0, b: member.structMembers.size());
595 for (size_t i = 0; i < m; ++i) {
596 const RHIShader::UBO_Member &arrayMember = member.structMembers[i];
597 for (const RHIShader::UBO_Member &arrayStructMember : arrayMember.structMembers) {
598 uploadUniform(uniforms, ubo,
599 member: arrayStructMember,
600 distanceToCommand,
601 arrayOffset: int(i * blockVariable.size / arr0));
602 }
603 }
604 } else { // Array of scalars
605 uploadUniform(uniforms, ubo,
606 member, distanceToCommand);
607 }
608 } else {
609 if (!blockVariable.structMembers.empty()) { // Struct
610 for (const RHIShader::UBO_Member &structMember : member.structMembers) {
611 uploadUniform(uniforms, ubo,
612 member: structMember,
613 distanceToCommand);
614 }
615 } else { // Scalar
616 uploadUniform(uniforms, ubo,
617 member, distanceToCommand);
618 }
619 }
620 }
621 }
622
623 // User provided UBO
624 // Since we create per Material UBO, if we directly provide a UBO as a parameter
625 // we will have to copy content of the parameter's UBO into the Material UBO at the
626 // property offset
627 for (const BlockToUBO &ubo : parameterPack.uniformBuffers()) {
628 // Find RHIShader::UBO_Block for UBO
629 const RHIShader::UBO_Block *block = findUboBlockForBinding(ubo.m_bindingIndex);
630 if (block == nullptr)
631 continue;
632 // Find Material UBO for Block
633 PipelineUBOSet::MultiUBOBufferWithBindingAndBlockSize *materialsUBO = findMaterialUBOForBlock(*block);
634 if (materialsUBO == nullptr)
635 continue;
636
637 // Copy content of UBO buffer to materialUBO at offset
638 Buffer *uboBuffer = m_nodeManagers->bufferManager()->lookupResource(id: ubo.m_bufferID);
639 if (!uboBuffer)
640 continue;
641
642 const HRHIBuffer &materialBuffer = materialsUBO->bufferForCommand(distanceToCommand);
643 const size_t localOffsetIntoBuffer = materialsUBO->localOffsetInBufferForCommand(distanceToCommand);
644 materialBuffer->update(data: uboBuffer->data(), offset: int(localOffsetIntoBuffer));
645 }
646
647 // ShaderData -> convenience for filling a struct member of a UBO
648 // Note: we could use setDefaultUniformBlockShaderDataValue to unpack all values as individual uniforms
649 // but setting the whole ShaderData properties at once is a lot more efficient
650 for (const ShaderDataForUBO &shaderDataForUbo : parameterPack.shaderDatasForUBOs()) {
651 // Find ShaderData backend
652 const ShaderData *shaderData = m_nodeManagers->shaderDataManager()->lookupResource(id: shaderDataForUbo.m_shaderDataID);
653 if (shaderData == nullptr)
654 continue;
655 // Find RHIShader::UBO_Block for ShaderData
656 const RHIShader::UBO_Block *block = findUboBlockForBinding(shaderDataForUbo.m_bindingIndex);
657 if (block == nullptr)
658 continue;
659 // Find Material UBO for Block
660 PipelineUBOSet::MultiUBOBufferWithBindingAndBlockSize *materialsUBO = findMaterialUBOForBlock(*block);
661 if (materialsUBO == nullptr)
662 continue;
663
664 // Upload ShaderData property that match members of each UBO block instance
665 for (const RHIShader::UBO_Member &uboInstance : std::as_const(t: block->members)) {
666 uploadShaderDataProperty(shaderData, ubo: materialsUBO,
667 uboMemberInstance: uboInstance, distanceToCommand);
668 }
669 }
670
671 // Note: There's nothing to do for SSBO as those are directly uploaded to the GPU, no extracting
672 // required
673}
674
675HRHIBuffer PipelineUBOSet::MultiUBOBufferWithBindingAndBlockSize::bufferForCommand(size_t distanceToCommand) const
676{
677 const size_t uboIdx = distanceToCommand / commandsPerUBO;
678 return buffers[uboIdx];
679}
680
681size_t PipelineUBOSet::MultiUBOBufferWithBindingAndBlockSize::localOffsetInBufferForCommand(size_t distanceToCommand) const
682{
683 return (distanceToCommand % commandsPerUBO) * alignedBlockSize;
684}
685
686} // Rhi
687
688} // Render
689
690} // Qt3DRender
691
692QT_END_NAMESPACE
693

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