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 | |
33 | static inline void qDryRunPrintQsbcAdd(const QByteArray &id) |
34 | { |
35 | printf(format: "Shader pipeline generated for (dry run):\n %s\n\n" , qPrintable(id)); |
36 | } |
37 | |
38 | static 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 | |
53 | GenShaders::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 | |
77 | GenShaders::~GenShaders() = default; |
78 | |
79 | bool 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 | |
109 | QQuick3DViewport *view3D = sceneData.viewport; |
110 | Q_ASSERT(view3D); |
111 | |
112 | QVector<QSSGRenderGraphObject *> nodes; |
113 | |
114 | if (!view3D->camera()) { |
115 | auto camera = new QQuick3DPerspectiveCamera(); |
116 | auto node = QQuick3DObjectPrivate::updateSpatialNode(o: camera, n: nullptr); |
117 | QQuick3DObjectPrivate::get(item: camera)->spatialNode = node; |
118 | nodes.append(t: node); |
119 | view3D->setCamera(camera); |
120 | } |
121 | |
122 | // Realize resources |
123 | // Textures |
124 | const auto &textures = sceneData.textures; |
125 | for (const auto &tex : textures) { |
126 | auto node = QQuick3DObjectPrivate::updateSpatialNode(o: tex, n: nullptr); |
127 | auto obj = QQuick3DObjectPrivate::get(item: tex); |
128 | obj->spatialNode = node; |
129 | nodes.append(t: node); |
130 | } |
131 | |
132 | // Free Materials (see also the model section) |
133 | const auto &materials = sceneData.materials; |
134 | for (const auto &mat : materials) { |
135 | auto obj = QQuick3DObjectPrivate::get(item: mat); |
136 | obj->sceneManager = sceneManager; |
137 | auto node = QQuick3DObjectPrivate::updateSpatialNode(o: mat, n: nullptr); |
138 | obj->spatialNode = node; |
139 | nodes.append(t: node); |
140 | } |
141 | |
142 | bool shadowPerspectivePass = false; |
143 | bool shadowOrthoPass = false; |
144 | |
145 | // Lights |
146 | const auto &lights = sceneData.lights; |
147 | for (const auto &light : lights) { |
148 | if (auto node = QQuick3DObjectPrivate::updateSpatialNode(o: light, n: nullptr)) { |
149 | nodes.append(t: node); |
150 | layer.addChild(inChild&: static_cast<QSSGRenderNode &>(*node)); |
151 | const auto &lightNode = static_cast<const QSSGRenderLight &>(*node); |
152 | if (lightNode.type == QSSGRenderLight::Type::DirectionalLight) |
153 | shadowOrthoPass |= true; |
154 | else |
155 | shadowPerspectivePass |= true; |
156 | } |
157 | } |
158 | |
159 | // NOTE: Model.castsShadows; Model.receivesShadows; variants needs to be added for runtime support |
160 | const auto &models = sceneData.models; |
161 | for (const auto &model : models) { |
162 | auto materialList = model->materials(); |
163 | for (int i = 0, e = materialList.count(&materialList); i != e; ++i) { |
164 | auto mat = materialList.at(&materialList, i); |
165 | auto obj = QQuick3DObjectPrivate::get(item: mat); |
166 | obj->sceneManager = sceneManager; |
167 | QSSGRenderGraphObject *node = nullptr; |
168 | if (obj->type == QQuick3DObjectPrivate::Type::CustomMaterial) { |
169 | auto customMatNode = new QSSGRenderCustomMaterial; |
170 | customMatNode->incompleteBuildTimeObject = true; |
171 | node = QQuick3DObjectPrivate::updateSpatialNode(o: mat, n: customMatNode); |
172 | customMatNode->incompleteBuildTimeObject = false; |
173 | } else { |
174 | node = QQuick3DObjectPrivate::updateSpatialNode(o: mat, n: nullptr); |
175 | } |
176 | QQuick3DObjectPrivate::get(item: mat)->spatialNode = node; |
177 | nodes.append(t: node); |
178 | } |
179 | if (auto instanceList = qobject_cast<QQuick3DInstanceList *>(object: model->instancing())) { |
180 | auto obj = QQuick3DObjectPrivate::get(item: instanceList); |
181 | auto node = QQuick3DObjectPrivate::updateSpatialNode(o: instanceList, n: nullptr); |
182 | obj->spatialNode = node; |
183 | nodes.append(t: node); |
184 | } |
185 | |
186 | auto node = QQuick3DObjectPrivate::updateSpatialNode(o: model, n: nullptr); |
187 | QQuick3DObjectPrivate::get(item: model)->spatialNode = node; |
188 | nodes.append(t: node); |
189 | } |
190 | |
191 | QQuick3DRenderLayerHelpers::updateLayerNodeHelper(view3D: *view3D, rci: renderContext, layerNode&: layer, aaIsDirty, temporalIsDirty); |
192 | |
193 | const QString outCollectionFile = outputFolder + QString::fromLatin1(ba: QSSGShaderCache::shaderCollectionFile()); |
194 | QQsbIODeviceCollection qsbc(outCollectionFile); |
195 | if (!dryRun && !qsbc.map(mode: QQsbIODeviceCollection::Write)) |
196 | return false; |
197 | |
198 | QByteArray shaderString; |
199 | const auto generateShaderForModel = [&](QSSGRenderModel &model) { |
200 | layerData.resetForFrame(); |
201 | layer.addChild(inChild&: model); |
202 | layerData.prepareForRender(); |
203 | |
204 | const auto &features = layerData.getShaderFeatures(); |
205 | |
206 | const auto &propertyTable = layerData.getDefaultMaterialPropertyTable(); |
207 | |
208 | const auto &opaqueObjects = layerData.getSortedOpaqueRenderableObjects(camera: *layerData.renderedCameras[0]); |
209 | const auto &transparentObjects = layerData.getSortedTransparentRenderableObjects(camera: *layerData.renderedCameras[0]); |
210 | |
211 | QSSGRenderableObject *renderable = nullptr; |
212 | if (!opaqueObjects.isEmpty()) |
213 | renderable = opaqueObjects[0].obj; |
214 | else if (!transparentObjects.isEmpty()) |
215 | renderable = transparentObjects[0].obj; |
216 | |
217 | auto generateShader = [&](const QSSGShaderFeatures &features) { |
218 | if ((renderable->type == QSSGSubsetRenderable::Type::DefaultMaterialMeshSubset)) { |
219 | auto shaderPipeline = QSSGRendererPrivate::generateRhiShaderPipelineImpl(renderable&: *static_cast<QSSGSubsetRenderable *>(renderable), shaderLibraryManager&: *shaderLibraryManager, shaderCache&: *shaderCache, shaderProgramGenerator&: *shaderProgramGenerator, shaderKeyProperties: propertyTable, featureSet: features, shaderString); |
220 | if (shaderPipeline != nullptr) { |
221 | const auto qsbcFeatureList = QQsbCollection::toFeatureSet(ssgFeatureSet: features); |
222 | const QByteArray qsbcKey = QQsbCollection::EntryDesc::generateSha(materialKey: shaderString, featureSet: qsbcFeatureList); |
223 | const auto vertexStage = shaderPipeline->vertexStage(); |
224 | const auto fragmentStage = shaderPipeline->fragmentStage(); |
225 | if (vertexStage && fragmentStage) { |
226 | if (dryRun) |
227 | qDryRunPrintQsbcAdd(id: shaderString); |
228 | else |
229 | qsbc.addEntry(key: qsbcKey, entryDesc: { .materialKey: shaderString, .featureSet: qsbcFeatureList, .vertShader: vertexStage->shader(), .fragShader: fragmentStage->shader() }); |
230 | } |
231 | } |
232 | } else if ((renderable->type == QSSGSubsetRenderable::Type::CustomMaterialMeshSubset)) { |
233 | Q_ASSERT(!layerData.renderedCameras.isEmpty()); |
234 | QSSGSubsetRenderable &cmr(static_cast<QSSGSubsetRenderable &>(*renderable)); |
235 | auto pipelineState = layerData.getPipelineState(); |
236 | const auto &cms = renderContext->customMaterialSystem(); |
237 | const auto &material = static_cast<const QSSGRenderCustomMaterial &>(cmr.getMaterial()); |
238 | auto shaderPipeline = cms->shadersForCustomMaterial(ps: &pipelineState, |
239 | material, |
240 | renderable&: cmr, |
241 | defaultMaterialShaderKeyProperties: propertyTable, |
242 | featureSet: features); |
243 | |
244 | if (shaderPipeline) { |
245 | shaderString = material.m_shaderPathKey[QSSGRenderCustomMaterial::RegularShaderPathKeyIndex]; |
246 | const auto qsbcFeatureList = QQsbCollection::toFeatureSet(ssgFeatureSet: features); |
247 | const QByteArray qsbcKey = QQsbCollection::EntryDesc::generateSha(materialKey: shaderString, featureSet: qsbcFeatureList); |
248 | const auto vertexStage = shaderPipeline->vertexStage(); |
249 | const auto fragmentStage = shaderPipeline->fragmentStage(); |
250 | if (vertexStage && fragmentStage) { |
251 | if (dryRun) |
252 | qDryRunPrintQsbcAdd(id: shaderString); |
253 | else |
254 | qsbc.addEntry(key: qsbcKey, entryDesc: { .materialKey: shaderString, .featureSet: qsbcFeatureList, .vertShader: vertexStage->shader(), .fragShader: fragmentStage->shader() }); |
255 | } |
256 | } |
257 | } |
258 | }; |
259 | |
260 | if (renderable) { |
261 | generateShader(features); |
262 | |
263 | QSSGShaderFeatures depthPassFeatures; |
264 | depthPassFeatures.set(feature: QSSGShaderFeatures::Feature::DepthPass, val: true); |
265 | generateShader(depthPassFeatures); |
266 | |
267 | if (shadowPerspectivePass) { |
268 | QSSGShaderFeatures shadowPassFeatures; |
269 | shadowPassFeatures.set(feature: QSSGShaderFeatures::Feature::PerspectiveShadowPass, val: true); |
270 | generateShader(shadowPassFeatures); |
271 | } |
272 | |
273 | if (shadowOrthoPass) { |
274 | QSSGShaderFeatures shadowPassFeatures; |
275 | shadowPassFeatures.set(feature: QSSGShaderFeatures::Feature::OrthoShadowPass, val: true); |
276 | generateShader(shadowPassFeatures); |
277 | } |
278 | } |
279 | layer.removeChild(inChild&: model); |
280 | }; |
281 | |
282 | for (const auto &model : models) |
283 | generateShaderForModel(static_cast<QSSGRenderModel &>(*QQuick3DObjectPrivate::get(item: model)->spatialNode)); |
284 | |
285 | // Let's generate some shaders for the "free" materials as well. |
286 | QSSGRenderModel model; // dummy |
287 | model.meshPath = QSSGRenderPath("#Cube" ); |
288 | for (const auto &mat : materials) { |
289 | model.materials = { QQuick3DObjectPrivate::get(item: mat)->spatialNode }; |
290 | generateShaderForModel(model); |
291 | } |
292 | |
293 | // Now generate the shaders for the effects |
294 | const auto generateEffectShader = [&](QQuick3DEffect &effect) { |
295 | auto obj = QQuick3DObjectPrivate::get(item: &effect); |
296 | obj->sceneManager = sceneManager; |
297 | QSSGRenderEffect *renderEffect = new QSSGRenderEffect; |
298 | renderEffect->incompleteBuildTimeObject = true; |
299 | if (auto ret = QQuick3DObjectPrivate::updateSpatialNode(o: &effect, n: renderEffect)) |
300 | Q_ASSERT(ret == renderEffect); |
301 | renderEffect->incompleteBuildTimeObject = false; |
302 | obj->spatialNode = renderEffect; |
303 | nodes.append(t: renderEffect); |
304 | |
305 | const auto &commands = renderEffect->commands; |
306 | for (const QSSGRenderEffect::Command &c : commands) { |
307 | QSSGCommand *command = c.command; |
308 | if (command->m_type == CommandType::BindShader) { |
309 | auto bindShaderCommand = static_cast<const QSSGBindShader &>(*command); |
310 | for (const auto isYUpInFramebuffer : { true, false }) { // Generate effects for both up-directions. |
311 | const auto shaderPipeline = QSSGRhiEffectSystem::buildShaderForEffect(inCmd: bindShaderCommand, |
312 | generator&: *shaderProgramGenerator, |
313 | shaderLib&: *shaderLibraryManager, |
314 | shaderCache&: *shaderCache, |
315 | isYUpInFramebuffer, |
316 | viewCount: 1); // no multiview support here yet |
317 | if (shaderPipeline) { |
318 | const auto &key = bindShaderCommand.m_shaderPathKey; |
319 | const QSSGShaderFeatures features = shaderLibraryManager->getShaderMetaData(inShaderPathKey: key, type: QSSGShaderCache::ShaderType::Fragment).features; |
320 | const auto qsbcFeatureList = QQsbCollection::toFeatureSet(ssgFeatureSet: features); |
321 | QByteArray qsbcKey = QQsbCollection::EntryDesc::generateSha(materialKey: key, featureSet: qsbcFeatureList); |
322 | const auto vertexStage = shaderPipeline->vertexStage(); |
323 | const auto fragmentStage = shaderPipeline->fragmentStage(); |
324 | if (vertexStage && fragmentStage) { |
325 | if (dryRun) |
326 | qDryRunPrintQsbcAdd(id: key); |
327 | else |
328 | qsbc.addEntry(key: qsbcKey, entryDesc: { .materialKey: key, .featureSet: qsbcFeatureList, .vertShader: vertexStage->shader(), .fragShader: fragmentStage->shader() }); |
329 | } |
330 | } |
331 | } |
332 | } |
333 | } |
334 | }; |
335 | |
336 | // Effects |
337 | if (sceneData.viewport && sceneData.viewport->environment()) { |
338 | auto &env = *sceneData.viewport->environment(); |
339 | auto effects = env.effects(); |
340 | const auto effectCount = effects.count(&effects); |
341 | for (int i = 0; i < effectCount; ++i) { |
342 | auto effect = effects.at(&effects, i); |
343 | generateEffectShader(*effect); |
344 | } |
345 | } |
346 | |
347 | // Free Effects |
348 | for (const auto &effect : std::as_const(t: sceneData.effects)) |
349 | generateEffectShader(*effect); |
350 | |
351 | if (!qsbc.availableEntries().isEmpty()) |
352 | qsbcFiles.push_back(t: resourceFolderRelative + QDir::separator() + QString::fromLatin1(ba: QSSGShaderCache::shaderCollectionFile())); |
353 | qsbc.unmap(); |
354 | |
355 | auto &children = layer.children; |
356 | for (auto it = children.begin(), end = children.end(); it != end;) |
357 | children.remove(inObj&: *it++); |
358 | |
359 | qDeleteAll(c: nodes); |
360 | |
361 | return true; |
362 | } |
363 | |