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 | |
31 | static inline void qDryRunPrintQsbcAdd(const QByteArray &id) |
32 | { |
33 | printf(format: "Shader pipeline generated for (dry run):\n %s\n\n" , qPrintable(id)); |
34 | } |
35 | |
36 | static 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 | |
51 | GenShaders::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 | |
74 | GenShaders::~GenShaders() = default; |
75 | |
76 | bool 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 | |