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 auto &[localInstanceTransform, globalInstanceTransform] = inData.getInstanceTransforms(node: modelNode);
148
149 const auto &defaultMaterialShaderKeyProperties = inData.getDefaultMaterialPropertyTable();
150
151 const QMatrix4x4 &modelMatrix(modelNode.usesBoneTexture() ? QMatrix4x4() : renderable.modelContext.globalTransform);
152
153 QSSGMaterialShaderGenerator::setRhiMaterialProperties(*context,
154 shaders&: shaderPipeline,
155 ubufData,
156 inPipelineState: ps,
157 inMaterial: material,
158 inKey: renderable.shaderDescription,
159 inProperties: defaultMaterialShaderKeyProperties,
160 inCameras: cameras,
161 inModelViewProjections: alteredModelViewProjection ? alteredMvpList : renderable.modelContext.modelViewProjections,
162 inNormalMatrix: renderable.modelContext.normalMatrix,
163 inGlobalTransform: modelMatrix,
164 clipSpaceCorrMatrix,
165 localInstanceTransform,
166 globalInstanceTransform,
167 inMorphWeights: toDataView(type: modelNode.morphWeights),
168 inFirstImage: renderable.firstImage,
169 inOpacity: renderable.opacity,
170 inRenderProperties: inData,
171 inLights: renderable.lights,
172 reflectionProbe: renderable.reflectionProbe,
173 receivesShadows: true,
174 receivesReflections: renderable.renderableFlags.receivesReflections(),
175 shadowDepthAdjust: depthAdjust,
176 lightmapTexture);
177}
178
179static const QRhiShaderResourceBinding::StageFlags CUSTOM_MATERIAL_VISIBILITY_ALL =
180 QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage;
181
182void QSSGCustomMaterialSystem::rhiPrepareRenderable(QSSGRhiGraphicsPipelineState *ps,
183 QSSGPassKey passKey,
184 QSSGSubsetRenderable &renderable,
185 const QSSGShaderFeatures &featureSet,
186 const QSSGRenderCustomMaterial &material,
187 const QSSGLayerRenderData &layerData,
188 QRhiRenderPassDescriptor *renderPassDescriptor,
189 int samples,
190 int viewCount,
191 QSSGRenderCamera *alteredCamera,
192 QSSGRenderTextureCubeFace cubeFace,
193 QMatrix4x4 *alteredModelViewProjection,
194 QSSGReflectionMapEntry *entry,
195 bool oit)
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 const auto &altCamTransform = layerData.getGlobalTransform(node: *alteredCamera);
253 instancing = QSSGLayerRenderData::prepareInstancing(rhiCtx, renderable: &renderable, cameraDirection: QSSGRenderNode::getScalingCorrectDirection(globalTransform: altCamTransform), cameraPosition: QSSGRenderNode::getGlobalPos(globalTransform: altCamTransform), minThreshold: renderable.instancingLodMin, maxThreshold: renderable.instancingLodMax);
254 }
255
256 ps->samples = samples;
257 ps->viewCount = viewCount;
258
259 ps->cullMode = QSSGRhiHelpers::toCullMode(cullFaceMode: cullMode);
260
261 if (!oit)
262 ps->targetBlend[0] = blend;
263
264 auto &ia = QSSGRhiInputAssemblerStatePrivate::get(ps&: *ps);
265
266 ia = renderable.subset.rhi.ia;
267
268 //### Copied code from default materials
269 int instanceBufferBinding = 0;
270 if (instancing) {
271 // Need to setup new bindings for instanced buffers
272 const quint32 stride = renderable.modelContext.model.instanceTable->stride();
273 QVarLengthArray<QRhiVertexInputBinding, 8> bindings;
274 std::copy(first: ia.inputLayout.cbeginBindings(),
275 last: ia.inputLayout.cendBindings(),
276 result: std::back_inserter(x&: bindings));
277 bindings.append(t: { stride, QRhiVertexInputBinding::PerInstance });
278 instanceBufferBinding = bindings.size() - 1;
279 ia.inputLayout.setBindings(first: bindings.cbegin(), last: bindings.cend());
280 }
281
282 QSSGRhiHelpers::bakeVertexInputLocations(ia: &ia, shaders: *shaderPipeline, instanceBufferBinding);
283
284 QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
285 QRhiTexture *dummyTexture = rhiCtx->dummyTexture(flags: {}, rub: resourceUpdates);
286 QRhiTexture *dummyCubeTexture = rhiCtx->dummyTexture(flags: QRhiTexture::CubeMap, rub: resourceUpdates);
287 rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
288
289 bindings.addUniformBuffer(binding: 0, stage: CUSTOM_MATERIAL_VISIBILITY_ALL, buf: dcd.ubuf, offset: 0, size: shaderPipeline->ub0Size());
290 bindings.addUniformBuffer(binding: 1, stage: CUSTOM_MATERIAL_VISIBILITY_ALL, buf: dcd.ubuf,
291 offset: shaderPipeline->ub0LightDataOffset(),
292 size: shaderPipeline->ub0LightDataSize());
293 bindings.addUniformBuffer(binding: 2, stage: CUSTOM_MATERIAL_VISIBILITY_ALL, buf: dcd.ubuf,
294 offset: shaderPipeline->ub0ShadowDataOffset(),
295 size: shaderPipeline->ub0ShadowDataSize());
296
297 QVector<QShaderDescription::InOutVariable> samplerVars =
298 shaderPipeline->fragmentStage()->shader().description().combinedImageSamplers();
299 for (const QShaderDescription::InOutVariable &var : shaderPipeline->vertexStage()->shader().description().combinedImageSamplers()) {
300 auto it = std::find_if(first: samplerVars.cbegin(), last: samplerVars.cend(),
301 pred: [&var](const QShaderDescription::InOutVariable &v) { return var.binding == v.binding; });
302 if (it == samplerVars.cend())
303 samplerVars.append(t: var);
304 }
305
306 int maxSamplerBinding = -1;
307 for (const QShaderDescription::InOutVariable &var : samplerVars)
308 maxSamplerBinding = qMax(a: maxSamplerBinding, b: var.binding);
309
310 // Will need to set unused image-samplers to something dummy
311 // because the shader code contains all custom property textures,
312 // and not providing a binding for all of them is invalid with some
313 // graphics APIs (and will need a real texture because setting a
314 // null handle or similar is not permitted with some of them so the
315 // srb does not accept null QRhiTextures either; but first let's
316 // figure out what bindings are unused in this frame)
317 QBitArray samplerBindingsSpecified(maxSamplerBinding + 1);
318
319 if (blendParticles)
320 samplerBindingsSpecified.setBit(shaderPipeline->bindingForTexture(name: "qt_particleTexture"));
321
322 // Skinning
323 if (QRhiTexture *boneTexture = layerData.getBonemapTexture(modelContext: renderable.modelContext)) {
324 int binding = shaderPipeline->bindingForTexture(name: "qt_boneTexture");
325 if (binding >= 0) {
326 QRhiSampler *boneSampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Nearest,
327 .magFilter: QRhiSampler::Nearest,
328 .mipmap: QRhiSampler::None,
329 .hTiling: QRhiSampler::ClampToEdge,
330 .vTiling: QRhiSampler::ClampToEdge,
331 .zTiling: QRhiSampler::Repeat
332 });
333 bindings.addTexture(binding,
334 stage: QRhiShaderResourceBinding::VertexStage,
335 tex: boneTexture,
336 sampler: boneSampler);
337 samplerBindingsSpecified.setBit(binding);
338 }
339 }
340
341 // Morphing
342 auto *targetsTexture = renderable.subset.rhi.targetsTexture;
343 if (targetsTexture) {
344 int binding = shaderPipeline->bindingForTexture(name: "qt_morphTargetTexture");
345 if (binding >= 0) {
346 QRhiSampler *targetsSampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Nearest,
347 .magFilter: QRhiSampler::Nearest,
348 .mipmap: QRhiSampler::None,
349 .hTiling: QRhiSampler::ClampToEdge,
350 .vTiling: QRhiSampler::ClampToEdge,
351 .zTiling: QRhiSampler::ClampToEdge
352 });
353 bindings.addTexture(binding, stage: QRhiShaderResourceBinding::VertexStage, tex: renderable.subset.rhi.targetsTexture, sampler: targetsSampler);
354 samplerBindingsSpecified.setBit(binding);
355 }
356 }
357
358 // Prioritize reflection texture over Light Probe texture because
359 // reflection texture also contains the irradiance and pre filtered
360 // values for the light probe.
361 if (featureSet.isSet(feature: QSSGShaderFeatures::Feature::ReflectionProbe)) {
362 int reflectionSampler = shaderPipeline->bindingForTexture(name: "qt_reflectionMap");
363 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: QRhiSampler::Linear,
364 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat });
365 QRhiTexture* reflectionTexture = layerData.getReflectionMapManager()->reflectionMapEntry(probeIdx: renderable.reflectionProbeIndex)->m_rhiPrefilteredCube;
366 if (reflectionSampler >= 0 && reflectionTexture) {
367 bindings.addTexture(binding: reflectionSampler, stage: QRhiShaderResourceBinding::FragmentStage, tex: reflectionTexture, sampler);
368 samplerBindingsSpecified.setBit(reflectionSampler);
369 }
370 } else if (shaderPipeline->lightProbeTexture()) {
371 int binding = shaderPipeline->bindingForTexture(name: "qt_lightProbe", hint: int(QSSGRhiSamplerBindingHints::LightProbe));
372 if (binding >= 0) {
373 samplerBindingsSpecified.setBit(binding);
374 QPair<QSSGRenderTextureCoordOp, QSSGRenderTextureCoordOp> tiling = shaderPipeline->lightProbeTiling();
375 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: QRhiSampler::Linear, // enables mipmapping
376 .hTiling: QSSGRhiHelpers::toRhi(tiling: tiling.first), .vTiling: QSSGRhiHelpers::toRhi(tiling: tiling.second), .zTiling: QRhiSampler::Repeat });
377 bindings.addTexture(binding,
378 stage: QRhiShaderResourceBinding::FragmentStage,
379 tex: shaderPipeline->lightProbeTexture(), sampler);
380 } // else ignore, not an error (for example, an unshaded material's fragment shader will not have this sampler)
381 }
382
383 if (shaderPipeline->screenTexture()) {
384 const int screenTextureBinding = shaderPipeline->bindingForTexture(name: "qt_screenTexture", hint: int(QSSGRhiSamplerBindingHints::ScreenTexture));
385 const int screenTextureArrayBinding = shaderPipeline->bindingForTexture(name: "qt_screenTextureArray", hint: int(QSSGRhiSamplerBindingHints::ScreenTextureArray));
386 if (screenTextureBinding >= 0 || screenTextureArrayBinding >= 0) {
387 // linear min/mag, mipmap filtering depends on the
388 // texture, with SCREEN_TEXTURE there are no mipmaps, but
389 // once SCREEN_MIP_TEXTURE is seen the texture (the same
390 // one) has mipmaps generated.
391 QRhiSampler::Filter mipFilter = shaderPipeline->screenTexture()->flags().testFlag(flag: QRhiTexture::MipMapped)
392 ? QRhiSampler::Linear : QRhiSampler::None;
393 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: mipFilter,
394 .hTiling: QRhiSampler::Repeat, .vTiling: QRhiSampler::Repeat, .zTiling: QRhiSampler::Repeat });
395 if (screenTextureBinding >= 0) {
396 samplerBindingsSpecified.setBit(screenTextureBinding);
397 bindings.addTexture(binding: screenTextureBinding,
398 stage: QRhiShaderResourceBinding::FragmentStage,
399 tex: shaderPipeline->screenTexture(), sampler);
400 }
401 if (screenTextureArrayBinding >= 0) {
402 samplerBindingsSpecified.setBit(screenTextureArrayBinding);
403 bindings.addTexture(binding: screenTextureArrayBinding,
404 stage: QRhiShaderResourceBinding::FragmentStage,
405 tex: shaderPipeline->screenTexture(), sampler);
406 }
407 } // else ignore, not an error
408 }
409
410 if (shaderPipeline->depthTexture()) {
411 const int depthTextureBinding = shaderPipeline->bindingForTexture(name: "qt_depthTexture", hint: int(QSSGRhiSamplerBindingHints::DepthTexture));
412 const int depthTextureArrayBinding = shaderPipeline->bindingForTexture(name: "qt_depthTextureArray", hint: int(QSSGRhiSamplerBindingHints::DepthTextureArray));
413 if (depthTextureBinding >= 0 || depthTextureArrayBinding >= 0) {
414 // nearest min/mag, no mipmap
415 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Nearest, .magFilter: QRhiSampler::Nearest, .mipmap: QRhiSampler::None,
416 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat });
417 if (depthTextureBinding >= 0) {
418 samplerBindingsSpecified.setBit(depthTextureBinding);
419 bindings.addTexture(binding: depthTextureBinding,
420 stage: QRhiShaderResourceBinding::FragmentStage,
421 tex: shaderPipeline->depthTexture(), sampler);
422 }
423 if (depthTextureArrayBinding >= 0) {
424 samplerBindingsSpecified.setBit(depthTextureArrayBinding);
425 bindings.addTexture(binding: depthTextureArrayBinding,
426 stage: QRhiShaderResourceBinding::FragmentStage,
427 tex: shaderPipeline->depthTexture(), sampler);
428 }
429 } // else ignore, not an error
430 }
431
432 if (shaderPipeline->ssaoTexture()) {
433 const int ssaoTextureBinding = shaderPipeline->bindingForTexture(name: "qt_aoTexture", hint: int(QSSGRhiSamplerBindingHints::AoTexture));
434 const int ssaoTextureArrayBinding = shaderPipeline->bindingForTexture(name: "qt_aoTextureArray", hint: int(QSSGRhiSamplerBindingHints::AoTextureArray));
435 if (ssaoTextureBinding >= 0 || ssaoTextureArrayBinding >= 0) {
436 // linear min/mag, no mipmap
437 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: QRhiSampler::None,
438 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat });
439 if (ssaoTextureBinding >= 0) {
440 samplerBindingsSpecified.setBit(ssaoTextureBinding);
441 bindings.addTexture(binding: ssaoTextureBinding,
442 stage: QRhiShaderResourceBinding::FragmentStage,
443 tex: shaderPipeline->ssaoTexture(), sampler);
444 }
445 if (ssaoTextureArrayBinding >= 0) {
446 samplerBindingsSpecified.setBit(ssaoTextureArrayBinding);
447 bindings.addTexture(binding: ssaoTextureArrayBinding,
448 stage: QRhiShaderResourceBinding::FragmentStage,
449 tex: shaderPipeline->ssaoTexture(), sampler);
450 }
451 } // else ignore, not an error
452 }
453
454 if (shaderPipeline->lightmapTexture()) {
455 int binding = shaderPipeline->bindingForTexture(name: "qt_lightmap", hint: int(QSSGRhiSamplerBindingHints::LightmapTexture));
456 if (binding >= 0) {
457 samplerBindingsSpecified.setBit(binding);
458 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: QRhiSampler::None,
459 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat });
460 bindings.addTexture(binding,
461 stage: QRhiShaderResourceBinding::FragmentStage,
462 tex: shaderPipeline->lightmapTexture(), sampler);
463 } // else ignore, not an error
464 }
465
466 const int shadowMapCount = shaderPipeline->shadowMapCount();
467 for (int i = 0; i < shadowMapCount; ++i) {
468 QSSGRhiShadowMapProperties &shadowMapProperties(shaderPipeline->shadowMapAt(index: i));
469 QRhiTexture *texture = shadowMapProperties.shadowMapTexture;
470 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: QRhiSampler::None,
471 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat });
472 const QByteArray &name(shadowMapProperties.shadowMapTextureUniformName);
473 if (shadowMapProperties.cachedBinding < 0)
474 shadowMapProperties.cachedBinding = shaderPipeline->bindingForTexture(name);
475 if (shadowMapProperties.cachedBinding < 0) // may not be used in the shader with unshaded custom materials, that's normal
476 continue;
477 samplerBindingsSpecified.setBit(shadowMapProperties.cachedBinding);
478 bindings.addTexture(binding: shadowMapProperties.cachedBinding,
479 stage: QRhiShaderResourceBinding::FragmentStage,
480 tex: texture,
481 sampler);
482 }
483
484 QSSGRenderableImage *renderableImage = renderable.firstImage;
485 while (renderableImage) {
486 const char *samplerName = QSSGMaterialShaderGenerator::getSamplerName(type: renderableImage->m_mapType);
487 const int samplerHint = int(renderableImage->m_mapType);
488 int samplerBinding = shaderPipeline->bindingForTexture(name: samplerName, hint: samplerHint);
489 if (samplerBinding >= 0) {
490 QRhiTexture *texture = renderableImage->m_texture.m_texture;
491 if (samplerBinding >= 0 && texture) {
492 const bool mipmapped = texture->flags().testFlag(flag: QRhiTexture::MipMapped);
493 QSSGRhiSamplerDescription samplerDesc = {
494 .minFilter: QSSGRhiHelpers::toRhi(op: renderableImage->m_imageNode.m_minFilterType),
495 .magFilter: QSSGRhiHelpers::toRhi(op: renderableImage->m_imageNode.m_magFilterType),
496 .mipmap: mipmapped ? QSSGRhiHelpers::toRhi(op: renderableImage->m_imageNode.m_mipFilterType) : QRhiSampler::None,
497 .hTiling: QSSGRhiHelpers::toRhi(tiling: renderableImage->m_imageNode.m_horizontalTilingMode),
498 .vTiling: QSSGRhiHelpers::toRhi(tiling: renderableImage->m_imageNode.m_verticalTilingMode),
499 .zTiling: QSSGRhiHelpers::toRhi(tiling: renderableImage->m_imageNode.m_depthTilingMode)
500 };
501 rhiCtx->checkAndAdjustForNPoT(texture, samplerDescription: &samplerDesc);
502 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: samplerDesc);
503 samplerBindingsSpecified.setBit(samplerBinding);
504 bindings.addTexture(binding: samplerBinding,
505 stage: CUSTOM_MATERIAL_VISIBILITY_ALL,
506 tex: texture, sampler);
507 }
508 } // else this is not necessarily an error, e.g. having metalness/roughness maps with metalness disabled
509 renderableImage = renderableImage->m_nextImage;
510 }
511
512 if (maxSamplerBinding >= 0) {
513 // custom property textures
514 int customTexCount = shaderPipeline->extraTextureCount();
515 for (int i = 0; i < customTexCount; ++i) {
516 QSSGRhiTexture &t(shaderPipeline->extraTextureAt(index: i));
517 const int samplerBinding = shaderPipeline->bindingForTexture(name: t.name);
518 if (samplerBinding >= 0) {
519 samplerBindingsSpecified.setBit(samplerBinding);
520 rhiCtx->checkAndAdjustForNPoT(texture: t.texture, samplerDescription: &t.samplerDesc);
521 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: t.samplerDesc);
522 bindings.addTexture(binding: samplerBinding,
523 stage: CUSTOM_MATERIAL_VISIBILITY_ALL,
524 tex: t.texture,
525 sampler);
526 }
527 }
528
529 // use a dummy texture for the unused samplers in the shader
530 QRhiSampler *dummySampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Nearest, .magFilter: QRhiSampler::Nearest, .mipmap: QRhiSampler::None,
531 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat });
532
533 for (const QShaderDescription::InOutVariable &var : samplerVars) {
534 if (!samplerBindingsSpecified.testBit(i: var.binding)) {
535 QRhiTexture *t = var.type == QShaderDescription::SamplerCube ? dummyCubeTexture : dummyTexture;
536 bindings.addTexture(binding: var.binding, stage: CUSTOM_MATERIAL_VISIBILITY_ALL, tex: t, sampler: dummySampler);
537 }
538 }
539 }
540
541 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(q: rhiCtx);
542
543 // do the same srb lookup acceleration as default materials
544 QRhiShaderResourceBindings *&srb = dcd.srb;
545 bool srbChanged = false;
546 if (!srb || bindings != dcd.bindings) {
547 srb = rhiCtxD->srb(bindings);
548 rhiCtxD->releaseCachedSrb(bindings&: dcd.bindings);
549 dcd.bindings = bindings;
550 srbChanged = true;
551 }
552
553 if (cubeFace == QSSGRenderTextureCubeFaceNone)
554 renderable.rhiRenderData.mainPass.srb = srb;
555 else
556 renderable.rhiRenderData.reflectionPass.srb[cubeFaceIdx] = srb;
557
558 const auto pipelineKey = QSSGGraphicsPipelineStateKey::create(state: *ps, rpDesc: renderPassDescriptor, srb);
559 if (dcd.pipeline
560 && !srbChanged
561 && dcd.renderTargetDescriptionHash == pipelineKey.extra.renderTargetDescriptionHash
562 && dcd.renderTargetDescription == pipelineKey.renderTargetDescription
563 && dcd.ps == *ps)
564 {
565 if (cubeFace == QSSGRenderTextureCubeFaceNone)
566 renderable.rhiRenderData.mainPass.pipeline = dcd.pipeline;
567 else
568 renderable.rhiRenderData.reflectionPass.pipeline = dcd.pipeline;
569 } else {
570 if (cubeFace == QSSGRenderTextureCubeFaceNone) {
571 renderable.rhiRenderData.mainPass.pipeline = rhiCtxD->pipeline(key: pipelineKey,
572 rpDesc: renderPassDescriptor,
573 srb);
574 dcd.pipeline = renderable.rhiRenderData.mainPass.pipeline;
575 } else {
576 renderable.rhiRenderData.reflectionPass.pipeline = rhiCtxD->pipeline(key: pipelineKey,
577 rpDesc: renderPassDescriptor,
578 srb);
579 dcd.pipeline = renderable.rhiRenderData.reflectionPass.pipeline;
580 }
581
582 dcd.renderTargetDescriptionHash = pipelineKey.extra.renderTargetDescriptionHash;
583 dcd.renderTargetDescription = pipelineKey.renderTargetDescription;
584 dcd.ps = *ps;
585 }
586 }
587}
588
589void QSSGCustomMaterialSystem::setShaderResources(char *ubufData,
590 const QSSGRenderCustomMaterial &inMaterial,
591 const QByteArray &inPropertyName,
592 const QVariant &propertyValue,
593 QSSGRenderShaderValue::Type inPropertyType,
594 QSSGRhiShaderPipeline &shaderPipeline)
595{
596 Q_UNUSED(inMaterial);
597
598 if (inPropertyType == QSSGRenderShaderValue::Texture) {
599 QSSGRenderCustomMaterial::TextureProperty *textureProperty =
600 reinterpret_cast<QSSGRenderCustomMaterial::TextureProperty *>(propertyValue.value<void *>());
601 QSSGRenderImage *image = textureProperty->texImage;
602 if (image) {
603 const auto &theBufferManager(context->bufferManager());
604 const QSSGRenderImageTexture texture = theBufferManager->loadRenderImage(image);
605 if (texture.m_texture) {
606 const QSSGRhiTexture t = {
607 .name: inPropertyName,
608 .texture: texture.m_texture,
609 .samplerDesc: { .minFilter: QSSGRhiHelpers::toRhi(op: textureProperty->minFilterType),
610 .magFilter: QSSGRhiHelpers::toRhi(op: textureProperty->magFilterType),
611 .mipmap: textureProperty->mipFilterType != QSSGRenderTextureFilterOp::None ? QSSGRhiHelpers::toRhi(op: textureProperty->mipFilterType) : QRhiSampler::None,
612 .hTiling: QSSGRhiHelpers::toRhi(tiling: textureProperty->horizontalClampType),
613 .vTiling: QSSGRhiHelpers::toRhi(tiling: textureProperty->verticalClampType),
614 .zTiling: QSSGRhiHelpers::toRhi(tiling: textureProperty->zClampType)
615 }
616 };
617 shaderPipeline.addExtraTexture(t);
618 }
619 }
620 } else {
621 shaderPipeline.setUniformValue(ubufData, name: inPropertyName, value: propertyValue, type: inPropertyType);
622 }
623}
624
625void QSSGCustomMaterialSystem::applyRhiShaderPropertyValues(char *ubufData,
626 const QSSGRenderCustomMaterial &material,
627 QSSGRhiShaderPipeline &shaderPipeline)
628{
629 const auto &properties = material.m_properties;
630 for (const auto &prop : properties)
631 setShaderResources(ubufData, inMaterial: material, inPropertyName: prop.name, propertyValue: prop.value, inPropertyType: prop.shaderDataType, shaderPipeline);
632
633 const auto textProps = material.m_textureProperties;
634 for (const auto &prop : textProps)
635 setShaderResources(ubufData, inMaterial: material, inPropertyName: prop.name, propertyValue: QVariant::fromValue(value: (void *)&prop), inPropertyType: prop.shaderDataType, shaderPipeline);
636}
637
638void QSSGCustomMaterialSystem::rhiRenderRenderable(QSSGRhiContext *rhiCtx,
639 QSSGSubsetRenderable &renderable,
640 bool *needsSetViewport,
641 QSSGRenderTextureCubeFace cubeFace,
642 const QSSGRhiGraphicsPipelineState &state)
643{
644 QRhiGraphicsPipeline *ps = renderable.rhiRenderData.mainPass.pipeline;
645 QRhiShaderResourceBindings *srb = renderable.rhiRenderData.mainPass.srb;
646
647 if (cubeFace != QSSGRenderTextureCubeFaceNone) {
648 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(face: cubeFace);
649 ps = renderable.rhiRenderData.reflectionPass.pipeline;
650 srb = renderable.rhiRenderData.reflectionPass.srb[cubeFaceIdx];
651 }
652
653 if (!ps || !srb)
654 return;
655
656 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
657 QRhiBuffer *vertexBuffer = renderable.subset.rhi.vertexBuffer->buffer();
658 QRhiBuffer *indexBuffer = renderable.subset.rhi.indexBuffer ? renderable.subset.rhi.indexBuffer->buffer() : nullptr;
659
660 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
661 cb->setGraphicsPipeline(ps);
662 cb->setShaderResources(srb);
663
664 if (*needsSetViewport) {
665 cb->setViewport(state.viewport);
666 *needsSetViewport = false;
667 }
668
669 QRhiCommandBuffer::VertexInput vertexBuffers[2];
670 int vertexBufferCount = 1;
671 vertexBuffers[0] = QRhiCommandBuffer::VertexInput(vertexBuffer, 0);
672 quint32 instances = 1;
673 if (renderable.modelContext.model.instancing()) {
674 instances = renderable.modelContext.model.instanceCount();
675 vertexBuffers[1] = QRhiCommandBuffer::VertexInput(renderable.instanceBuffer, 0);
676 vertexBufferCount = 2;
677 }
678 if (indexBuffer) {
679 cb->setVertexInput(startBinding: 0, bindingCount: vertexBufferCount, bindings: vertexBuffers, indexBuf: indexBuffer, indexOffset: 0, indexFormat: renderable.subset.rhi.indexBuffer->indexFormat());
680 cb->drawIndexed(indexCount: renderable.subset.count, instanceCount: instances, firstIndex: renderable.subset.offset);
681 QSSGRHICTX_STAT(rhiCtx, drawIndexed(renderable.subset.count, instances));
682 } else {
683 cb->setVertexInput(startBinding: 0, bindingCount: vertexBufferCount, bindings: vertexBuffers);
684 cb->draw(vertexCount: renderable.subset.count, instanceCount: instances, firstVertex: renderable.subset.offset);
685 QSSGRHICTX_STAT(rhiCtx, draw(renderable.subset.count, instances));
686 }
687 Q_QUICK3D_PROFILE_END_WITH_IDS(QQuick3DProfiler::Quick3DRenderCall, (renderable.subset.count | quint64(instances) << 32),
688 QVector<int>({renderable.modelContext.model.profilingId,
689 renderable.material.profilingId}));
690}
691
692QT_END_NAMESPACE
693

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