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

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