Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qssgrhieffectsystem.cpp
Go to the documentation of this file.
1// Copyright (C) 2008-2012 NVIDIA Corporation.
2// Copyright (C) 2020 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
4
5#include <QtQuick3DRuntimeRender/private/qssgrhieffectsystem_p.h>
6#include <QtQuick3DRuntimeRender/private/qssgrenderer_p.h>
7#include <QtQuick3DRuntimeRender/private/qssgrhiquadrenderer_p.h>
8#include <QtQuick3DRuntimeRender/private/qssgrendercamera_p.h>
9#include <QtQuick3DRuntimeRender/private/qssglayerrenderdata_p.h>
10
13#include <qtquick3d_tracepoints_p.h>
14
15#include <QtQuick3DUtils/private/qssgassert_p.h>
16
17#include <QtCore/qloggingcategory.h>
18
20
21Q_DECLARE_LOGGING_CATEGORY(lcEffectSystem);
22Q_LOGGING_CATEGORY(lcEffectSystem, "qt.quick3d.effects");
23
42
43QSSGRhiEffectSystem::QSSGRhiEffectSystem(const std::shared_ptr<QSSGRenderContextInterface> &sgContext)
44 : m_sgContext(sgContext)
45{
46}
47
48QSSGRhiEffectSystem::~QSSGRhiEffectSystem()
49{
50 releaseResources();
51}
52
53void QSSGRhiEffectSystem::setup(QSize outputSize)
54{
55 if (outputSize.isEmpty()) {
56 releaseResources();
57 return;
58 }
59 m_outSize = outputSize;
60}
61
62QSSGRhiEffectTexture *QSSGRhiEffectSystem::findTexture(const QByteArray &bufferName)
63{
64 auto findTexture = [bufferName](const QSSGRhiEffectTexture *rt){ return rt->name == bufferName; };
65 const auto foundIt = std::find_if(m_textures.cbegin(), m_textures.cend(), findTexture);
66 QSSGRhiEffectTexture *result = foundIt == m_textures.cend() ? nullptr : *foundIt;
67 return result;
68}
69
70QSSGRhiEffectTexture *QSSGRhiEffectSystem::getTexture(const QByteArray &bufferName,
71 const QSize &size,
72 QRhiTexture::Format format,
73 bool isFinalOutput,
74 const QSSGRenderEffect *inEffect,
75 quint8 viewCount)
76{
77 QSSGRhiEffectTexture *result = findTexture(bufferName);
78 const bool gotMatch = result != nullptr;
79
80 // If not found, look for an unused texture
81 if (!result) {
82 // ### This could be enhanced to try to find a texture with the right
83 // size/format/flags first. It is not essential because the texture will be
84 // recreated below if the size or format does not match, but it would
85 // be more optimal (for Effects with Buffers with sizeMultipliers on
86 // them, or ones that use different formats) to look for a matching
87 // size/format too instead of picking the first unused texture.
88 auto findUnused = [](const QSSGRhiEffectTexture *rt){ return rt->name.isEmpty(); };
89 const auto found = std::find_if(m_textures.cbegin(), m_textures.cend(), findUnused);
90 if (found != m_textures.cend()) {
91 result = *found;
92 result->desc = {};
93 }
94 }
95
96 if (!result) {
97 result = new QSSGRhiEffectTexture {};
98 m_textures.append(result);
99 }
100
101 const auto &rhiCtx = m_sgContext->rhiContext();
102 QRhi *rhi = rhiCtx->rhi();
103 const bool formatChanged = result->texture && result->texture->format() != format;
104 const bool needsRebuild = result->texture && (result->texture->pixelSize() != size || formatChanged);
105
106 QRhiTexture::Flags flags = QRhiTexture::RenderTarget;
107 if (isFinalOutput) // play nice with progressive/temporal AA
108 flags |= QRhiTexture::UsedAsTransferSource;
109
110 if (!result->texture) {
111 if (viewCount >= 2)
112 result->texture = rhi->newTextureArray(format, viewCount, size, 1, flags);
113 else
114 result->texture = rhi->newTexture(format, size, 1, flags);
115 result->texture->create();
116 } else if (needsRebuild) {
117 result->texture->setFlags(flags);
118 result->texture->setPixelSize(size);
119 result->texture->setFormat(format);
120 result->texture->create();
121 }
122
123 if (!result->renderTarget) {
124 QRhiColorAttachment colorAttachment(result->texture);
125 colorAttachment.setMultiViewCount(viewCount);
126 QRhiTextureRenderTargetDescription desc(colorAttachment);
127 result->renderTarget = rhi->newTextureRenderTarget(desc);
128 result->renderPassDescriptor = result->renderTarget->newCompatibleRenderPassDescriptor();
129 result->renderTarget->setRenderPassDescriptor(result->renderPassDescriptor);
130 result->renderTarget->create();
131 m_pendingClears.insert(result->renderTarget);
132 } else if (needsRebuild) {
133 if (formatChanged) {
134 delete result->renderPassDescriptor;
135 result->renderPassDescriptor = result->renderTarget->newCompatibleRenderPassDescriptor();
136 result->renderTarget->setRenderPassDescriptor(result->renderPassDescriptor);
137 }
138 result->renderTarget->create();
139 m_pendingClears.insert(result->renderTarget);
140 }
141
142 if (!gotMatch) {
143 QByteArray rtName = inEffect->debugObjectName.toLatin1();
144 rtName += QByteArrayLiteral(" effect pass ");
145 rtName += bufferName;
146 result->renderTarget->setName(rtName);
147 }
148
149 result->name = bufferName;
150 return result;
151}
152
153void QSSGRhiEffectSystem::releaseTexture(QSSGRhiEffectTexture *texture)
154{
155 // Mark as unused by setting the name to empty, unless the Buffer had scene
156 // lifetime on it (then it needs to live on for ever).
157 if (!texture->flags.isSceneLifetime())
158 texture->name = {};
159}
160
161void QSSGRhiEffectSystem::releaseTextures()
162{
163 for (auto *t : std::as_const(m_textures))
164 releaseTexture(t);
165}
166
167QRhiTexture *QSSGRhiEffectSystem::process(const QSSGRenderLayer &layer,
168 QRhiTexture *inTexture,
169 QRhiTexture *inDepthTexture,
170 QRhiTexture *inNormalTexture)
171{
172 QSSG_ASSERT(m_sgContext != nullptr, return inTexture);
173 QSSG_ASSERT(layer.firstEffect != nullptr, return inTexture);
174 const auto &rhiContext = m_sgContext->rhiContext();
175 const auto &renderer = m_sgContext->renderer();
176 QSSG_ASSERT(rhiContext && renderer, return inTexture);
177
178 const auto viewCount = layer.viewCount;
179
180 m_depthTexture = inDepthTexture;
181 m_normalTexture = inNormalTexture;
182 m_cameraClipRange = layer.renderedCameras[0]->clipPlanes;
183
184 bool usesProjectionMatrix = false;
185 bool usesViewMatrix = false;
186 for (const QSSGRenderEffect *eff = layer.firstEffect; eff; eff = eff->m_nextEffect) {
187 if (eff->testFlag(QSSGRenderEffect::Flags::UsesProjectionMatrix)
188 || eff->testFlag(QSSGRenderEffect::Flags::UsesInverseProjectionMatrix))
189 {
190 usesProjectionMatrix = true;
191 }
192 if (eff->testFlag(QSSGRenderEffect::Flags::UsesViewMatrix))
193 usesViewMatrix = true;
194 }
195
196 if (usesProjectionMatrix) {
197 m_projectionMatrices.resize(viewCount);
198 const QMatrix4x4 clipSpaceCorrMatrix = rhiContext->rhi()->clipSpaceCorrMatrix();
199 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
200 m_projectionMatrices[viewIndex] = clipSpaceCorrMatrix * layer.renderedCameras[viewIndex]->projection;
201 }
202
203 if (usesViewMatrix) {
204 QMatrix4x4 camGlobalTransforms[2] { QMatrix4x4{Qt::Uninitialized}, QMatrix4x4{Qt::Uninitialized} };
205 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
206 camGlobalTransforms[viewIndex] = layer.renderData->getGlobalTransform(*layer.renderedCameras[viewIndex]);
207 m_viewMatrices.resize(viewCount);
208 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
209 m_viewMatrices[viewIndex] = camGlobalTransforms[viewIndex].inverted();
210 }
211
212 m_currentUbufIndex = 0;
213 // FIXME: Keeping the change minimal for now, but we should avoid the need for this cast.
214 QSSGRenderEffect *currentEffect = const_cast<QSSGRenderEffect *>(layer.firstEffect);
215 QSSGRhiEffectTexture firstTex{ inTexture, nullptr, nullptr, {}, {}, {} };
216 auto *latestOutput = doRenderEffect(currentEffect, &firstTex, viewCount);
217 firstTex.texture = nullptr; // make sure we don't delete inTexture when we go out of scope
218
219 while ((currentEffect = currentEffect->m_nextEffect)) {
220 QSSGRhiEffectTexture *effectOut = doRenderEffect(currentEffect, latestOutput, viewCount);
221 releaseTexture(latestOutput);
222 latestOutput = effectOut;
223 }
224
225 releaseTextures();
226 return latestOutput ? latestOutput->texture : nullptr;
227}
228
229void QSSGRhiEffectSystem::releaseResources()
230{
231 qDeleteAll(m_textures);
232 m_textures.clear();
233
234 m_shaderPipelines.clear();
235}
236
237QSSGRenderTextureFormat::Format QSSGRhiEffectSystem::overriddenOutputFormat(const QSSGRenderEffect *inEffect)
238{
239 QSSGRenderTextureFormat::Format format = QSSGRenderTextureFormat::Unknown;
240 for (const QSSGCommand *cmd : inEffect->commands) {
241 if (cmd->m_type == CommandType::BindTarget) {
242 const QSSGBindTarget *targetCmd = static_cast<const QSSGBindTarget *>(cmd);
243 format = targetCmd->m_outputFormat == QSSGRenderTextureFormat::Unknown
244 ? inEffect->outputFormat : targetCmd->m_outputFormat.format;
245 }
246 }
247 return format;
248}
249
250QSSGRhiEffectTexture *QSSGRhiEffectSystem::doRenderEffect(const QSSGRenderEffect *inEffect,
251 QSSGRhiEffectTexture *inTexture,
252 quint8 viewCount)
253{
254 // Run through the effect commands and render the effect.
255 qCDebug(lcEffectSystem) << "START effect " << inEffect->className;
256 QSSGRhiEffectTexture *finalOutputTexture = nullptr;
257 QSSGRhiEffectTexture *currentOutput = nullptr;
258 QSSGRhiEffectTexture *currentInput = inTexture;
259 for (const QSSGCommand *theCommand : inEffect->commands) {
260 qCDebug(lcEffectSystem).noquote() << " >" << theCommand->typeAsString() << "--" << theCommand->debugString();
261
262 switch (theCommand->m_type) {
263 case CommandType::AllocateBuffer:
264 allocateBufferCmd(static_cast<const QSSGAllocateBuffer *>(theCommand), inTexture, inEffect, viewCount);
265 break;
266
267 case CommandType::ApplyBufferValue: {
268 auto *applyCommand = static_cast<const QSSGApplyBufferValue *>(theCommand);
269
270 /*
271 BufferInput { buffer: buf }
272 -> INPUT (qt_inputTexture) in the shader samples the texture for Buffer buf in the pass
273 BufferInput { sampler: "ttt" }
274 -> ttt in the shader samples the input texture for the pass
275 (ttt also needs to be a TextureInput with a Texture{} to get the sampler declared in the shader code,
276 beware that without the BufferInput the behavior would change: ttt would then sample a dummy texture)
277 BufferInput { buffer: buf; sampler: "ttt" }
278 -> ttt in the shader samples the texture for Buffer buf in the pass
279 */
280
281 auto *buffer = applyCommand->m_bufferName.isEmpty() ? inTexture : findTexture(applyCommand->m_bufferName);
282 if (applyCommand->m_samplerName.isEmpty())
283 currentInput = buffer;
284 else
285 addTextureToShaderPipeline(applyCommand->m_samplerName, buffer->texture, buffer->desc);
286 break;
287 }
288
289 case CommandType::ApplyInstanceValue:
290 applyInstanceValueCmd(static_cast<const QSSGApplyInstanceValue *>(theCommand), inEffect);
291 break;
292
293 case CommandType::ApplyValue:
294 applyValueCmd(static_cast<const QSSGApplyValue *>(theCommand), inEffect);
295 break;
296
297 case CommandType::BindBuffer: {
298 auto *bindCmd = static_cast<const QSSGBindBuffer *>(theCommand);
299 currentOutput = findTexture(bindCmd->m_bufferName);
300 break;
301 }
302
303 case CommandType::BindShader:
304 bindShaderCmd(static_cast<const QSSGBindShader *>(theCommand), inEffect, viewCount);
305 break;
306
307 case CommandType::BindTarget: {
308 auto targetCmd = static_cast<const QSSGBindTarget*>(theCommand);
309 // matches overriddenOutputFormat()
310 QSSGRenderTextureFormat::Format f = targetCmd->m_outputFormat == QSSGRenderTextureFormat::Unknown ?
311 inEffect->outputFormat : targetCmd->m_outputFormat.format;
312 // f is now either Unknown (common case), or if the effect overrides the output format, then that
313 QRhiTexture::Format rhiFormat = f == QSSGRenderTextureFormat::Unknown ?
314 currentInput->texture->format() : QSSGBufferManager::toRhiFormat(f);
315 qCDebug(lcEffectSystem) << " Target format override" << QSSGBaseTypeHelpers::toString(f) << "Effective RHI format" << rhiFormat;
316 // Make sure we use different names for each effect inside one frame
317 QByteArray tmpName = QByteArrayLiteral("__output_").append(QByteArray::number(m_currentUbufIndex));
318 currentOutput = getTexture(tmpName, m_outSize, rhiFormat, true, inEffect, viewCount);
319 finalOutputTexture = currentOutput;
320 break;
321 }
322
323 case CommandType::Render:
324 renderCmd(inEffect, currentInput, currentOutput, viewCount);
325 currentInput = inTexture; // default input for each new pass is defined to be original input
326 break;
327
328 default:
329 qWarning() << "Effect command" << theCommand->typeAsString() << "not implemented";
330 break;
331 }
332 }
333 // TODO: release textures used by this effect now, instead of after processing all the effects
334 qCDebug(lcEffectSystem) << "END effect " << inEffect->className;
335 return finalOutputTexture;
336}
337
338void QSSGRhiEffectSystem::allocateBufferCmd(const QSSGAllocateBuffer *inCmd,
339 QSSGRhiEffectTexture *inTexture,
340 const QSSGRenderEffect *inEffect,
341 quint8 viewCount)
342{
343 // Note: Allocate is used both to allocate new, and refer to buffer created earlier
344 QSize bufferSize(m_outSize * qreal(inCmd->m_sizeMultiplier));
345
346 QSSGRenderTextureFormat f = inCmd->m_format;
347 QRhiTexture::Format rhiFormat = (f == QSSGRenderTextureFormat::Unknown) ? inTexture->texture->format()
348 : QSSGBufferManager::toRhiFormat(f);
349
350 QSSGRhiEffectTexture *buf = getTexture(inCmd->m_name, bufferSize, rhiFormat, false, inEffect, viewCount);
351 auto filter = QSSGRhiHelpers::toRhi(inCmd->m_filterOp);
352 auto tiling = QSSGRhiHelpers::toRhi(inCmd->m_texCoordOp);
353 buf->desc = { filter, filter, QRhiSampler::None, tiling, tiling, QRhiSampler::Repeat };
354 buf->flags = inCmd->m_bufferFlags;
355}
356
357void QSSGRhiEffectSystem::applyInstanceValueCmd(const QSSGApplyInstanceValue *inCmd, const QSSGRenderEffect *inEffect)
358{
359 if (!m_currentShaderPipeline)
360 return;
361
362 const bool setAll = inCmd->m_propertyName.isEmpty();
363 for (const QSSGRenderEffect::Property &property : std::as_const(inEffect->properties)) {
364 if (setAll || property.name == inCmd->m_propertyName) {
365 m_currentShaderPipeline->setUniformValue(m_currentUBufData, property.name, property.value, property.shaderDataType);
366 //qCDebug(lcEffectSystem) << "setUniformValue" << property.name << toString(property.shaderDataType) << "to" << property.value;
367 }
368 }
369 for (const QSSGRenderEffect::TextureProperty &textureProperty : std::as_const(inEffect->textureProperties)) {
370 if (setAll || textureProperty.name == inCmd->m_propertyName) {
371 bool texAdded = false;
372 QSSGRenderImage *image = textureProperty.texImage;
373 if (image) {
374 const auto &theBufferManager(m_sgContext->bufferManager());
375 const QSSGRenderImageTexture texture = theBufferManager->loadRenderImage(image);
376 if (texture.m_texture) {
377 const QSSGRhiSamplerDescription desc{
378 QSSGRhiHelpers::toRhi(textureProperty.minFilterType),
379 QSSGRhiHelpers::toRhi(textureProperty.magFilterType),
380 textureProperty.mipFilterType != QSSGRenderTextureFilterOp::None ? QSSGRhiHelpers::toRhi(textureProperty.mipFilterType) : QRhiSampler::None,
381 QSSGRhiHelpers::toRhi(textureProperty.horizontalClampType),
382 QSSGRhiHelpers::toRhi(textureProperty.verticalClampType),
383 QSSGRhiHelpers::toRhi(textureProperty.zClampType)
384 };
385 addTextureToShaderPipeline(textureProperty.name, texture.m_texture, desc);
386 texAdded = true;
387 }
388 }
389 if (!texAdded) {
390 // Something went wrong, e.g. image file not found. Still need to add a dummy texture for the shader
391 qCDebug(lcEffectSystem) << "Using dummy texture for property" << textureProperty.name;
392 addTextureToShaderPipeline(textureProperty.name, nullptr, {});
393 }
394 }
395 }
396}
397
398void QSSGRhiEffectSystem::applyValueCmd(const QSSGApplyValue *inCmd, const QSSGRenderEffect *inEffect)
399{
400 if (!m_currentShaderPipeline)
401 return;
402
403 const auto &properties = inEffect->properties;
404 const auto foundIt = std::find_if(properties.cbegin(), properties.cend(), [inCmd](const QSSGRenderEffect::Property &prop) {
405 return (prop.name == inCmd->m_propertyName);
406 });
407
408 if (foundIt != properties.cend())
409 m_currentShaderPipeline->setUniformValue(m_currentUBufData, inCmd->m_propertyName, inCmd->m_value, foundIt->shaderDataType);
410 else
411 qWarning() << "Could not find effect property" << inCmd->m_propertyName;
412}
413
414static const char *effect_builtin_textureMapUV =
415 "vec2 qt_effectTextureMapUV(vec2 uv)\n"
416 "{\n"
417 " return uv;\n"
418 "}\n";
419
421 "vec2 qt_effectTextureMapUV(vec2 uv)\n"
422 "{\n"
423 " return vec2(uv.x, 1.0 - uv.y);\n"
424 "}\n";
425
426QSSGRhiShaderPipelinePtr QSSGRhiEffectSystem::buildShaderForEffect(const QSSGBindShader &inCmd,
427 QSSGProgramGenerator &generator,
428 QSSGShaderLibraryManager &shaderLib,
429 QSSGShaderCache &shaderCache,
430 bool isYUpInFramebuffer,
431 int viewCount)
432{
433 const auto &key = inCmd.m_shaderPathKey;
434 qCDebug(lcEffectSystem) << " generating new shader pipeline for: " << key;
435
436 generator.beginProgram();
437
438 {
439 const QByteArray src = shaderLib.getShaderSource(inCmd.m_shaderPathKey, QSSGShaderCache::ShaderType::Vertex);
440 QSSGStageGeneratorBase *vStage = generator.getStage(QSSGShaderGeneratorStage::Vertex);
441 // The variation based on isYUpInFramebuffer is captured in 'key' as
442 // well, so it is safe to vary the source code here.
443 vStage->append(isYUpInFramebuffer ? effect_builtin_textureMapUV : effect_builtin_textureMapUVFlipped);
444 vStage->append(src);
445 }
446 {
447 const QByteArray src = shaderLib.getShaderSource(inCmd.m_shaderPathKey, QSSGShaderCache::ShaderType::Fragment);
448 QSSGStageGeneratorBase *fStage = generator.getStage(QSSGShaderGeneratorStage::Fragment);
449 fStage->append(src);
450 }
451
452 return generator.compileGeneratedRhiShader(key,
453 shaderLib.getShaderMetaData(inCmd.m_shaderPathKey, QSSGShaderCache::ShaderType::Fragment).features,
454 shaderLib,
455 shaderCache,
456 QSSGRhiShaderPipeline::UsedWithoutIa,
457 viewCount,
458 false);
459}
460
461void QSSGRhiEffectSystem::bindShaderCmd(const QSSGBindShader *inCmd, const QSSGRenderEffect *inEffect, quint8 viewCount)
462{
463 QElapsedTimer timer;
464 timer.start();
465
466 m_currentTextures.clear();
467 m_pendingClears.clear();
468 m_currentShaderPipeline = nullptr;
469
470 const auto &rhiCtx = m_sgContext->rhiContext();
471 QRhi *rhi = rhiCtx->rhi();
472 const auto &shaderLib = m_sgContext->shaderLibraryManager();
473 const auto &shaderCache = m_sgContext->shaderCache();
474
475 // Now we need a proper unique key (unique in the scene), the filenames are
476 // not sufficient. This means that using the same shader source files in
477 // multiple Effects in the same scene will work. It wouldn't if all those
478 // Effects reused the same QSSGRhiShaderPipeline (i.e. if the only cache
479 // key was the m_shaderPathKey).
480 QSSGEffectSceneCacheKey cacheKey;
481 cacheKey.m_shaderPathKey = inCmd->m_shaderPathKey;
482 cacheKey.m_cmd = quintptr(inCmd);
483 cacheKey.m_ubufIndex = m_currentUbufIndex;
484 cacheKey.updateHashCode();
485
486 // look for a runtime pipeline
487 const auto it = m_shaderPipelines.constFind(cacheKey);
488 if (it != m_shaderPipelines.cend())
489 m_currentShaderPipeline = (*it).get();
490
491 QByteArray qsbcKey;
492 QSSGShaderFeatures features;
493 if (!m_currentShaderPipeline) { // don't spend time if already got the pipeline
494 features = shaderLib->getShaderMetaData(inCmd->m_shaderPathKey, QSSGShaderCache::ShaderType::Fragment).features;
495 qsbcKey = QQsbCollection::EntryDesc::generateSha(inCmd->m_shaderPathKey, QQsbCollection::toFeatureSet(features));
496 }
497
498 // Check if there's a build-time generated entry for this effect
499 if (!m_currentShaderPipeline && !shaderLib->m_preGeneratedShaderEntries.isEmpty()) {
500 const QQsbCollection::EntryMap &pregenEntries = shaderLib->m_preGeneratedShaderEntries;
501 const auto foundIt = pregenEntries.constFind(QQsbCollection::Entry(qsbcKey));
502 if (foundIt != pregenEntries.cend()) {
503 // The result here is always a new QSSGRhiShaderPipeline, which
504 // fulfills the requirements of our local cache (cmd/ubufIndex in
505 // cacheKey, not needed here since the result is a new object).
506 const auto &shader = shaderCache->newPipelineFromPregenerated(inCmd->m_shaderPathKey,
507 features,
508 *foundIt,
509 *inEffect,
510 QSSGRhiShaderPipeline::UsedWithoutIa);
511 m_shaderPipelines.insert(cacheKey, shader);
512 m_currentShaderPipeline = shader.get();
513 }
514 }
515
516 if (!m_currentShaderPipeline) {
517 // Try the persistent (disk-based) cache then. The result here is
518 // always a new QSSGRhiShaderPipeline, which fulfills the requirements
519 // of our local cache (cmd/ubufIndex in cacheKey, not needed here since
520 // the result is a new object). Alternatively, the result may be null
521 // if there was no hit.
522 const auto &shaderPipeline = shaderCache->tryNewPipelineFromPersistentCache(qsbcKey,
523 inCmd->m_shaderPathKey,
524 features,
525 QSSGRhiShaderPipeline::UsedWithoutIa);
526 if (shaderPipeline) {
527 m_shaderPipelines.insert(cacheKey, shaderPipeline);
528 m_currentShaderPipeline = shaderPipeline.get();
529 }
530 }
531
532 if (!m_currentShaderPipeline) {
533 // Final option, generate the shader pipeline
534 Q_TRACE_SCOPE(QSSG_generateShader);
535 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DGenerateShader);
536 const auto &generator = m_sgContext->shaderProgramGenerator();
537 if (auto stages = buildShaderForEffect(*inCmd, *generator, *shaderLib, *shaderCache, rhi->isYUpInFramebuffer(), viewCount)) {
538 m_shaderPipelines.insert(cacheKey, stages);
539 m_currentShaderPipeline = stages.get();
540 }
541 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DGenerateShader, 0, inEffect->profilingId);
542 }
543
544 const auto &rhiContext = m_sgContext->rhiContext();
545
546 if (m_currentShaderPipeline) {
547 const void *cacheKey1 = reinterpret_cast<const void *>(this);
548 const void *cacheKey2 = reinterpret_cast<const void *>(qintptr(m_currentUbufIndex));
549 QSSGRhiDrawCallData &dcd = QSSGRhiContextPrivate::get(rhiContext.get())->drawCallData({ cacheKey1, cacheKey2, nullptr, 0 });
550 m_currentShaderPipeline->ensureCombinedUniformBuffer(&dcd.ubuf);
551 m_currentUBufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
552 } else {
553 m_currentUBufData = nullptr;
554 }
555
556 QSSGRhiContextStats::get(*rhiContext).registerEffectShaderGenerationTime(timer.elapsed());
557}
558
559void QSSGRhiEffectSystem::renderCmd(const QSSGRenderEffect *inEffect, QSSGRhiEffectTexture *inTexture, QSSGRhiEffectTexture *target, quint8 viewCount)
560{
561 if (!m_currentShaderPipeline)
562 return;
563
564 if (!target) {
565 qWarning("No effect render target?");
566 return;
567 }
568
569 // the shader only uses one of these (or none)
570 addTextureToShaderPipeline(QByteArrayLiteral("qt_inputTexture"), inTexture->texture, inTexture->desc);
571 addTextureToShaderPipeline(QByteArrayLiteral("qt_inputTextureArray"), inTexture->texture, inTexture->desc);
572
573 const auto &rhiContext = m_sgContext->rhiContext();
574 const auto &renderer = m_sgContext->renderer();
575
576 QRhiCommandBuffer *cb = rhiContext->commandBuffer();
577 cb->debugMarkBegin(QByteArrayLiteral("Post-processing effect"));
578 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
579
580 for (QRhiTextureRenderTarget *rt : m_pendingClears) {
581 // Effects like motion blur use an accumulator texture that should
582 // start out empty (and they are sampled in the first pass), so such
583 // textures need an explicit clear. It is not applicable for the common
584 // case of outputting into a texture because that will get a clear
585 // anyway when rendering the quad.
586 if (rt != target->renderTarget) {
587 cb->beginPass(rt, Qt::transparent, { 1.0f, 0 }, nullptr, rhiContext->commonPassFlags());
588 QSSGRHICTX_STAT(rhiContext, beginRenderPass(rt));
589 cb->endPass();
590 QSSGRHICTX_STAT(rhiContext, endRenderPass());
591 }
592 }
593 m_pendingClears.clear();
594
595 const QSize inputSize = inTexture->texture->pixelSize();
596 const QSize outputSize = target->texture->pixelSize();
597 addCommonEffectUniforms(inEffect, inputSize, outputSize, viewCount);
598
599 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiContext.get());
600
601 const void *cacheKey1 = reinterpret_cast<const void *>(this);
602 const void *cacheKey2 = reinterpret_cast<const void *>(qintptr(m_currentUbufIndex));
603 QSSGRhiDrawCallData &dcd = rhiCtxD->drawCallData({ cacheKey1, cacheKey2, nullptr, 0 });
604 dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
605 m_currentUBufData = nullptr;
606
607 QRhiResourceUpdateBatch *rub = rhiContext->rhi()->nextResourceUpdateBatch();
608 renderer->rhiQuadRenderer()->prepareQuad(rhiContext.get(), rub);
609
610 // do resource bindings
611 const QRhiShaderResourceBinding::StageFlags VISIBILITY_ALL =
612 QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage;
613 QSSGRhiShaderResourceBindingList bindings;
614 for (const QSSGRhiTexture &rhiTex : m_currentTextures) {
615 int binding = m_currentShaderPipeline->bindingForTexture(rhiTex.name);
616 if (binding < 0) // may not be used in the shader (think qt_inputTexture, it's not given a shader samples INPUT)
617 continue;
618 qCDebug(lcEffectSystem) << " -> texture binding" << binding << "for" << rhiTex.name;
619 // Make sure to bind all samplers even if the texture is missing, otherwise we can get crash on some graphics APIs
620 QRhiTexture *texture = rhiTex.texture ? rhiTex.texture : rhiContext->dummyTexture({}, rub);
621 bindings.addTexture(binding,
622 QRhiShaderResourceBinding::FragmentStage,
623 texture,
624 rhiContext->sampler(rhiTex.samplerDesc));
625 }
626 bindings.addUniformBuffer(0, VISIBILITY_ALL, dcd.ubuf);
627
628 QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
629
630 QSSGRhiGraphicsPipelineState ps;
631 ps.viewport = QRhiViewport(0, 0, float(outputSize.width()), float(outputSize.height()));
632 ps.samples = target->renderTarget->sampleCount();
633 ps.viewCount = viewCount;
634 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, m_currentShaderPipeline);
635
636 renderer->rhiQuadRenderer()->recordRenderQuadPass(rhiContext.get(), &ps, srb, target->renderTarget, QSSGRhiQuadRenderer::UvCoords);
637 m_currentUbufIndex++;
638 cb->debugMarkEnd();
639 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("post_processing_effect"));
640}
641
642void QSSGRhiEffectSystem::addCommonEffectUniforms(const QSSGRenderEffect *inEffect, const QSize &inputSize, const QSize &outputSize, quint8 viewCount)
643{
644 const auto &rhiContext = m_sgContext->rhiContext();
645 QRhi *rhi = rhiContext->rhi();
646
647 QMatrix4x4 mvp;
648 if (rhi->isYUpInFramebuffer() != rhi->isYUpInNDC())
649 mvp.data()[5] = -1.0f;
650 m_currentShaderPipeline->setUniformValue(m_currentUBufData, "qt_modelViewProjection", mvp, QSSGRenderShaderValue::Matrix4x4);
651
652 const bool usesProjectionMatrix = inEffect->testFlag(QSSGRenderEffect::Flags::UsesProjectionMatrix);
653 const bool usesInverseProjectionMatrix = inEffect->testFlag(QSSGRenderEffect::Flags::UsesInverseProjectionMatrix);
654 if (usesProjectionMatrix || usesInverseProjectionMatrix) {
655 if (viewCount < 2) {
656 if (usesProjectionMatrix)
657 m_currentShaderPipeline->setUniformValue(m_currentUBufData, "qt_projectionMatrix", m_projectionMatrices[0], QSSGRenderShaderValue::Matrix4x4);
658 if (usesInverseProjectionMatrix)
659 m_currentShaderPipeline->setUniformValue(m_currentUBufData, "qt_inverseProjectionMatrix", m_projectionMatrices[0].inverted(), QSSGRenderShaderValue::Matrix4x4);
660 } else {
661 if (usesProjectionMatrix)
662 m_currentShaderPipeline->setUniformArray(m_currentUBufData, "qt_projectionMatrix", m_projectionMatrices.constData(), viewCount, QSSGRenderShaderValue::Matrix4x4);
663 if (usesInverseProjectionMatrix) {
664 QVarLengthArray<QMatrix4x4, 2> invertedProjections(viewCount);
665 for (quint8 viewIndex = 0; viewIndex < viewCount; ++viewIndex)
666 invertedProjections[viewIndex] = m_projectionMatrices[viewIndex].inverted();
667 m_currentShaderPipeline->setUniformArray(m_currentUBufData, "qt_inverseProjectionMatrix", invertedProjections.constData(), viewCount, QSSGRenderShaderValue::Matrix4x4);
668 }
669 }
670 }
671
672 if (inEffect->testFlag(QSSGRenderEffect::Flags::UsesViewMatrix)) {
673 if (viewCount < 2)
674 m_currentShaderPipeline->setUniformValue(m_currentUBufData, "qt_viewMatrix", m_viewMatrices[0], QSSGRenderShaderValue::Matrix4x4);
675 else
676 m_currentShaderPipeline->setUniformArray(m_currentUBufData, "qt_viewMatrix", m_viewMatrices.constData(), viewCount, QSSGRenderShaderValue::Matrix4x4);
677 }
678
679 QVector2D size(inputSize.width(), inputSize.height());
680 m_currentShaderPipeline->setUniformValue(m_currentUBufData, "qt_inputSize", size, QSSGRenderShaderValue::Vec2);
681
682 size = QVector2D(outputSize.width(), outputSize.height());
683 m_currentShaderPipeline->setUniformValue(m_currentUBufData, "qt_outputSize", size, QSSGRenderShaderValue::Vec2);
684
685 float fc = float(m_sgContext->renderer()->frameCount());
686 m_currentShaderPipeline->setUniformValue(m_currentUBufData, "qt_frame_num", fc, QSSGRenderShaderValue::Float);
687
688 // Bames and values for uniforms that are also used by default and/or
689 // custom materials must always match, effects must not deviate.
690
691 m_currentShaderPipeline->setUniformValue(m_currentUBufData, "qt_cameraProperties", m_cameraClipRange, QSSGRenderShaderValue::Vec2);
692
693 float vp = rhi->isYUpInFramebuffer() ? 1.0f : -1.0f;
694 m_currentShaderPipeline->setUniformValue(m_currentUBufData, "qt_normalAdjustViewportFactor", vp, QSSGRenderShaderValue::Float);
695
696 const float nearClip = rhi->isClipDepthZeroToOne() ? 0.0f : -1.0f;
697 m_currentShaderPipeline->setUniformValue(m_currentUBufData, "qt_nearClipValue", nearClip, QSSGRenderShaderValue::Float);
698
699 const QVector4D rhiProperties(
700 rhi->isYUpInFramebuffer() ? 1.0f : -1.0f,
701 rhi->isYUpInNDC() ? 1.0f : -1.0f,
702 rhi->isClipDepthZeroToOne() ? 0.0f : -1.0f,
703 0.0f // unused
704 );
705 m_currentShaderPipeline->setUniformValue(m_currentUBufData, "qt_rhi_properties", rhiProperties, QSSGRenderShaderValue::Vec4);
706
707 if (m_depthTexture) {
708 static const QSSGRhiSamplerDescription depthSamplerDesc {
709 QRhiSampler::Nearest, QRhiSampler::Nearest,
710 QRhiSampler::None,
711 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat
712 };
713 addTextureToShaderPipeline("qt_depthTexture", m_depthTexture, depthSamplerDesc);
714 addTextureToShaderPipeline("qt_depthTextureArray", m_depthTexture, depthSamplerDesc);
715 }
716
717 if (m_normalTexture) {
718 static const QSSGRhiSamplerDescription normalSamplerDesc {
719 QRhiSampler::Nearest, QRhiSampler::Nearest,
720 QRhiSampler::None,
721 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat
722 };
723 addTextureToShaderPipeline("qt_normalTexture", m_normalTexture, normalSamplerDesc);
724 }
725}
726
727void QSSGRhiEffectSystem::addTextureToShaderPipeline(const QByteArray &name,
728 QRhiTexture *texture,
729 const QSSGRhiSamplerDescription &samplerDescription)
730{
731 if (!m_currentShaderPipeline)
732 return;
733
734 static const QSSGRhiSamplerDescription defaultDescription { QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
735 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat };
736 bool validDescription = samplerDescription.magFilter != QRhiSampler::None;
737
738 // This is a map for a reason: there can be multiple calls to this function
739 // for the same 'name', with a different 'texture', take the last value
740 // into account only.
741 m_currentTextures.insert(name, { name, texture, validDescription ? samplerDescription : defaultDescription});
742}
743
744QT_END_NAMESPACE
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")
static const char * effect_builtin_textureMapUVFlipped
static const char * effect_builtin_textureMapUV
QSSGRhiSamplerDescription desc
QSSGAllocateBufferFlags flags
QRhiTextureRenderTarget * renderTarget
QRhiRenderPassDescriptor * renderPassDescriptor
QSSGRhiEffectTexture & operator=(const QSSGRhiEffectTexture &)=delete