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

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