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 | 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 | |