1// Copyright (C) 2008-2012 NVIDIA Corporation.
2// Copyright (C) 2019 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
4
5#include "qssgrhicustommaterialsystem_p.h"
6
7#include <QtQuick3DUtils/private/qssgutils_p.h>
8
9#include "qssgrendercontextcore.h"
10#include <QtQuick3DRuntimeRender/private/qssgrenderbuffermanager_p.h>
11#include <QtQuick3DRuntimeRender/private/qssgrendermesh_p.h>
12#include <QtQuick3DRuntimeRender/private/qssgrendercamera_p.h>
13#include <QtQuick3DRuntimeRender/private/qssgrenderlight_p.h>
14#include <QtQuick3DRuntimeRender/private/qssgrenderlayer_p.h>
15#include <QtQuick3DRuntimeRender/private/qssgrenderableimage_p.h>
16#include <QtQuick3DRuntimeRender/private/qssgvertexpipelineimpl_p.h>
17#include <QtQuick3DRuntimeRender/private/qssglayerrenderdata_p.h>
18#include <QtQuick3DRuntimeRender/private/qssgrendermodel_p.h>
19#include <QtQuick3DRuntimeRender/private/qssgruntimerenderlogging_p.h>
20#include <QtQuick3DRuntimeRender/private/qssgrhiparticles_p.h>
21#include <qtquick3d_tracepoints_p.h>
22
23#include <QtCore/qbitarray.h>
24
25QT_BEGIN_NAMESPACE
26
27Q_TRACE_POINT(qtquick3d, QSSG_generateShader_entry)
28Q_TRACE_POINT(qtquick3d, QSSG_generateShader_exit)
29
30QSSGCustomMaterialSystem::QSSGCustomMaterialSystem() = default;
31
32QSSGCustomMaterialSystem::~QSSGCustomMaterialSystem()
33{
34}
35
36bool QSSGCustomMaterialSystem::prepareForRender(const QSSGRenderModel &,
37 const QSSGRenderSubset &,
38 QSSGRenderCustomMaterial &inMaterial)
39{
40 return inMaterial.isDirty();
41}
42
43void QSSGCustomMaterialSystem::setRenderContextInterface(QSSGRenderContextInterface *inContext)
44{
45 context = inContext;
46}
47
48void QSSGCustomMaterialSystem::releaseCachedResources()
49{
50 shaderMap.clear();
51}
52
53QSSGRhiShaderPipelinePtr QSSGCustomMaterialSystem::shadersForCustomMaterial(QSSGRhiGraphicsPipelineState *ps,
54 const QSSGRenderCustomMaterial &material,
55 QSSGSubsetRenderable &renderable,
56 const QSSGShaderDefaultMaterialKeyProperties &defaultMaterialShaderKeyProperties,
57 const QSSGShaderFeatures &featureSet)
58{
59 QElapsedTimer timer;
60 timer.start();
61
62 QSSGRhiShaderPipelinePtr shaderPipeline;
63
64 const bool multiView = featureSet.isSet(feature: QSSGShaderFeatures::Feature::DisableMultiView)
65 ? false
66 : defaultMaterialShaderKeyProperties.m_viewCount.getValue(inDataStore: renderable.shaderDescription) >= 2;
67 const QByteArray shaderPathKey = material.m_shaderPathKey[multiView ? QSSGRenderCustomMaterial::MultiViewShaderPathKeyIndex
68 : QSSGRenderCustomMaterial::RegularShaderPathKeyIndex];
69
70 // This just references inFeatureSet and inRenderable.shaderDescription -
71 // cheap to construct and is good enough for the find(). This is the first
72 // level, fast lookup. (equivalent to what
73 // QSSGRenderer::getShaderPipelineForDefaultMaterial does for the
74 // default/principled material)
75 QSSGShaderMapKey skey = QSSGShaderMapKey(shaderPathKey,
76 featureSet,
77 renderable.shaderDescription);
78 auto it = shaderMap.find(key: skey);
79 if (it == shaderMap.end()) {
80 // NB this key calculation must replicate exactly what the generator does in generateMaterialRhiShader()
81 QByteArray shaderString = shaderPathKey;
82 QSSGShaderDefaultMaterialKey matKey(renderable.shaderDescription);
83 matKey.toString(ioString&: shaderString, inProperties: defaultMaterialShaderKeyProperties);
84
85 // Try the persistent (disk-based) cache.
86 const QByteArray qsbcKey = QQsbCollection::EntryDesc::generateSha(materialKey: shaderString, featureSet: QQsbCollection::toFeatureSet(ssgFeatureSet: featureSet));
87 shaderPipeline = context->shaderCache()->tryNewPipelineFromPersistentCache(qsbcKey, inKey: shaderPathKey, inFeatures: featureSet);
88
89 if (!shaderPipeline) {
90 // Have to generate the shaders and send it all through the shader conditioning pipeline.
91 Q_TRACE_SCOPE(QSSG_generateShader);
92 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DGenerateShader);
93 QSSGMaterialVertexPipeline vertexPipeline(*context->shaderProgramGenerator(),
94 defaultMaterialShaderKeyProperties,
95 material.adapter);
96
97 shaderPipeline = QSSGMaterialShaderGenerator::generateMaterialRhiShader(inShaderKeyPrefix: shaderPathKey,
98 vertexGenerator&: vertexPipeline,
99 key: renderable.shaderDescription,
100 inProperties: defaultMaterialShaderKeyProperties,
101 inFeatureSet: featureSet,
102 inMaterial: renderable.material,
103 inLights: renderable.lights,
104 inFirstImage: renderable.firstImage,
105 shaderLibraryManager&: *context->shaderLibraryManager(),
106 theCache&: *context->shaderCache());
107 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DGenerateShader, 0, material.profilingId);
108 }
109
110 // make skey useable as a key for the QHash (makes a copy of the materialKey, instead of just referencing)
111 skey.detach();
112 // insert it no matter what, no point in trying over and over again
113 shaderMap.insert(key: skey, value: shaderPipeline);
114 } else {
115 shaderPipeline = it.value();
116 }
117
118 if (shaderPipeline) {
119 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps&: *ps, pipeline: shaderPipeline.get());
120 shaderPipeline->resetExtraTextures();
121 }
122
123 QSSGRhiContextStats::get(rhiCtx&: *context->rhiContext()).registerMaterialShaderGenerationTime(ms: timer.elapsed());
124
125 return shaderPipeline;
126}
127
128void QSSGCustomMaterialSystem::updateUniformsForCustomMaterial(QSSGRhiShaderPipeline &shaderPipeline,
129 QSSGRhiContext *rhiCtx,
130 const QSSGLayerRenderData &inData,
131 char *ubufData,
132 QSSGRhiGraphicsPipelineState *ps,
133 const QSSGRenderCustomMaterial &material,
134 QSSGSubsetRenderable &renderable,
135 const QSSGRenderCameraList &cameras,
136 const QVector2D *depthAdjust,
137 const QMatrix4x4 *alteredModelViewProjection)
138{
139 QSSGRenderMvpArray alteredMvpList;
140 if (alteredModelViewProjection)
141 alteredMvpList[0] = *alteredModelViewProjection;
142
143 const QMatrix4x4 clipSpaceCorrMatrix = rhiCtx->rhi()->clipSpaceCorrMatrix();
144 QRhiTexture *lightmapTexture = inData.getLightmapTexture(modelContext: renderable.modelContext);
145
146 const auto &modelNode = renderable.modelContext.model;
147 const QMatrix4x4 &localInstanceTransform(modelNode.localInstanceTransform);
148 const QMatrix4x4 &globalInstanceTransform(modelNode.globalInstanceTransform);
149
150 const auto &defaultMaterialShaderKeyProperties = inData.getDefaultMaterialPropertyTable();
151
152 const QMatrix4x4 &modelMatrix(modelNode.usesBoneTexture() ? QMatrix4x4() : renderable.globalTransform);
153
154 QSSGMaterialShaderGenerator::setRhiMaterialProperties(*context,
155 shaders&: shaderPipeline,
156 ubufData,
157 inPipelineState: ps,
158 inMaterial: material,
159 inKey: renderable.shaderDescription,
160 inProperties: defaultMaterialShaderKeyProperties,
161 inCameras: cameras,
162 inModelViewProjections: alteredModelViewProjection ? alteredMvpList : renderable.modelContext.modelViewProjections,
163 inNormalMatrix: renderable.modelContext.normalMatrix,
164 inGlobalTransform: modelMatrix,
165 clipSpaceCorrMatrix,
166 localInstanceTransform,
167 globalInstanceTransform,
168 inMorphWeights: toDataView(type: modelNode.morphWeights),
169 inFirstImage: renderable.firstImage,
170 inOpacity: renderable.opacity,
171 inRenderProperties: inData,
172 inLights: renderable.lights,
173 reflectionProbe: renderable.reflectionProbe,
174 receivesShadows: true,
175 receivesReflections: renderable.renderableFlags.receivesReflections(),
176 shadowDepthAdjust: depthAdjust,
177 lightmapTexture);
178}
179
180static const QRhiShaderResourceBinding::StageFlags CUSTOM_MATERIAL_VISIBILITY_ALL =
181 QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage;
182
183void QSSGCustomMaterialSystem::rhiPrepareRenderable(QSSGRhiGraphicsPipelineState *ps,
184 QSSGPassKey passKey,
185 QSSGSubsetRenderable &renderable,
186 const QSSGShaderFeatures &featureSet,
187 const QSSGRenderCustomMaterial &material,
188 const QSSGLayerRenderData &layerData,
189 QRhiRenderPassDescriptor *renderPassDescriptor,
190 int samples,
191 int viewCount,
192 QSSGRenderCamera *alteredCamera,
193 QSSGRenderTextureCubeFace cubeFace,
194 QMatrix4x4 *alteredModelViewProjection,
195 QSSGReflectionMapEntry *entry)
196{
197 QSSGRhiContext *rhiCtx = context->rhiContext().get();
198
199 QRhiGraphicsPipeline::TargetBlend blend; // no blending by default
200 if (material.m_renderFlags.testFlag(flag: QSSGRenderCustomMaterial::RenderFlag::Blending)) {
201 blend.enable = true;
202 blend.srcColor = material.m_srcBlend;
203 blend.srcAlpha = material.m_srcAlphaBlend;
204 blend.dstColor = material.m_dstBlend;
205 blend.dstAlpha = material.m_dstAlphaBlend;
206 }
207
208 const QSSGCullFaceMode cullMode = material.m_cullMode;
209
210 const auto &defaultMaterialShaderKeyProperties = layerData.getDefaultMaterialPropertyTable();
211
212 const bool blendParticles = defaultMaterialShaderKeyProperties.m_blendParticles.getValue(inDataStore: renderable.shaderDescription);
213
214 const auto &shaderPipeline = shadersForCustomMaterial(ps, material, renderable, defaultMaterialShaderKeyProperties, featureSet);
215
216 if (shaderPipeline) {
217 QSSGRhiShaderResourceBindingList bindings;
218 const auto &modelNode = renderable.modelContext.model;
219
220 // NOTE:
221 // - entryIdx should 0 for QSSGRenderTextureCubeFaceNone.
222 // In all other cases the entryIdx is a combination of the cubeface idx and the subset offset, where the lower bits
223 // are the cubeface idx.
224 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(face: cubeFace);
225 const quintptr entryIdx = quintptr(cubeFace != QSSGRenderTextureCubeFaceNone) * (cubeFaceIdx + (quintptr(renderable.subset.offset) << 3));
226 // As the entry might be null we create an entry key consisting of the entry and the material.
227 const auto entryPartA = reinterpret_cast<quintptr>(&material);
228 const auto entryPartB = reinterpret_cast<quintptr>(entry);
229 const void *entryKey = reinterpret_cast<const void *>(entryPartA ^ entryPartB);
230
231 QSSGRhiDrawCallData &dcd = QSSGRhiContextPrivate::get(q: rhiCtx)->drawCallData(key: { .cid: passKey, .model: &modelNode, .entry: entryKey, .entryIdx: entryIdx });
232
233 shaderPipeline->ensureCombinedUniformBuffer(ubuf: &dcd.ubuf);
234 char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
235 if (!alteredCamera) {
236 updateUniformsForCustomMaterial(shaderPipeline&: *shaderPipeline, rhiCtx, inData: layerData, ubufData, ps, material, renderable, cameras: layerData.renderedCameras, depthAdjust: nullptr, alteredModelViewProjection: nullptr);
237 } else {
238 QSSGRenderCameraList cameras({ alteredCamera });
239 updateUniformsForCustomMaterial(shaderPipeline&: *shaderPipeline, rhiCtx, inData: layerData, ubufData, ps, material, renderable, cameras, depthAdjust: nullptr, alteredModelViewProjection);
240 }
241 if (blendParticles)
242 QSSGParticleRenderer::updateUniformsForParticleModel(shaderPipeline&: *shaderPipeline, ubufData, model: &renderable.modelContext.model, offset: renderable.subset.offset);
243 dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
244
245 if (blendParticles)
246 QSSGParticleRenderer::prepareParticlesForModel(shaderPipeline&: *shaderPipeline, rhiCtx, bindings, model: &renderable.modelContext.model);
247 bool instancing = false;
248 if (!alteredCamera) {
249 const QSSGRenderCameraDataList &cameraDatas(*layerData.renderedCameraData);
250 instancing = QSSGLayerRenderData::prepareInstancing(rhiCtx, renderable: &renderable, cameraDirection: cameraDatas[0].direction, cameraPosition: cameraDatas[0].position, minThreshold: renderable.instancingLodMin, maxThreshold: renderable.instancingLodMax);
251 } else {
252 instancing = QSSGLayerRenderData::prepareInstancing(rhiCtx, renderable: &renderable, cameraDirection: alteredCamera->getScalingCorrectDirection(), cameraPosition: alteredCamera->getGlobalPos(), minThreshold: renderable.instancingLodMin, maxThreshold: renderable.instancingLodMax);
253 }
254
255 ps->samples = samples;
256 ps->viewCount = viewCount;
257
258 ps->cullMode = QSSGRhiHelpers::toCullMode(cullFaceMode: cullMode);
259
260 ps->targetBlend = blend;
261
262 auto &ia = QSSGRhiInputAssemblerStatePrivate::get(ps&: *ps);
263
264 ia = renderable.subset.rhi.ia;
265
266 //### Copied code from default materials
267 int instanceBufferBinding = 0;
268 if (instancing) {
269 // Need to setup new bindings for instanced buffers
270 const quint32 stride = renderable.modelContext.model.instanceTable->stride();
271 QVarLengthArray<QRhiVertexInputBinding, 8> bindings;
272 std::copy(first: ia.inputLayout.cbeginBindings(),
273 last: ia.inputLayout.cendBindings(),
274 result: std::back_inserter(x&: bindings));
275 bindings.append(t: { stride, QRhiVertexInputBinding::PerInstance });
276 instanceBufferBinding = bindings.size() - 1;
277 ia.inputLayout.setBindings(first: bindings.cbegin(), last: bindings.cend());
278 }
279
280 QSSGRhiHelpers::bakeVertexInputLocations(ia: &ia, shaders: *shaderPipeline, instanceBufferBinding);
281
282 QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
283 QRhiTexture *dummyTexture = rhiCtx->dummyTexture(flags: {}, rub: resourceUpdates);
284 QRhiTexture *dummyCubeTexture = rhiCtx->dummyTexture(flags: QRhiTexture::CubeMap, rub: resourceUpdates);
285 rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
286
287 bindings.addUniformBuffer(binding: 0, stage: CUSTOM_MATERIAL_VISIBILITY_ALL, buf: dcd.ubuf, offset: 0, size: shaderPipeline->ub0Size());
288 bindings.addUniformBuffer(binding: 1, stage: CUSTOM_MATERIAL_VISIBILITY_ALL, buf: dcd.ubuf,
289 offset: shaderPipeline->ub0LightDataOffset(),
290 size: shaderPipeline->ub0LightDataSize());
291 bindings.addUniformBuffer(binding: 2, stage: CUSTOM_MATERIAL_VISIBILITY_ALL, buf: dcd.ubuf,
292 offset: shaderPipeline->ub0ShadowDataOffset(),
293 size: shaderPipeline->ub0ShadowDataSize());
294
295 QVector<QShaderDescription::InOutVariable> samplerVars =
296 shaderPipeline->fragmentStage()->shader().description().combinedImageSamplers();
297 for (const QShaderDescription::InOutVariable &var : shaderPipeline->vertexStage()->shader().description().combinedImageSamplers()) {
298 auto it = std::find_if(first: samplerVars.cbegin(), last: samplerVars.cend(),
299 pred: [&var](const QShaderDescription::InOutVariable &v) { return var.binding == v.binding; });
300 if (it == samplerVars.cend())
301 samplerVars.append(t: var);
302 }
303
304 int maxSamplerBinding = -1;
305 for (const QShaderDescription::InOutVariable &var : samplerVars)
306 maxSamplerBinding = qMax(a: maxSamplerBinding, b: var.binding);
307
308 // Will need to set unused image-samplers to something dummy
309 // because the shader code contains all custom property textures,
310 // and not providing a binding for all of them is invalid with some
311 // graphics APIs (and will need a real texture because setting a
312 // null handle or similar is not permitted with some of them so the
313 // srb does not accept null QRhiTextures either; but first let's
314 // figure out what bindings are unused in this frame)
315 QBitArray samplerBindingsSpecified(maxSamplerBinding + 1);
316
317 if (blendParticles)
318 samplerBindingsSpecified.setBit(shaderPipeline->bindingForTexture(name: "qt_particleTexture"));
319
320 // Skinning
321 if (QRhiTexture *boneTexture = layerData.getBonemapTexture(modelContext: renderable.modelContext)) {
322 int binding = shaderPipeline->bindingForTexture(name: "qt_boneTexture");
323 if (binding >= 0) {
324 QRhiSampler *boneSampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Nearest,
325 .magFilter: QRhiSampler::Nearest,
326 .mipmap: QRhiSampler::None,
327 .hTiling: QRhiSampler::ClampToEdge,
328 .vTiling: QRhiSampler::ClampToEdge,
329 .zTiling: QRhiSampler::Repeat
330 });
331 bindings.addTexture(binding,
332 stage: QRhiShaderResourceBinding::VertexStage,
333 tex: boneTexture,
334 sampler: boneSampler);
335 samplerBindingsSpecified.setBit(binding);
336 }
337 }
338
339 // Morphing
340 auto *targetsTexture = renderable.subset.rhi.targetsTexture;
341 if (targetsTexture) {
342 int binding = shaderPipeline->bindingForTexture(name: "qt_morphTargetTexture");
343 if (binding >= 0) {
344 QRhiSampler *targetsSampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Nearest,
345 .magFilter: QRhiSampler::Nearest,
346 .mipmap: QRhiSampler::None,
347 .hTiling: QRhiSampler::ClampToEdge,
348 .vTiling: QRhiSampler::ClampToEdge,
349 .zTiling: QRhiSampler::ClampToEdge
350 });
351 bindings.addTexture(binding, stage: QRhiShaderResourceBinding::VertexStage, tex: renderable.subset.rhi.targetsTexture, sampler: targetsSampler);
352 samplerBindingsSpecified.setBit(binding);
353 }
354 }
355
356 // Prioritize reflection texture over Light Probe texture because
357 // reflection texture also contains the irradiance and pre filtered
358 // values for the light probe.
359 if (featureSet.isSet(feature: QSSGShaderFeatures::Feature::ReflectionProbe)) {
360 int reflectionSampler = shaderPipeline->bindingForTexture(name: "qt_reflectionMap");
361 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: QRhiSampler::Linear,
362 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat });
363 QRhiTexture* reflectionTexture = layerData.getReflectionMapManager()->reflectionMapEntry(probeIdx: renderable.reflectionProbeIndex)->m_rhiPrefilteredCube;
364 if (reflectionSampler >= 0 && reflectionTexture) {
365 bindings.addTexture(binding: reflectionSampler, stage: QRhiShaderResourceBinding::FragmentStage, tex: reflectionTexture, sampler);
366 samplerBindingsSpecified.setBit(reflectionSampler);
367 }
368 } else if (shaderPipeline->lightProbeTexture()) {
369 int binding = shaderPipeline->bindingForTexture(name: "qt_lightProbe", hint: int(QSSGRhiSamplerBindingHints::LightProbe));
370 if (binding >= 0) {
371 samplerBindingsSpecified.setBit(binding);
372 QPair<QSSGRenderTextureCoordOp, QSSGRenderTextureCoordOp> tiling = shaderPipeline->lightProbeTiling();
373 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: QRhiSampler::Linear, // enables mipmapping
374 .hTiling: QSSGRhiHelpers::toRhi(tiling: tiling.first), .vTiling: QSSGRhiHelpers::toRhi(tiling: tiling.second), .zTiling: QRhiSampler::Repeat });
375 bindings.addTexture(binding,
376 stage: QRhiShaderResourceBinding::FragmentStage,
377 tex: shaderPipeline->lightProbeTexture(), sampler);
378 } // else ignore, not an error (for example, an unshaded material's fragment shader will not have this sampler)
379 }
380
381 if (shaderPipeline->screenTexture()) {
382 const int screenTextureBinding = shaderPipeline->bindingForTexture(name: "qt_screenTexture", hint: int(QSSGRhiSamplerBindingHints::ScreenTexture));
383 const int screenTextureArrayBinding = shaderPipeline->bindingForTexture(name: "qt_screenTextureArray", hint: int(QSSGRhiSamplerBindingHints::ScreenTextureArray));
384 if (screenTextureBinding >= 0 || screenTextureArrayBinding >= 0) {
385 // linear min/mag, mipmap filtering depends on the
386 // texture, with SCREEN_TEXTURE there are no mipmaps, but
387 // once SCREEN_MIP_TEXTURE is seen the texture (the same
388 // one) has mipmaps generated.
389 QRhiSampler::Filter mipFilter = shaderPipeline->screenTexture()->flags().testFlag(flag: QRhiTexture::MipMapped)
390 ? QRhiSampler::Linear : QRhiSampler::None;
391 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: mipFilter,
392 .hTiling: QRhiSampler::Repeat, .vTiling: QRhiSampler::Repeat, .zTiling: QRhiSampler::Repeat });
393 if (screenTextureBinding >= 0) {
394 samplerBindingsSpecified.setBit(screenTextureBinding);
395 bindings.addTexture(binding: screenTextureBinding,
396 stage: QRhiShaderResourceBinding::FragmentStage,
397 tex: shaderPipeline->screenTexture(), sampler);
398 }
399 if (screenTextureArrayBinding >= 0) {
400 samplerBindingsSpecified.setBit(screenTextureArrayBinding);
401 bindings.addTexture(binding: screenTextureArrayBinding,
402 stage: QRhiShaderResourceBinding::FragmentStage,
403 tex: shaderPipeline->screenTexture(), sampler);
404 }
405 } // else ignore, not an error
406 }
407
408 if (shaderPipeline->depthTexture()) {
409 const int depthTextureBinding = shaderPipeline->bindingForTexture(name: "qt_depthTexture", hint: int(QSSGRhiSamplerBindingHints::DepthTexture));
410 const int depthTextureArrayBinding = shaderPipeline->bindingForTexture(name: "qt_depthTextureArray", hint: int(QSSGRhiSamplerBindingHints::DepthTextureArray));
411 if (depthTextureBinding >= 0 || depthTextureArrayBinding >= 0) {
412 // nearest min/mag, no mipmap
413 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Nearest, .magFilter: QRhiSampler::Nearest, .mipmap: QRhiSampler::None,
414 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat });
415 if (depthTextureBinding >= 0) {
416 samplerBindingsSpecified.setBit(depthTextureBinding);
417 bindings.addTexture(binding: depthTextureBinding,
418 stage: QRhiShaderResourceBinding::FragmentStage,
419 tex: shaderPipeline->depthTexture(), sampler);
420 }
421 if (depthTextureArrayBinding >= 0) {
422 samplerBindingsSpecified.setBit(depthTextureArrayBinding);
423 bindings.addTexture(binding: depthTextureArrayBinding,
424 stage: QRhiShaderResourceBinding::FragmentStage,
425 tex: shaderPipeline->depthTexture(), sampler);
426 }
427 } // else ignore, not an error
428 }
429
430 if (shaderPipeline->ssaoTexture()) {
431 const int ssaoTextureBinding = shaderPipeline->bindingForTexture(name: "qt_aoTexture", hint: int(QSSGRhiSamplerBindingHints::AoTexture));
432 const int ssaoTextureArrayBinding = shaderPipeline->bindingForTexture(name: "qt_aoTextureArray", hint: int(QSSGRhiSamplerBindingHints::AoTextureArray));
433 if (ssaoTextureBinding >= 0 || ssaoTextureArrayBinding >= 0) {
434 // linear min/mag, no mipmap
435 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: QRhiSampler::None,
436 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat });
437 if (ssaoTextureBinding >= 0) {
438 samplerBindingsSpecified.setBit(ssaoTextureBinding);
439 bindings.addTexture(binding: ssaoTextureBinding,
440 stage: QRhiShaderResourceBinding::FragmentStage,
441 tex: shaderPipeline->ssaoTexture(), sampler);
442 }
443 if (ssaoTextureArrayBinding >= 0) {
444 samplerBindingsSpecified.setBit(ssaoTextureArrayBinding);
445 bindings.addTexture(binding: ssaoTextureArrayBinding,
446 stage: QRhiShaderResourceBinding::FragmentStage,
447 tex: shaderPipeline->ssaoTexture(), sampler);
448 }
449 } // else ignore, not an error
450 }
451
452 if (shaderPipeline->lightmapTexture()) {
453 int binding = shaderPipeline->bindingForTexture(name: "qt_lightmap", hint: int(QSSGRhiSamplerBindingHints::LightmapTexture));
454 if (binding >= 0) {
455 samplerBindingsSpecified.setBit(binding);
456 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: QRhiSampler::None,
457 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat });
458 bindings.addTexture(binding,
459 stage: QRhiShaderResourceBinding::FragmentStage,
460 tex: shaderPipeline->lightmapTexture(), sampler);
461 } // else ignore, not an error
462 }
463
464 const int shadowMapCount = shaderPipeline->shadowMapCount();
465 for (int i = 0; i < shadowMapCount; ++i) {
466 QSSGRhiShadowMapProperties &shadowMapProperties(shaderPipeline->shadowMapAt(index: i));
467 QRhiTexture *texture = shadowMapProperties.shadowMapTexture;
468 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: QRhiSampler::None,
469 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat });
470 const QByteArray &name(shadowMapProperties.shadowMapTextureUniformName);
471 if (shadowMapProperties.cachedBinding < 0)
472 shadowMapProperties.cachedBinding = shaderPipeline->bindingForTexture(name);
473 if (shadowMapProperties.cachedBinding < 0) // may not be used in the shader with unshaded custom materials, that's normal
474 continue;
475 samplerBindingsSpecified.setBit(shadowMapProperties.cachedBinding);
476 bindings.addTexture(binding: shadowMapProperties.cachedBinding,
477 stage: QRhiShaderResourceBinding::FragmentStage,
478 tex: texture,
479 sampler);
480 }
481
482 QSSGRenderableImage *renderableImage = renderable.firstImage;
483 while (renderableImage) {
484 const char *samplerName = QSSGMaterialShaderGenerator::getSamplerName(type: renderableImage->m_mapType);
485 const int samplerHint = int(renderableImage->m_mapType);
486 int samplerBinding = shaderPipeline->bindingForTexture(name: samplerName, hint: samplerHint);
487 if (samplerBinding >= 0) {
488 QRhiTexture *texture = renderableImage->m_texture.m_texture;
489 if (samplerBinding >= 0 && texture) {
490 const bool mipmapped = texture->flags().testFlag(flag: QRhiTexture::MipMapped);
491 QSSGRhiSamplerDescription samplerDesc = {
492 .minFilter: QSSGRhiHelpers::toRhi(op: renderableImage->m_imageNode.m_minFilterType),
493 .magFilter: QSSGRhiHelpers::toRhi(op: renderableImage->m_imageNode.m_magFilterType),
494 .mipmap: mipmapped ? QSSGRhiHelpers::toRhi(op: renderableImage->m_imageNode.m_mipFilterType) : QRhiSampler::None,
495 .hTiling: QSSGRhiHelpers::toRhi(tiling: renderableImage->m_imageNode.m_horizontalTilingMode),
496 .vTiling: QSSGRhiHelpers::toRhi(tiling: renderableImage->m_imageNode.m_verticalTilingMode),
497 .zTiling: QSSGRhiHelpers::toRhi(tiling: renderableImage->m_imageNode.m_depthTilingMode)
498 };
499 rhiCtx->checkAndAdjustForNPoT(texture, samplerDescription: &samplerDesc);
500 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: samplerDesc);
501 samplerBindingsSpecified.setBit(samplerBinding);
502 bindings.addTexture(binding: samplerBinding,
503 stage: CUSTOM_MATERIAL_VISIBILITY_ALL,
504 tex: texture, sampler);
505 }
506 } // else this is not necessarily an error, e.g. having metalness/roughness maps with metalness disabled
507 renderableImage = renderableImage->m_nextImage;
508 }
509
510 if (maxSamplerBinding >= 0) {
511 // custom property textures
512 int customTexCount = shaderPipeline->extraTextureCount();
513 for (int i = 0; i < customTexCount; ++i) {
514 QSSGRhiTexture &t(shaderPipeline->extraTextureAt(index: i));
515 const int samplerBinding = shaderPipeline->bindingForTexture(name: t.name);
516 if (samplerBinding >= 0) {
517 samplerBindingsSpecified.setBit(samplerBinding);
518 rhiCtx->checkAndAdjustForNPoT(texture: t.texture, samplerDescription: &t.samplerDesc);
519 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: t.samplerDesc);
520 bindings.addTexture(binding: samplerBinding,
521 stage: CUSTOM_MATERIAL_VISIBILITY_ALL,
522 tex: t.texture,
523 sampler);
524 }
525 }
526
527 // use a dummy texture for the unused samplers in the shader
528 QRhiSampler *dummySampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Nearest, .magFilter: QRhiSampler::Nearest, .mipmap: QRhiSampler::None,
529 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat });
530
531 for (const QShaderDescription::InOutVariable &var : samplerVars) {
532 if (!samplerBindingsSpecified.testBit(i: var.binding)) {
533 QRhiTexture *t = var.type == QShaderDescription::SamplerCube ? dummyCubeTexture : dummyTexture;
534 bindings.addTexture(binding: var.binding, stage: CUSTOM_MATERIAL_VISIBILITY_ALL, tex: t, sampler: dummySampler);
535 }
536 }
537 }
538
539 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(q: rhiCtx);
540
541 // do the same srb lookup acceleration as default materials
542 QRhiShaderResourceBindings *&srb = dcd.srb;
543 bool srbChanged = false;
544 if (!srb || bindings != dcd.bindings) {
545 srb = rhiCtxD->srb(bindings);
546 rhiCtxD->releaseCachedSrb(bindings&: dcd.bindings);
547 dcd.bindings = bindings;
548 srbChanged = true;
549 }
550
551 if (cubeFace == QSSGRenderTextureCubeFaceNone)
552 renderable.rhiRenderData.mainPass.srb = srb;
553 else
554 renderable.rhiRenderData.reflectionPass.srb[cubeFaceIdx] = srb;
555
556 const auto pipelineKey = QSSGGraphicsPipelineStateKey::create(state: *ps, rpDesc: renderPassDescriptor, srb);
557 if (dcd.pipeline
558 && !srbChanged
559 && dcd.renderTargetDescriptionHash == pipelineKey.extra.renderTargetDescriptionHash
560 && dcd.renderTargetDescription == pipelineKey.renderTargetDescription
561 && dcd.ps == *ps)
562 {
563 if (cubeFace == QSSGRenderTextureCubeFaceNone)
564 renderable.rhiRenderData.mainPass.pipeline = dcd.pipeline;
565 else
566 renderable.rhiRenderData.reflectionPass.pipeline = dcd.pipeline;
567 } else {
568 if (cubeFace == QSSGRenderTextureCubeFaceNone) {
569 renderable.rhiRenderData.mainPass.pipeline = rhiCtxD->pipeline(key: pipelineKey,
570 rpDesc: renderPassDescriptor,
571 srb);
572 dcd.pipeline = renderable.rhiRenderData.mainPass.pipeline;
573 } else {
574 renderable.rhiRenderData.reflectionPass.pipeline = rhiCtxD->pipeline(key: pipelineKey,
575 rpDesc: renderPassDescriptor,
576 srb);
577 dcd.pipeline = renderable.rhiRenderData.reflectionPass.pipeline;
578 }
579
580 dcd.renderTargetDescriptionHash = pipelineKey.extra.renderTargetDescriptionHash;
581 dcd.renderTargetDescription = pipelineKey.renderTargetDescription;
582 dcd.ps = *ps;
583 }
584 }
585}
586
587void QSSGCustomMaterialSystem::setShaderResources(char *ubufData,
588 const QSSGRenderCustomMaterial &inMaterial,
589 const QByteArray &inPropertyName,
590 const QVariant &propertyValue,
591 QSSGRenderShaderValue::Type inPropertyType,
592 QSSGRhiShaderPipeline &shaderPipeline)
593{
594 Q_UNUSED(inMaterial);
595
596 if (inPropertyType == QSSGRenderShaderValue::Texture) {
597 QSSGRenderCustomMaterial::TextureProperty *textureProperty =
598 reinterpret_cast<QSSGRenderCustomMaterial::TextureProperty *>(propertyValue.value<void *>());
599 QSSGRenderImage *image = textureProperty->texImage;
600 if (image) {
601 const auto &theBufferManager(context->bufferManager());
602 const QSSGRenderImageTexture texture = theBufferManager->loadRenderImage(image);
603 if (texture.m_texture) {
604 const QSSGRhiTexture t = {
605 .name: inPropertyName,
606 .texture: texture.m_texture,
607 .samplerDesc: { .minFilter: QSSGRhiHelpers::toRhi(op: textureProperty->minFilterType),
608 .magFilter: QSSGRhiHelpers::toRhi(op: textureProperty->magFilterType),
609 .mipmap: textureProperty->mipFilterType != QSSGRenderTextureFilterOp::None ? QSSGRhiHelpers::toRhi(op: textureProperty->mipFilterType) : QRhiSampler::None,
610 .hTiling: QSSGRhiHelpers::toRhi(tiling: textureProperty->horizontalClampType),
611 .vTiling: QSSGRhiHelpers::toRhi(tiling: textureProperty->verticalClampType),
612 .zTiling: QSSGRhiHelpers::toRhi(tiling: textureProperty->zClampType)
613 }
614 };
615 shaderPipeline.addExtraTexture(t);
616 }
617 }
618 } else {
619 shaderPipeline.setUniformValue(ubufData, name: inPropertyName, value: propertyValue, type: inPropertyType);
620 }
621}
622
623void QSSGCustomMaterialSystem::applyRhiShaderPropertyValues(char *ubufData,
624 const QSSGRenderCustomMaterial &material,
625 QSSGRhiShaderPipeline &shaderPipeline)
626{
627 const auto &properties = material.m_properties;
628 for (const auto &prop : properties)
629 setShaderResources(ubufData, inMaterial: material, inPropertyName: prop.name, propertyValue: prop.value, inPropertyType: prop.shaderDataType, shaderPipeline);
630
631 const auto textProps = material.m_textureProperties;
632 for (const auto &prop : textProps)
633 setShaderResources(ubufData, inMaterial: material, inPropertyName: prop.name, propertyValue: QVariant::fromValue(value: (void *)&prop), inPropertyType: prop.shaderDataType, shaderPipeline);
634}
635
636void QSSGCustomMaterialSystem::rhiRenderRenderable(QSSGRhiContext *rhiCtx,
637 QSSGSubsetRenderable &renderable,
638 bool *needsSetViewport,
639 QSSGRenderTextureCubeFace cubeFace,
640 const QSSGRhiGraphicsPipelineState &state)
641{
642 QRhiGraphicsPipeline *ps = renderable.rhiRenderData.mainPass.pipeline;
643 QRhiShaderResourceBindings *srb = renderable.rhiRenderData.mainPass.srb;
644
645 if (cubeFace != QSSGRenderTextureCubeFaceNone) {
646 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(face: cubeFace);
647 ps = renderable.rhiRenderData.reflectionPass.pipeline;
648 srb = renderable.rhiRenderData.reflectionPass.srb[cubeFaceIdx];
649 }
650
651 if (!ps || !srb)
652 return;
653
654 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
655 QRhiBuffer *vertexBuffer = renderable.subset.rhi.vertexBuffer->buffer();
656 QRhiBuffer *indexBuffer = renderable.subset.rhi.indexBuffer ? renderable.subset.rhi.indexBuffer->buffer() : nullptr;
657
658 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
659 cb->setGraphicsPipeline(ps);
660 cb->setShaderResources(srb);
661
662 if (*needsSetViewport) {
663 cb->setViewport(state.viewport);
664 *needsSetViewport = false;
665 }
666
667 QRhiCommandBuffer::VertexInput vertexBuffers[2];
668 int vertexBufferCount = 1;
669 vertexBuffers[0] = QRhiCommandBuffer::VertexInput(vertexBuffer, 0);
670 quint32 instances = 1;
671 if (renderable.modelContext.model.instancing()) {
672 instances = renderable.modelContext.model.instanceCount();
673 vertexBuffers[1] = QRhiCommandBuffer::VertexInput(renderable.instanceBuffer, 0);
674 vertexBufferCount = 2;
675 }
676 if (indexBuffer) {
677 cb->setVertexInput(startBinding: 0, bindingCount: vertexBufferCount, bindings: vertexBuffers, indexBuf: indexBuffer, indexOffset: 0, indexFormat: renderable.subset.rhi.indexBuffer->indexFormat());
678 cb->drawIndexed(indexCount: renderable.subset.count, instanceCount: instances, firstIndex: renderable.subset.offset);
679 QSSGRHICTX_STAT(rhiCtx, drawIndexed(renderable.subset.count, instances));
680 } else {
681 cb->setVertexInput(startBinding: 0, bindingCount: vertexBufferCount, bindings: vertexBuffers);
682 cb->draw(vertexCount: renderable.subset.count, instanceCount: instances, firstVertex: renderable.subset.offset);
683 QSSGRHICTX_STAT(rhiCtx, draw(renderable.subset.count, instances));
684 }
685 Q_QUICK3D_PROFILE_END_WITH_IDS(QQuick3DProfiler::Quick3DRenderCall, (renderable.subset.count | quint64(instances) << 32),
686 QVector<int>({renderable.modelContext.model.profilingId,
687 renderable.material.profilingId}));
688}
689
690QT_END_NAMESPACE
691

source code of qtquick3d/src/runtimerender/qssgrhicustommaterialsystem.cpp