1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "genshaders.h"
5
6#include <QtCore/qdir.h>
7
8#include <QtQml/qqmllist.h>
9
10#include <QtQuick3D/private/qquick3dsceneenvironment_p.h>
11#include <QtQuick3D/private/qquick3dprincipledmaterial_p.h>
12#include <QtQuick3D/private/qquick3dviewport_p.h>
13#include <QtQuick3D/private/qquick3dscenerenderer_p.h>
14#include <QtQuick3D/private/qquick3dscenemanager_p.h>
15#include <QtQuick3D/private/qquick3dperspectivecamera_p.h>
16
17// Lights
18#include <QtQuick3D/private/qquick3dspotlight_p.h>
19#include <QtQuick3D/private/qquick3ddirectionallight_p.h>
20#include <QtQuick3D/private/qquick3dpointlight_p.h>
21
22#include <QtQuick3DUtils/private/qqsbcollection_p.h>
23
24#include <private/qssgrenderer_p.h>
25#include <private/qssglayerrenderdata_p.h>
26
27#include <QtQuick3DRuntimeRender/private/qssgrhieffectsystem_p.h>
28#include <QtQuick3DRuntimeRender/private/qssgrhicustommaterialsystem_p.h>
29#include <QtQuick3DRuntimeRender/private/qssgdebugdrawsystem_p.h>
30#include <QtQuick3DRuntimeRender/private/qssgrenderroot_p.h>
31
32#include <rhi/qshaderbaker.h>
33
34static inline void qDryRunPrintQsbcAdd(const QByteArray &id)
35{
36 printf(format: "Shader pipeline generated for (dry run):\n %s\n\n", qPrintable(id));
37}
38
39static void initBaker(QShaderBaker *baker, QRhi *rhi)
40{
41 Q_UNUSED(rhi); // that's a Null-backed rhi here anyways
42 QVector<QShaderBaker::GeneratedShader> outputs;
43 // TODO: For simplicity we're just going to add all off these for now.
44 outputs.append(t: { QShader::SpirvShader, QShaderVersion(100) }); // Vulkan 1.0
45 outputs.append(t: { QShader::HlslShader, QShaderVersion(50) }); // Shader Model 5.0
46 outputs.append(t: { QShader::MslShader, QShaderVersion(12) }); // Metal 1.2
47 outputs.append(t: { QShader::GlslShader, QShaderVersion(300, QShaderVersion::GlslEs) }); // GLES 3.0+
48 outputs.append(t: { QShader::GlslShader, QShaderVersion(140) }); // OpenGL 3.1+
49
50 baker->setGeneratedShaders(outputs);
51 baker->setGeneratedShaderVariants({ QShader::StandardShader });
52}
53
54GenShaders::GenShaders()
55{
56 sceneManager = new QQuick3DSceneManager;
57
58 rhi = QRhi::create(impl: QRhi::Null, params: nullptr);
59 QRhiCommandBuffer *cb;
60 rhi->beginOffscreenFrame(cb: &cb);
61
62 std::unique_ptr<QSSGRhiContext> rhiContext = std::make_unique<QSSGRhiContext>(args&: rhi);
63 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(q: rhiContext.get());
64 rhiCtxD->setCommandBuffer(cb);
65
66 renderContext = std::make_shared<QSSGRenderContextInterface>(args: std::make_unique<QSSGBufferManager>(),
67 args: std::make_unique<QSSGRenderer>(),
68 args: std::make_shared<QSSGShaderLibraryManager>(),
69 args: std::make_unique<QSSGShaderCache>(args&: *rhiContext, args: &initBaker),
70 args: std::make_unique<QSSGCustomMaterialSystem>(),
71 args: std::make_unique<QSSGProgramGenerator>(),
72 args: std::move(rhiContext));
73 wa = new QQuick3DWindowAttachment(nullptr);
74 wa->setRci(renderContext);
75 sceneManager->wattached = wa;
76}
77
78GenShaders::~GenShaders() = default;
79
80bool GenShaders::process(const MaterialParser::SceneData &sceneData,
81 QVector<QString> &qsbcFiles,
82 const QDir &outDir,
83 bool generateMultipleLights,
84 bool dryRun)
85{
86 Q_UNUSED(generateMultipleLights);
87
88 const QString resourceFolderRelative = QSSGShaderCache::resourceFolder().mid(index: 2);
89 if (!dryRun && !outDir.exists(name: resourceFolderRelative)) {
90 if (!outDir.mkpath(dirPath: resourceFolderRelative)) {
91 qDebug(msg: "Unable to create folder: %s", qPrintable(outDir.path() + QDir::separator() + resourceFolderRelative));
92 return false;
93 }
94 }
95
96 const QString outputFolder = outDir.canonicalPath() + QDir::separator() + resourceFolderRelative;
97
98 QSSGRenderLayer layer;
99 if (!layer.rootNode) {
100 auto *rootNode = new QSSGRenderRoot;
101 rootNode->addChild(inChild&: layer);
102 layer.rootNode = rootNode;
103 }
104 renderContext->renderer()->setViewport(QRect(QPoint(), QSize(888,666)));
105 const auto &renderer = renderContext->renderer();
106 QSSGLayerRenderData layerData(layer, *renderer);
107
108 const auto &shaderLibraryManager = renderContext->shaderLibraryManager();
109 const auto &shaderCache = renderContext->shaderCache();
110 const auto &shaderProgramGenerator = renderContext->shaderProgramGenerator();
111
112 bool aaIsDirty = false;
113 bool temporalIsDirty = false;
114
115 QQuick3DViewport *view3D = sceneData.viewport;
116 Q_ASSERT(view3D);
117
118 QVector<QSSGRenderGraphObject *> nodes;
119
120 if (!view3D->camera()) {
121 auto camera = new QQuick3DPerspectiveCamera();
122 auto node = QQuick3DObjectPrivate::updateSpatialNode(o: camera, n: nullptr);
123 QQuick3DObjectPrivate::get(item: camera)->spatialNode = node;
124 nodes.append(t: node);
125 view3D->setCamera(camera);
126 }
127
128 // Realize resources
129 // Textures
130 const auto &textures = sceneData.textures;
131 for (const auto &tex : textures) {
132 auto node = QQuick3DObjectPrivate::updateSpatialNode(o: tex, n: nullptr);
133 auto obj = QQuick3DObjectPrivate::get(item: tex);
134 obj->spatialNode = node;
135 nodes.append(t: node);
136 }
137
138 // Free Materials (see also the model section)
139 const auto &materials = sceneData.materials;
140 for (const auto &mat : materials) {
141 auto obj = QQuick3DObjectPrivate::get(item: mat);
142 obj->sceneManager = sceneManager;
143 auto node = QQuick3DObjectPrivate::updateSpatialNode(o: mat, n: nullptr);
144 obj->spatialNode = node;
145 nodes.append(t: node);
146 }
147
148 bool shadowPerspectivePass = false;
149 bool shadowOrthoPass = false;
150
151 // Lights
152 const auto &lights = sceneData.lights;
153 for (const auto &light : lights) {
154 if (auto node = QQuick3DObjectPrivate::updateSpatialNode(o: light, n: nullptr)) {
155 nodes.append(t: node);
156 layer.addChild(inChild&: static_cast<QSSGRenderNode &>(*node));
157 const auto &lightNode = static_cast<const QSSGRenderLight &>(*node);
158 if (lightNode.type == QSSGRenderLight::Type::DirectionalLight)
159 shadowOrthoPass |= true;
160 else
161 shadowPerspectivePass |= true;
162 }
163 }
164
165 // NOTE: Model.castsShadows; Model.receivesShadows; variants needs to be added for runtime support
166 const auto &models = sceneData.models;
167 for (const auto &model : models) {
168 auto materialList = model->materials();
169 for (int i = 0, e = materialList.count(&materialList); i != e; ++i) {
170 auto mat = materialList.at(&materialList, i);
171 auto obj = QQuick3DObjectPrivate::get(item: mat);
172 obj->sceneManager = sceneManager;
173 QSSGRenderGraphObject *node = nullptr;
174 if (obj->type == QQuick3DObjectPrivate::Type::CustomMaterial) {
175 auto customMatNode = new QSSGRenderCustomMaterial;
176 customMatNode->incompleteBuildTimeObject = true;
177 node = QQuick3DObjectPrivate::updateSpatialNode(o: mat, n: customMatNode);
178 customMatNode->incompleteBuildTimeObject = false;
179 } else {
180 node = QQuick3DObjectPrivate::updateSpatialNode(o: mat, n: nullptr);
181 }
182 QQuick3DObjectPrivate::get(item: mat)->spatialNode = node;
183 nodes.append(t: node);
184 }
185 if (auto instanceList = qobject_cast<QQuick3DInstanceList *>(object: model->instancing())) {
186 auto obj = QQuick3DObjectPrivate::get(item: instanceList);
187 auto node = QQuick3DObjectPrivate::updateSpatialNode(o: instanceList, n: nullptr);
188 obj->spatialNode = node;
189 nodes.append(t: node);
190 }
191
192 auto node = QQuick3DObjectPrivate::updateSpatialNode(o: model, n: nullptr);
193 QQuick3DObjectPrivate::get(item: model)->spatialNode = node;
194 nodes.append(t: node);
195 }
196
197 // Ensure all spatial node types are in the render tree
198 for (auto *node : std::as_const(t&: nodes)) {
199 if (QSSGRenderGraphObject::isNodeType(type: node->type)) {
200 QSSGRenderNode *n = static_cast<QSSGRenderNode *>(node);
201 if (!n->parent)
202 layer.addChild(inChild&: *n);
203 }
204 }
205
206 QQuick3DRenderLayerHelpers::updateLayerNodeHelper(view3D: *view3D, rci: renderContext, layerNode&: layer, aaIsDirty, temporalIsDirty);
207
208 const QString outCollectionFile = outputFolder + QString::fromLatin1(ba: QSSGShaderCache::shaderCollectionFile());
209 QQsbIODeviceCollection qsbc(outCollectionFile);
210 if (!dryRun && !qsbc.map(mode: QQsbIODeviceCollection::Write))
211 return false;
212
213 QByteArray shaderString;
214 const auto generateShaderForModel = [&](QSSGRenderModel &model) {
215 layerData.resetForFrame();
216 if (!model.parent)
217 layer.addChild(inChild&: model);
218 layerData.layer.rootNode->reindex();
219 layerData.prepareForRender();
220
221 const auto &features = layerData.getShaderFeatures();
222
223 const auto &propertyTable = layerData.getDefaultMaterialPropertyTable();
224
225 const auto &opaqueObjects = layerData.getSortedOpaqueRenderableObjects(camera: *layerData.renderedCameras[0]);
226 const auto &transparentObjects = layerData.getSortedTransparentRenderableObjects(camera: *layerData.renderedCameras[0]);
227
228 QSSGRenderableObject *renderable = nullptr;
229 if (!opaqueObjects.isEmpty())
230 renderable = opaqueObjects[0].obj;
231 else if (!transparentObjects.isEmpty())
232 renderable = transparentObjects[0].obj;
233
234 auto generateShader = [&](const QSSGShaderFeatures &features) {
235 if ((renderable->type == QSSGSubsetRenderable::Type::DefaultMaterialMeshSubset)) {
236 auto shaderPipeline = QSSGRendererPrivate::generateRhiShaderPipelineImpl(renderable&: *static_cast<QSSGSubsetRenderable *>(renderable), shaderLibraryManager&: *shaderLibraryManager, shaderCache&: *shaderCache, shaderProgramGenerator&: *shaderProgramGenerator, shaderKeyProperties: propertyTable, featureSet: features, shaderString);
237 if (shaderPipeline != nullptr) {
238 const auto qsbcFeatureList = QQsbCollection::toFeatureSet(ssgFeatureSet: features);
239 const QByteArray qsbcKey = QQsbCollection::EntryDesc::generateSha(materialKey: shaderString, featureSet: qsbcFeatureList);
240 const auto vertexStage = shaderPipeline->vertexStage();
241 const auto fragmentStage = shaderPipeline->fragmentStage();
242 if (vertexStage && fragmentStage) {
243 if (dryRun)
244 qDryRunPrintQsbcAdd(id: shaderString);
245 else
246 qsbc.addEntry(key: qsbcKey, entryDesc: { .materialKey: shaderString, .featureSet: qsbcFeatureList, .vertShader: vertexStage->shader(), .fragShader: fragmentStage->shader() });
247 }
248 }
249 } else if ((renderable->type == QSSGSubsetRenderable::Type::CustomMaterialMeshSubset)) {
250 Q_ASSERT(!layerData.renderedCameras.isEmpty());
251 QSSGSubsetRenderable &cmr(static_cast<QSSGSubsetRenderable &>(*renderable));
252 auto pipelineState = layerData.getPipelineState();
253 const auto &cms = renderContext->customMaterialSystem();
254 const auto &material = static_cast<const QSSGRenderCustomMaterial &>(cmr.getMaterial());
255 auto shaderPipeline = cms->shadersForCustomMaterial(ps: &pipelineState,
256 material,
257 renderable&: cmr,
258 defaultMaterialShaderKeyProperties: propertyTable,
259 featureSet: features);
260
261 if (shaderPipeline) {
262 shaderString = material.m_shaderPathKey[QSSGRenderCustomMaterial::RegularShaderPathKeyIndex];
263 const auto qsbcFeatureList = QQsbCollection::toFeatureSet(ssgFeatureSet: features);
264 const QByteArray qsbcKey = QQsbCollection::EntryDesc::generateSha(materialKey: shaderString, featureSet: qsbcFeatureList);
265 const auto vertexStage = shaderPipeline->vertexStage();
266 const auto fragmentStage = shaderPipeline->fragmentStage();
267 if (vertexStage && fragmentStage) {
268 if (dryRun)
269 qDryRunPrintQsbcAdd(id: shaderString);
270 else
271 qsbc.addEntry(key: qsbcKey, entryDesc: { .materialKey: shaderString, .featureSet: qsbcFeatureList, .vertShader: vertexStage->shader(), .fragShader: fragmentStage->shader() });
272 }
273 }
274 }
275 };
276
277 if (renderable) {
278 generateShader(features);
279
280 QSSGShaderFeatures depthPassFeatures;
281 depthPassFeatures.set(feature: QSSGShaderFeatures::Feature::DepthPass, val: true);
282 generateShader(depthPassFeatures);
283
284 if (shadowPerspectivePass) {
285 QSSGShaderFeatures shadowPassFeatures;
286 shadowPassFeatures.set(feature: QSSGShaderFeatures::Feature::PerspectiveShadowPass, val: true);
287 generateShader(shadowPassFeatures);
288 }
289
290 if (shadowOrthoPass) {
291 QSSGShaderFeatures shadowPassFeatures;
292 shadowPassFeatures.set(feature: QSSGShaderFeatures::Feature::OrthoShadowPass, val: true);
293 generateShader(shadowPassFeatures);
294 }
295 }
296 layer.removeChild(inChild&: model);
297 };
298
299 for (const auto &model : models)
300 generateShaderForModel(static_cast<QSSGRenderModel &>(*QQuick3DObjectPrivate::get(item: model)->spatialNode));
301
302 // Let's generate some shaders for the "free" materials as well.
303 QSSGRenderModel model; // dummy
304 model.meshPath = QSSGRenderPath("#Cube");
305 for (const auto &mat : materials) {
306 model.materials = { QQuick3DObjectPrivate::get(item: mat)->spatialNode };
307 generateShaderForModel(model);
308 }
309
310 // Now generate the shaders for the effects
311 const auto generateEffectShader = [&](QQuick3DEffect &effect) {
312 auto obj = QQuick3DObjectPrivate::get(item: &effect);
313 obj->sceneManager = sceneManager;
314 QSSGRenderEffect *renderEffect = new QSSGRenderEffect;
315 renderEffect->incompleteBuildTimeObject = true;
316 if (auto ret = QQuick3DObjectPrivate::updateSpatialNode(o: &effect, n: renderEffect))
317 Q_ASSERT(ret == renderEffect);
318 renderEffect->incompleteBuildTimeObject = false;
319 obj->spatialNode = renderEffect;
320 nodes.append(t: renderEffect);
321
322 const auto &commands = renderEffect->commands;
323 for (const QSSGRenderEffect::Command &c : commands) {
324 QSSGCommand *command = c.command;
325 if (command->m_type == CommandType::BindShader) {
326 auto bindShaderCommand = static_cast<const QSSGBindShader &>(*command);
327 for (const auto isYUpInFramebuffer : { true, false }) { // Generate effects for both up-directions.
328 const auto shaderPipeline = QSSGRhiEffectSystem::buildShaderForEffect(inCmd: bindShaderCommand,
329 generator&: *shaderProgramGenerator,
330 shaderLib&: *shaderLibraryManager,
331 shaderCache&: *shaderCache,
332 isYUpInFramebuffer,
333 viewCount: 1); // no multiview support here yet
334 if (shaderPipeline) {
335 const auto &key = bindShaderCommand.m_shaderPathKey;
336 const QSSGShaderFeatures features = shaderLibraryManager->getShaderMetaData(inShaderPathKey: key, type: QSSGShaderCache::ShaderType::Fragment).features;
337 const auto qsbcFeatureList = QQsbCollection::toFeatureSet(ssgFeatureSet: features);
338 QByteArray qsbcKey = QQsbCollection::EntryDesc::generateSha(materialKey: key, featureSet: qsbcFeatureList);
339 const auto vertexStage = shaderPipeline->vertexStage();
340 const auto fragmentStage = shaderPipeline->fragmentStage();
341 if (vertexStage && fragmentStage) {
342 if (dryRun)
343 qDryRunPrintQsbcAdd(id: key);
344 else
345 qsbc.addEntry(key: qsbcKey, entryDesc: { .materialKey: key, .featureSet: qsbcFeatureList, .vertShader: vertexStage->shader(), .fragShader: fragmentStage->shader() });
346 }
347 }
348 }
349 }
350 }
351 };
352
353 // Effects
354 if (sceneData.viewport && sceneData.viewport->environment()) {
355 auto &env = *sceneData.viewport->environment();
356 auto effects = env.effects();
357 const auto effectCount = effects.count(&effects);
358 for (int i = 0; i < effectCount; ++i) {
359 auto effect = effects.at(&effects, i);
360 generateEffectShader(*effect);
361 }
362 }
363
364 // Free Effects
365 for (const auto &effect : std::as_const(t: sceneData.effects))
366 generateEffectShader(*effect);
367
368 if (!qsbc.availableEntries().isEmpty())
369 qsbcFiles.push_back(t: resourceFolderRelative + QDir::separator() + QString::fromLatin1(ba: QSSGShaderCache::shaderCollectionFile()));
370 qsbc.unmap();
371
372 auto &children = layer.children;
373 for (auto it = children.begin(), end = children.end(); it != end;)
374 children.remove(inObj&: *it++);
375
376 qDeleteAll(c: nodes);
377
378 return true;
379}
380

source code of qtquick3d/tools/shadergen/genshaders.cpp