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

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