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