| 1 | // Copyright (C) 2008-2012 NVIDIA Corporation. |
| 2 | // Copyright (C) 2019 The Qt Company Ltd. |
| 3 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
| 4 | |
| 5 | #include "qssgrenderer_p.h" |
| 6 | |
| 7 | #include <QtQuick3DRuntimeRender/private/qssgrenderitem2d_p.h> |
| 8 | #include "../qssgrendercontextcore.h" |
| 9 | #include <QtQuick3DRuntimeRender/private/qssgrendercamera_p.h> |
| 10 | #include <QtQuick3DRuntimeRender/private/qssgrenderlight_p.h> |
| 11 | #include <QtQuick3DRuntimeRender/private/qssgrenderimage_p.h> |
| 12 | #include <QtQuick3DRuntimeRender/private/qssgrenderbuffermanager_p.h> |
| 13 | #include "../qssgrendercontextcore.h" |
| 14 | #include <QtQuick3DRuntimeRender/private/qssgrendereffect_p.h> |
| 15 | #include <QtQuick3DRuntimeRender/private/qssgrhicustommaterialsystem_p.h> |
| 16 | #include <QtQuick3DRuntimeRender/private/qssgrendershadercodegenerator_p.h> |
| 17 | #include <QtQuick3DRuntimeRender/private/qssgrenderdefaultmaterialshadergenerator_p.h> |
| 18 | #include <QtQuick3DRuntimeRender/private/qssgperframeallocator_p.h> |
| 19 | #include <QtQuick3DRuntimeRender/private/qssgrhiquadrenderer_p.h> |
| 20 | #include <QtQuick3DRuntimeRender/private/qssgrendertexturedata_p.h> |
| 21 | #include <QtQuick3DRuntimeRender/private/qssglayerrenderdata_p.h> |
| 22 | #include <QtQuick3DRuntimeRender/private/qssgrhiparticles_p.h> |
| 23 | #include <QtQuick3DRuntimeRender/private/qssgvertexpipelineimpl_p.h> |
| 24 | #include "../qssgshadermapkey_p.h" |
| 25 | #include "../qssgrenderpickresult_p.h" |
| 26 | #include "../graphobjects/qssgrenderroot_p.h" |
| 27 | |
| 28 | #include <QtQuick3DUtils/private/qquick3dprofiler_p.h> |
| 29 | #include <QtQuick3DUtils/private/qssgdataref_p.h> |
| 30 | #include <QtQuick3DUtils/private/qssgutils_p.h> |
| 31 | #include <QtQuick3DUtils/private/qssgassert_p.h> |
| 32 | #include <qtquick3d_tracepoints_p.h> |
| 33 | |
| 34 | #include <QtQuick/private/qsgcontext_p.h> |
| 35 | #include <QtQuick/private/qsgrenderer_p.h> |
| 36 | |
| 37 | #include <QtCore/QMutexLocker> |
| 38 | #include <QtCore/QBitArray> |
| 39 | |
| 40 | #include <cstdlib> |
| 41 | #include <algorithm> |
| 42 | #include <limits> |
| 43 | |
| 44 | /* |
| 45 | Rendering is done is several steps, these are: |
| 46 | |
| 47 | 1. \l{QSSGRenderer::beginFrame(){beginFrame()} - set's up the renderer to start a new frame. |
| 48 | |
| 49 | 2. Now that the renderer is reset, values for the \l{QSSGRenderer::setViewport}{viewport}, \l{QSSGRenderer::setDpr}{dpr}, |
| 50 | \l{QSSGRenderer::setScissorRect}{scissorRect} etc. should be updated. |
| 51 | |
| 52 | 3. \l{QSSGRenderer::prepareLayerForRender()} - At this stage the scene tree will be traversed |
| 53 | and state for the renderer needed to render gets collected. This includes, but is not limited to, |
| 54 | calculating global transforms, loading of meshes, preparing materials and setting up the rendering |
| 55 | steps needed for the frame (opaque and transparent pass etc.) |
| 56 | If the there are custom \l{QQuick3DRenderExtension}{render extensions} added to to \l{View3D::extensions}{View3D} |
| 57 | then they will get their first chance to modify or react to the collected data here. |
| 58 | If the users have implemented the virtual function \l{QSSGRenderExtension::prepareData()}{prepareData} it will be |
| 59 | called after all active nodes have been collected and had their global data updated, but before any mesh or material |
| 60 | has been loaded. |
| 61 | |
| 62 | 4. \l{QSSGRenderer::rhiPrepare()} - Starts rendering necessary sub-scenes and prepare resources. |
| 63 | Sub-scenes, or sub-passes that are to be done in full, will be done at this stage. |
| 64 | |
| 65 | 5. \l{QSSGRenderer::rhiRender()} - Renders the scene to the main target. |
| 66 | |
| 67 | 6. \l{QSSGRenderer::endFrame()} - Marks the frame as done and cleans-up dirty states and |
| 68 | uneeded resources. |
| 69 | */ |
| 70 | |
| 71 | QT_BEGIN_NAMESPACE |
| 72 | |
| 73 | struct QSSGRenderableImage; |
| 74 | class QSSGSubsetRenderable; |
| 75 | |
| 76 | void QSSGRenderer::releaseCachedResources() |
| 77 | { |
| 78 | m_rhiQuadRenderer.reset(); |
| 79 | m_rhiCubeRenderer.reset(); |
| 80 | } |
| 81 | |
| 82 | void QSSGRenderer::registerItem2DData(const Item2DData &data) |
| 83 | { |
| 84 | const auto foundIt = std::find_if(first: item2DDataList.begin(), last: item2DDataList.end(), pred: [&data](const Item2DData &i2dd) { |
| 85 | return i2dd.layer == data.layer && i2dd.item == data.item; |
| 86 | }); |
| 87 | |
| 88 | if (foundIt != item2DDataList.end()) { |
| 89 | // Update existing entry |
| 90 | *foundIt = data; |
| 91 | return; |
| 92 | } |
| 93 | |
| 94 | item2DDataList.push_back(x: data); |
| 95 | } |
| 96 | |
| 97 | void QSSGRenderer::populateItem2DDataMapForLayer(const QSSGRenderLayer &layer, Item2DDataMap &item2DDataMap) const |
| 98 | { |
| 99 | item2DDataMap.clear(); |
| 100 | for (const auto &item2dData : std::as_const(t: item2DDataList)) { |
| 101 | if (item2dData.layer == &layer) |
| 102 | item2DDataMap[item2dData.item] = item2dData; |
| 103 | } |
| 104 | } |
| 105 | |
| 106 | void QSSGRenderer::releaseItem2DData(const QSSGRenderItem2D &item2D) |
| 107 | { |
| 108 | for (auto it = item2DDataList.begin(); it != item2DDataList.end(); /* no increment */) { |
| 109 | if (it->item == &item2D) { |
| 110 | delete it->rpd; |
| 111 | delete it->renderer; |
| 112 | it = item2DDataList.erase(position: it); |
| 113 | } else { |
| 114 | ++it; |
| 115 | } |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | void QSSGRenderer::releaseItem2DData(const QSSGRenderLayer &layer) |
| 120 | { |
| 121 | for (auto it = item2DDataList.begin(); it != item2DDataList.end(); /* no increment */) { |
| 122 | if (it->layer == &layer) { |
| 123 | delete it->rpd; |
| 124 | delete it->renderer; |
| 125 | it = item2DDataList.erase(position: it); |
| 126 | } else { |
| 127 | ++it; |
| 128 | } |
| 129 | } |
| 130 | } |
| 131 | |
| 132 | QSSGRenderer::QSSGRenderer() = default; |
| 133 | |
| 134 | QSSGRenderer::~QSSGRenderer() |
| 135 | { |
| 136 | m_contextInterface = nullptr; |
| 137 | releaseCachedResources(); |
| 138 | } |
| 139 | |
| 140 | void QSSGRenderer::cleanupUnreferencedBuffers(QSSGRenderLayer *inLayer) |
| 141 | { |
| 142 | // Now check for unreferenced buffers and release them if necessary |
| 143 | m_contextInterface->bufferManager()->cleanupUnreferencedBuffers(frameId: m_frameCount, layer: inLayer); |
| 144 | } |
| 145 | |
| 146 | void QSSGRenderer::resetResourceCounters(QSSGRenderLayer *inLayer) |
| 147 | { |
| 148 | m_contextInterface->bufferManager()->resetUsageCounters(frameId: m_frameCount, layer: inLayer); |
| 149 | } |
| 150 | |
| 151 | bool QSSGRenderer::prepareLayerForRender(QSSGRenderLayer &inLayer) |
| 152 | { |
| 153 | QSSGLayerRenderData *theRenderData = getOrCreateLayerRenderData(layer&: inLayer); |
| 154 | Q_ASSERT(theRenderData); |
| 155 | |
| 156 | // Need to check if the world root node is dirty and if we need to trigger |
| 157 | // a reindex of the world root node. |
| 158 | Q_ASSERT(inLayer.rootNode); |
| 159 | if (inLayer.rootNode->isDirty(dirtyFlag: QSSGRenderRoot::DirtyFlag::TreeDirty)) |
| 160 | inLayer.rootNode->reindex(); // Clears TreeDirty flag |
| 161 | |
| 162 | beginLayerRender(inLayer&: *theRenderData); |
| 163 | theRenderData->resetForFrame(); |
| 164 | theRenderData->prepareForRender(); |
| 165 | endLayerRender(); |
| 166 | return theRenderData->layerPrepResult.flags.wasDirty(); |
| 167 | } |
| 168 | |
| 169 | // Phase 1: prepare. Called when the renderpass is not yet started on the command buffer. |
| 170 | void QSSGRenderer::rhiPrepare(QSSGRenderLayer &inLayer) |
| 171 | { |
| 172 | QSSGLayerRenderData *theRenderData = getOrCreateLayerRenderData(layer&: inLayer); |
| 173 | QSSG_ASSERT(theRenderData && !theRenderData->renderedCameras.isEmpty(), return); |
| 174 | |
| 175 | const auto layerPrepResult = theRenderData->layerPrepResult; |
| 176 | if (layerPrepResult.isLayerVisible()) { |
| 177 | /// |
| 178 | QSSGRhiContext *rhiCtx = contextInterface()->rhiContext().get(); |
| 179 | QSSG_ASSERT(rhiCtx->isValid() && rhiCtx->rhi()->isRecordingFrame(), return); |
| 180 | beginLayerRender(inLayer&: *theRenderData); |
| 181 | theRenderData->maybeProcessLightmapBaking(); |
| 182 | // Process active passes. "PreMain" passes are individual passes |
| 183 | // that does can and should be done in the rhi prepare phase. |
| 184 | // It is assumed that passes are sorted in the list with regards to |
| 185 | // execution order. |
| 186 | const auto &activePasses = theRenderData->activePasses; |
| 187 | for (const auto &pass : activePasses) { |
| 188 | pass->renderPrep(renderer&: *this, data&: *theRenderData); |
| 189 | if (pass->passType() == QSSGRenderPass::Type::Standalone) |
| 190 | pass->renderPass(renderer&: *this); |
| 191 | } |
| 192 | |
| 193 | endLayerRender(); |
| 194 | } |
| 195 | } |
| 196 | |
| 197 | // Phase 2: render. Called within an active renderpass on the command buffer. |
| 198 | void QSSGRenderer::rhiRender(QSSGRenderLayer &inLayer) |
| 199 | { |
| 200 | QSSGLayerRenderData *theRenderData = getOrCreateLayerRenderData(layer&: inLayer); |
| 201 | QSSG_ASSERT(theRenderData && !theRenderData->renderedCameras.isEmpty(), return); |
| 202 | if (theRenderData->layerPrepResult.isLayerVisible()) { |
| 203 | beginLayerRender(inLayer&: *theRenderData); |
| 204 | const auto &activePasses = theRenderData->activePasses; |
| 205 | for (const auto &pass : activePasses) { |
| 206 | if (pass->passType() == QSSGRenderPass::Type::Main || pass->passType() == QSSGRenderPass::Type::Extension) |
| 207 | pass->renderPass(renderer&: *this); |
| 208 | } |
| 209 | endLayerRender(); |
| 210 | } |
| 211 | } |
| 212 | |
| 213 | template<typename Container> |
| 214 | static void cleanupResourcesImpl(const QSSGRenderContextInterface &rci, const Container &resources) |
| 215 | { |
| 216 | const auto &rhiCtx = rci.rhiContext(); |
| 217 | if (!rhiCtx->isValid()) |
| 218 | return; |
| 219 | |
| 220 | const auto &bufferManager = rci.bufferManager(); |
| 221 | |
| 222 | for (const auto &resource : resources) { |
| 223 | if (resource->type == QSSGRenderGraphObject::Type::Geometry) { |
| 224 | auto geometry = static_cast<QSSGRenderGeometry*>(resource); |
| 225 | bufferManager->releaseGeometry(geometry); |
| 226 | } else if (resource->type == QSSGRenderGraphObject::Type::Model) { |
| 227 | auto model = static_cast<QSSGRenderModel*>(resource); |
| 228 | QSSGRhiContextPrivate::get(q: rhiCtx.get())->cleanupDrawCallData(model); |
| 229 | delete model->particleBuffer; |
| 230 | } else if (resource->type == QSSGRenderGraphObject::Type::TextureData || resource->type == QSSGRenderGraphObject::Type::Skin) { |
| 231 | static_assert(std::is_base_of_v<QSSGRenderTextureData, QSSGRenderSkin>, "QSSGRenderSkin is expected to be a QSSGRenderTextureData type!" ); |
| 232 | auto textureData = static_cast<QSSGRenderTextureData *>(resource); |
| 233 | bufferManager->releaseTextureData(data: textureData); |
| 234 | } else if (resource->type == QSSGRenderGraphObject::Type::RenderExtension) { |
| 235 | auto *rext = static_cast<QSSGRenderExtension *>(resource); |
| 236 | bufferManager->releaseExtensionResult(rext: *rext); |
| 237 | } else if (resource->type == QSSGRenderGraphObject::Type::ModelInstance) { |
| 238 | auto *rhiCtxD = QSSGRhiContextPrivate::get(q: rhiCtx.get()); |
| 239 | auto *table = static_cast<QSSGRenderInstanceTable *>(resource); |
| 240 | rhiCtxD->releaseInstanceBuffer(instanceTable: table); |
| 241 | } else if (resource->type == QSSGRenderGraphObject::Type::Item2D) { |
| 242 | auto *item2D = static_cast<QSSGRenderItem2D *>(resource); |
| 243 | rci.renderer()->releaseItem2DData(item2D: *item2D); |
| 244 | } |
| 245 | |
| 246 | // ### There might be more types that need to be supported |
| 247 | |
| 248 | delete resource; |
| 249 | } |
| 250 | } |
| 251 | |
| 252 | void QSSGRenderer::cleanupResources(QList<QSSGRenderGraphObject *> &resources) |
| 253 | { |
| 254 | cleanupResourcesImpl(rci: *m_contextInterface, resources); |
| 255 | resources.clear(); |
| 256 | } |
| 257 | |
| 258 | void QSSGRenderer::cleanupResources(QSet<QSSGRenderGraphObject *> &resources) |
| 259 | { |
| 260 | cleanupResourcesImpl(rci: *m_contextInterface, resources); |
| 261 | resources.clear(); |
| 262 | } |
| 263 | |
| 264 | QSSGLayerRenderData *QSSGRenderer::getOrCreateLayerRenderData(QSSGRenderLayer &layer) |
| 265 | { |
| 266 | if (layer.renderData == nullptr) |
| 267 | layer.renderData = new QSSGLayerRenderData(layer, *this); |
| 268 | |
| 269 | return layer.renderData; |
| 270 | } |
| 271 | |
| 272 | void QSSGRenderer::addMaterialDirtyClear(QSSGRenderGraphObject *material) |
| 273 | { |
| 274 | m_materialClearDirty.insert(value: material); |
| 275 | } |
| 276 | |
| 277 | static QByteArray logPrefix() { return QByteArrayLiteral("mesh default material pipeline-- " ); } |
| 278 | |
| 279 | |
| 280 | QSSGRhiShaderPipelinePtr QSSGRendererPrivate::generateRhiShaderPipelineImpl(QSSGSubsetRenderable &renderable, |
| 281 | QSSGShaderLibraryManager &shaderLibraryManager, |
| 282 | QSSGShaderCache &shaderCache, |
| 283 | QSSGProgramGenerator &shaderProgramGenerator, |
| 284 | const QSSGShaderDefaultMaterialKeyProperties &shaderKeyProperties, |
| 285 | const QSSGShaderFeatures &featureSet, |
| 286 | QByteArray &shaderString) |
| 287 | { |
| 288 | shaderString = logPrefix(); |
| 289 | QSSGShaderDefaultMaterialKey theKey(renderable.shaderDescription); |
| 290 | |
| 291 | // This is not a cheap operation. This function assumes that it will not be |
| 292 | // hit for every material for every model in every frame (except of course |
| 293 | // for materials that got changed). In practice this is ensured by the |
| 294 | // cheaper-to-lookup cache in getShaderPipelineForDefaultMaterial(). |
| 295 | theKey.toString(ioString&: shaderString, inProperties: shaderKeyProperties); |
| 296 | |
| 297 | // Check the in-memory, per-QSSGShaderCache (and so per-QQuickWindow) |
| 298 | // runtime cache. That may get cleared upon an explicit call to |
| 299 | // QQuickWindow::releaseResources(), but will otherwise store all |
| 300 | // encountered shader pipelines in any View3D in the window. |
| 301 | if (const auto &maybePipeline = shaderCache.tryGetRhiShaderPipeline(inKey: shaderString, inFeatures: featureSet)) |
| 302 | return maybePipeline; |
| 303 | |
| 304 | // Check if there's a pre-built (offline generated) shader for available. |
| 305 | const QByteArray qsbcKey = QQsbCollection::EntryDesc::generateSha(materialKey: shaderString, featureSet: QQsbCollection::toFeatureSet(ssgFeatureSet: featureSet)); |
| 306 | const QQsbCollection::EntryMap &pregenEntries = shaderLibraryManager.m_preGeneratedShaderEntries; |
| 307 | if (!pregenEntries.isEmpty()) { |
| 308 | const auto foundIt = pregenEntries.constFind(value: QQsbCollection::Entry(qsbcKey)); |
| 309 | if (foundIt != pregenEntries.cend()) |
| 310 | return shaderCache.newPipelineFromPregenerated(inKey: shaderString, inFeatures: featureSet, entry: *foundIt, obj: renderable.material); |
| 311 | } |
| 312 | |
| 313 | // Try the persistent (disk-based) cache then. |
| 314 | if (const auto &maybePipeline = shaderCache.tryNewPipelineFromPersistentCache(qsbcKey, inKey: shaderString, inFeatures: featureSet)) |
| 315 | return maybePipeline; |
| 316 | |
| 317 | // Otherwise, build new shader code and run the resulting shaders through |
| 318 | // the shader conditioning pipeline. |
| 319 | const auto &material = static_cast<const QSSGRenderDefaultMaterial &>(renderable.getMaterial()); |
| 320 | QSSGMaterialVertexPipeline vertexPipeline(shaderProgramGenerator, |
| 321 | shaderKeyProperties, |
| 322 | material.adapter); |
| 323 | |
| 324 | return QSSGMaterialShaderGenerator::generateMaterialRhiShader(inShaderKeyPrefix: logPrefix(), |
| 325 | vertexGenerator&: vertexPipeline, |
| 326 | key: renderable.shaderDescription, |
| 327 | inProperties: shaderKeyProperties, |
| 328 | inFeatureSet: featureSet, |
| 329 | inMaterial: renderable.material, |
| 330 | inLights: renderable.lights, |
| 331 | inFirstImage: renderable.firstImage, |
| 332 | shaderLibraryManager, |
| 333 | theCache&: shaderCache); |
| 334 | } |
| 335 | |
| 336 | QSSGRhiShaderPipelinePtr QSSGRendererPrivate::generateRhiShaderPipeline(QSSGRenderer &renderer, |
| 337 | QSSGSubsetRenderable &inRenderable, |
| 338 | const QSSGShaderFeatures &inFeatureSet) |
| 339 | { |
| 340 | auto *currentLayer = renderer.m_currentLayer; |
| 341 | auto &generatedShaderString = currentLayer->generatedShaderString; |
| 342 | const auto &m_contextInterface = renderer.m_contextInterface; |
| 343 | const auto &theCache = m_contextInterface->shaderCache(); |
| 344 | const auto &shaderProgramGenerator = m_contextInterface->shaderProgramGenerator(); |
| 345 | const auto &shaderLibraryManager = m_contextInterface->shaderLibraryManager(); |
| 346 | return QSSGRendererPrivate::generateRhiShaderPipelineImpl(renderable&: inRenderable, shaderLibraryManager&: *shaderLibraryManager, shaderCache&: *theCache, shaderProgramGenerator&: *shaderProgramGenerator, shaderKeyProperties: currentLayer->defaultMaterialShaderKeyProperties, featureSet: inFeatureSet, shaderString&: generatedShaderString); |
| 347 | } |
| 348 | |
| 349 | void QSSGRenderer::beginFrame(QSSGRenderLayer &layer, bool allowRecursion) |
| 350 | { |
| 351 | const bool executeBeginFrame = !(allowRecursion && (m_activeFrameRef++ != 0)); |
| 352 | if (executeBeginFrame) { |
| 353 | m_contextInterface->perFrameAllocator()->reset(); |
| 354 | QSSGRHICTX_STAT(m_contextInterface->rhiContext().get(), start(&layer)); |
| 355 | resetResourceCounters(inLayer: &layer); |
| 356 | } |
| 357 | } |
| 358 | |
| 359 | bool QSSGRenderer::endFrame(QSSGRenderLayer &layer, bool allowRecursion) |
| 360 | { |
| 361 | const bool executeEndFrame = !(allowRecursion && (--m_activeFrameRef != 0)); |
| 362 | if (executeEndFrame) { |
| 363 | cleanupUnreferencedBuffers(inLayer: &layer); |
| 364 | |
| 365 | // We need to do this endFrame(), as the material nodes might not exist after this! |
| 366 | for (auto *matObj : std::as_const(t&: m_materialClearDirty)) { |
| 367 | if (matObj->type == QSSGRenderGraphObject::Type::CustomMaterial) { |
| 368 | static_cast<QSSGRenderCustomMaterial *>(matObj)->clearDirty(); |
| 369 | } else if (matObj->type == QSSGRenderGraphObject::Type::DefaultMaterial || |
| 370 | matObj->type == QSSGRenderGraphObject::Type::PrincipledMaterial || |
| 371 | matObj->type == QSSGRenderGraphObject::Type::SpecularGlossyMaterial) { |
| 372 | static_cast<QSSGRenderDefaultMaterial *>(matObj)->clearDirty(); |
| 373 | } |
| 374 | } |
| 375 | m_materialClearDirty.clear(); |
| 376 | |
| 377 | QSSGRHICTX_STAT(m_contextInterface->rhiContext().get(), stop(&layer)); |
| 378 | |
| 379 | ++m_frameCount; |
| 380 | } |
| 381 | |
| 382 | return executeEndFrame; |
| 383 | } |
| 384 | |
| 385 | QSSGRendererPrivate::PickResultList QSSGRendererPrivate::syncPickAll(const QSSGRenderContextInterface &ctx, |
| 386 | const QSSGRenderLayer &layer, |
| 387 | const QSSGRenderRay &ray) |
| 388 | { |
| 389 | const auto &bufferManager = ctx.bufferManager(); |
| 390 | const bool isGlobalPickingEnabled = QSSGRendererPrivate::isGlobalPickingEnabled(renderer: *ctx.renderer()); |
| 391 | PickResultList pickResults; |
| 392 | Q_ASSERT(layer.getGlobalState(QSSGRenderNode::GlobalState::Active)); |
| 393 | getLayerHitObjectList(layer, bufferManager&: *bufferManager, ray, inPickEverything: isGlobalPickingEnabled, outIntersectionResult&: pickResults); |
| 394 | // Things are rendered in a particular order and we need to respect that ordering. |
| 395 | std::stable_sort(first: pickResults.begin(), last: pickResults.end(), comp: [](const QSSGRenderPickResult &lhs, const QSSGRenderPickResult &rhs) { |
| 396 | return lhs.m_distanceSq < rhs.m_distanceSq; |
| 397 | }); |
| 398 | return pickResults; |
| 399 | } |
| 400 | |
| 401 | QSSGRendererPrivate::PickResultList QSSGRendererPrivate::syncPick(const QSSGRenderContextInterface &ctx, |
| 402 | const QSSGRenderLayer &layer, |
| 403 | const QSSGRenderRay &ray, |
| 404 | QSSGRenderNode *target) |
| 405 | { |
| 406 | const auto &bufferManager = ctx.bufferManager(); |
| 407 | const bool isGlobalPickingEnabled = QSSGRendererPrivate::isGlobalPickingEnabled(renderer: *ctx.renderer()); |
| 408 | |
| 409 | Q_ASSERT(layer.getGlobalState(QSSGRenderNode::GlobalState::Active)); |
| 410 | PickResultList pickResults; |
| 411 | if (target) |
| 412 | intersectRayWithSubsetRenderable(layer, bufferManager&: *bufferManager, inRay: ray, node: *target, outIntersectionResultList&: pickResults); |
| 413 | else |
| 414 | getLayerHitObjectList(layer, bufferManager&: *bufferManager, ray, inPickEverything: isGlobalPickingEnabled, outIntersectionResult&: pickResults); |
| 415 | |
| 416 | std::stable_sort(first: pickResults.begin(), last: pickResults.end(), comp: [](const QSSGRenderPickResult &lhs, const QSSGRenderPickResult &rhs) { |
| 417 | return lhs.m_distanceSq < rhs.m_distanceSq; |
| 418 | }); |
| 419 | return pickResults; |
| 420 | } |
| 421 | |
| 422 | QSSGRendererPrivate::PickResultList QSSGRendererPrivate::syncPickSubset(const QSSGRenderLayer &layer, |
| 423 | QSSGBufferManager &bufferManager, |
| 424 | const QSSGRenderRay &ray, |
| 425 | QVarLengthArray<QSSGRenderNode*> subset) |
| 426 | { |
| 427 | QSSGRendererPrivate::PickResultList pickResults; |
| 428 | Q_ASSERT(layer.getGlobalState(QSSGRenderNode::GlobalState::Active)); |
| 429 | |
| 430 | for (auto target : subset) |
| 431 | intersectRayWithSubsetRenderable(layer, bufferManager, inRay: ray, node: *target, outIntersectionResultList&: pickResults); |
| 432 | |
| 433 | std::stable_sort(first: pickResults.begin(), last: pickResults.end(), comp: [](const QSSGRenderPickResult &lhs, const QSSGRenderPickResult &rhs) { |
| 434 | return lhs.m_distanceSq < rhs.m_distanceSq; |
| 435 | }); |
| 436 | return pickResults; |
| 437 | } |
| 438 | |
| 439 | void QSSGRendererPrivate::setGlobalPickingEnabled(QSSGRenderer &renderer, bool isEnabled) |
| 440 | { |
| 441 | renderer.m_globalPickingEnabled = isEnabled; |
| 442 | } |
| 443 | |
| 444 | void QSSGRendererPrivate::setRenderContextInterface(QSSGRenderer &renderer, QSSGRenderContextInterface *ctx) |
| 445 | { |
| 446 | renderer.m_contextInterface = ctx; |
| 447 | } |
| 448 | |
| 449 | void QSSGRendererPrivate::setSgRenderContext(QSSGRenderer &renderer, QSGRenderContext *sgRenderCtx) |
| 450 | { |
| 451 | renderer.m_qsgRenderContext = sgRenderCtx; |
| 452 | } |
| 453 | |
| 454 | QSGRenderContext *QSSGRendererPrivate::getSgRenderContext(const QSSGRenderer &renderer) |
| 455 | { |
| 456 | return renderer.m_qsgRenderContext.data(); |
| 457 | } |
| 458 | |
| 459 | const std::unique_ptr<QSSGRhiQuadRenderer> &QSSGRenderer::rhiQuadRenderer() const |
| 460 | { |
| 461 | if (!m_rhiQuadRenderer) |
| 462 | m_rhiQuadRenderer = std::make_unique<QSSGRhiQuadRenderer>(); |
| 463 | |
| 464 | return m_rhiQuadRenderer; |
| 465 | } |
| 466 | |
| 467 | const std::unique_ptr<QSSGRhiCubeRenderer> &QSSGRenderer::rhiCubeRenderer() const |
| 468 | { |
| 469 | if (!m_rhiCubeRenderer) |
| 470 | m_rhiCubeRenderer = std::make_unique<QSSGRhiCubeRenderer>(); |
| 471 | |
| 472 | return m_rhiCubeRenderer; |
| 473 | |
| 474 | } |
| 475 | |
| 476 | void QSSGRenderer::beginSubLayerRender(QSSGLayerRenderData &inLayer) |
| 477 | { |
| 478 | inLayer.saveRenderState(renderer: *this); |
| 479 | m_currentLayer = nullptr; |
| 480 | } |
| 481 | |
| 482 | void QSSGRenderer::endSubLayerRender(QSSGLayerRenderData &inLayer) |
| 483 | { |
| 484 | inLayer.restoreRenderState(renderer&: *this); |
| 485 | m_currentLayer = &inLayer; |
| 486 | } |
| 487 | |
| 488 | void QSSGRenderer::beginLayerRender(QSSGLayerRenderData &inLayer) |
| 489 | { |
| 490 | m_currentLayer = &inLayer; |
| 491 | } |
| 492 | void QSSGRenderer::endLayerRender() |
| 493 | { |
| 494 | m_currentLayer = nullptr; |
| 495 | } |
| 496 | |
| 497 | using RenderableList = QVarLengthArray<const QSSGRenderNode *>; |
| 498 | static void dfs(const QSSGRenderNode &node, RenderableList &renderables) |
| 499 | { |
| 500 | if (QSSGRenderGraphObject::isRenderable(type: node.type)) |
| 501 | renderables.push_back(t: &node); |
| 502 | |
| 503 | for (const auto &child : node.children) |
| 504 | dfs(node: child, renderables); |
| 505 | } |
| 506 | |
| 507 | void QSSGRendererPrivate::getLayerHitObjectList(const QSSGRenderLayer &layer, |
| 508 | QSSGBufferManager &bufferManager, |
| 509 | const QSSGRenderRay &ray, |
| 510 | bool inPickEverything, |
| 511 | PickResultList &outIntersectionResult) |
| 512 | { |
| 513 | RenderableList renderables; |
| 514 | for (const auto &childNode : layer.children) |
| 515 | dfs(node: childNode, renderables); |
| 516 | |
| 517 | for (int idx = renderables.size() - 1; idx >= 0; --idx) { |
| 518 | const auto &pickableObject = renderables.at(idx); |
| 519 | if (inPickEverything || pickableObject->getLocalState(stateFlag: QSSGRenderNode::LocalState::Pickable)) |
| 520 | intersectRayWithSubsetRenderable(layer, bufferManager, inRay: ray, node: *pickableObject, outIntersectionResultList&: outIntersectionResult); |
| 521 | } |
| 522 | } |
| 523 | |
| 524 | void QSSGRendererPrivate::intersectRayWithSubsetRenderable(const QSSGRenderLayer &layer, |
| 525 | QSSGBufferManager &bufferManager, |
| 526 | const QSSGRenderRay &inRay, |
| 527 | const QSSGRenderNode &node, |
| 528 | PickResultList &outIntersectionResultList) |
| 529 | { |
| 530 | if (!layer.renderData) |
| 531 | return; |
| 532 | |
| 533 | const auto *renderData = layer.renderData; |
| 534 | |
| 535 | // Item2D's requires special handling |
| 536 | if (node.type == QSSGRenderGraphObject::Type::Item2D) { |
| 537 | const QSSGRenderItem2D &item2D = static_cast<const QSSGRenderItem2D &>(node); |
| 538 | intersectRayWithItem2D(layer, inRay, item2D, outIntersectionResultList); |
| 539 | return; |
| 540 | } |
| 541 | |
| 542 | if (node.type != QSSGRenderGraphObject::Type::Model) |
| 543 | return; |
| 544 | |
| 545 | const QSSGRenderModel &model = static_cast<const QSSGRenderModel &>(node); |
| 546 | |
| 547 | // We have to have a guard here, as the meshes are usually loaded on the render thread, |
| 548 | // and we assume all meshes are loaded before picking and none are removed, which |
| 549 | // is usually true, except for custom geometry which can be updated at any time. So this |
| 550 | // guard should really only be locked whenever a custom geometry buffer is being updated |
| 551 | // on the render thread. Still naughty though because this can block the render thread. |
| 552 | QMutexLocker mutexLocker(bufferManager.meshUpdateMutex()); |
| 553 | auto mesh = bufferManager.getMeshForPicking(model); |
| 554 | if (!mesh) |
| 555 | return; |
| 556 | |
| 557 | const auto &subMeshes = mesh->subsets; |
| 558 | QSSGBounds3 modelBounds; |
| 559 | for (const auto &subMesh : subMeshes) |
| 560 | modelBounds.include(b: subMesh.bounds); |
| 561 | |
| 562 | if (modelBounds.isEmpty()) |
| 563 | return; |
| 564 | |
| 565 | const bool instancing = model.instancing(); // && instancePickingEnabled |
| 566 | int instanceCount = instancing ? model.instanceTable->count() : 1; |
| 567 | |
| 568 | for (int instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) { |
| 569 | |
| 570 | QMatrix4x4 modelTransform; |
| 571 | if (instancing) { |
| 572 | const auto &[localInstanceTransform, globalInstanceTransform] = renderData->getInstanceTransforms(node: model); |
| 573 | modelTransform = globalInstanceTransform * model.instanceTable->getTransform(index: instanceIndex) * localInstanceTransform; |
| 574 | } else { |
| 575 | modelTransform = renderData->getGlobalTransform(node: model); |
| 576 | } |
| 577 | auto rayData = QSSGRenderRay::createRayData(globalTransform: modelTransform, ray: inRay); |
| 578 | |
| 579 | auto hit = QSSGRenderRay::intersectWithAABBv2(data: rayData, bounds: modelBounds); |
| 580 | |
| 581 | // If we don't intersect with the model at all, then there's no need to go furher down! |
| 582 | if (!hit.intersects()) |
| 583 | continue; |
| 584 | |
| 585 | // Check each submesh to find the closest intersection point |
| 586 | float minRayLength = std::numeric_limits<float>::max(); |
| 587 | QSSGRenderRay::IntersectionResult intersectionResult; |
| 588 | QVector<QSSGRenderRay::IntersectionResult> results; |
| 589 | |
| 590 | int subset = 0; |
| 591 | int resultSubset = 0; |
| 592 | for (const auto &subMesh : subMeshes) { |
| 593 | QSSGRenderRay::IntersectionResult result; |
| 594 | if (!subMesh.bvhRoot.isNull()) { |
| 595 | hit = QSSGRenderRay::intersectWithAABBv2(data: rayData, bounds: subMesh.bvhRoot->boundingData); |
| 596 | if (hit.intersects()) { |
| 597 | results.clear(); |
| 598 | inRay.intersectWithBVH(data: rayData, bvh: static_cast<const QSSGMeshBVHNode *>(subMesh.bvhRoot), mesh, intersections&: results); |
| 599 | float subMeshMinRayLength = std::numeric_limits<float>::max(); |
| 600 | for (const auto &subMeshResult : std::as_const(t&: results)) { |
| 601 | if (subMeshResult.rayLengthSquared < subMeshMinRayLength) { |
| 602 | result = subMeshResult; |
| 603 | subMeshMinRayLength = result.rayLengthSquared; |
| 604 | } |
| 605 | } |
| 606 | } |
| 607 | } else { |
| 608 | hit = QSSGRenderRay::intersectWithAABBv2(data: rayData, bounds: subMesh.bounds); |
| 609 | if (hit.intersects()) |
| 610 | result = QSSGRenderRay::createIntersectionResult(data: rayData, hit); |
| 611 | } |
| 612 | if (result.intersects && result.rayLengthSquared < minRayLength) { |
| 613 | intersectionResult = result; |
| 614 | minRayLength = intersectionResult.rayLengthSquared; |
| 615 | resultSubset = subset; |
| 616 | } |
| 617 | subset++; |
| 618 | } |
| 619 | |
| 620 | if (intersectionResult.intersects) |
| 621 | outIntersectionResultList.push_back(t: QSSGRenderPickResult { .m_hitObject: &model, |
| 622 | .m_distanceSq: intersectionResult.rayLengthSquared, |
| 623 | .m_localUVCoords: intersectionResult.relXY, |
| 624 | .m_scenePosition: intersectionResult.scenePosition, |
| 625 | .m_localPosition: intersectionResult.localPosition, |
| 626 | .m_faceNormal: intersectionResult.faceNormal, |
| 627 | .m_sceneNormal: intersectionResult.sceneFaceNormal, |
| 628 | .m_subset: resultSubset, |
| 629 | .m_instanceIndex: instanceIndex |
| 630 | }); |
| 631 | } |
| 632 | } |
| 633 | |
| 634 | void QSSGRendererPrivate::intersectRayWithItem2D(const QSSGRenderLayer &layer, |
| 635 | const QSSGRenderRay &inRay, |
| 636 | const QSSGRenderItem2D &item2D, |
| 637 | PickResultList &outIntersectionResultList) |
| 638 | { |
| 639 | const auto &globalTransform = layer.renderData->getGlobalTransform(node: item2D); |
| 640 | |
| 641 | // Get the plane (and normal) that the item 2D is on |
| 642 | const QVector3D p0 = QSSGRenderNode::getGlobalPos(globalTransform); |
| 643 | const QVector3D normal = -QSSGRenderNode::getDirection(globalTransform); |
| 644 | |
| 645 | const float d = QVector3D::dotProduct(v1: inRay.direction, v2: normal); |
| 646 | float intersectionTime = 0; |
| 647 | if (d > 1e-6f) { |
| 648 | const QVector3D p0l0 = p0 - inRay.origin; |
| 649 | intersectionTime = QVector3D::dotProduct(v1: p0l0, v2: normal) / d; |
| 650 | if (intersectionTime >= 0) { |
| 651 | // Intersection |
| 652 | const QVector3D intersectionPoint = inRay.origin + inRay.direction * intersectionTime; |
| 653 | const QMatrix4x4 inverseGlobalTransform = globalTransform.inverted(); |
| 654 | const QVector3D localIntersectionPoint = QSSGUtils::mat44::transform(m: inverseGlobalTransform, v: intersectionPoint); |
| 655 | const QVector2D qmlCoordinate(localIntersectionPoint.x(), -localIntersectionPoint.y()); |
| 656 | outIntersectionResultList.push_back(t: QSSGRenderPickResult { .m_hitObject: &item2D, |
| 657 | .m_distanceSq: intersectionTime * intersectionTime, |
| 658 | .m_localUVCoords: qmlCoordinate, |
| 659 | .m_scenePosition: intersectionPoint, |
| 660 | .m_localPosition: localIntersectionPoint, |
| 661 | .m_faceNormal: -normal, .m_sceneNormal: -normal }); |
| 662 | } |
| 663 | } |
| 664 | } |
| 665 | |
| 666 | QSSGRhiShaderPipelinePtr QSSGRendererPrivate::getShaderPipelineForDefaultMaterial(QSSGRenderer &renderer, |
| 667 | QSSGSubsetRenderable &inRenderable, |
| 668 | const QSSGShaderFeatures &inFeatureSet) |
| 669 | { |
| 670 | auto *m_currentLayer = renderer.m_currentLayer; |
| 671 | QSSG_ASSERT(m_currentLayer != nullptr, return {}); |
| 672 | |
| 673 | // This function is the main entry point for retrieving the shaders for a |
| 674 | // default material, and is called for every material for every model in |
| 675 | // every frame. Therefore, like with custom materials, employ a first level |
| 676 | // cache (a simple hash table), with a key that's quick to |
| 677 | // generate/hash/compare. Even though there are other levels of caching in |
| 678 | // the components that get invoked from here, those may not be suitable |
| 679 | // performance wise. So bail out right here as soon as possible. |
| 680 | auto &shaderMap = m_currentLayer->shaderMap; |
| 681 | |
| 682 | QElapsedTimer timer; |
| 683 | timer.start(); |
| 684 | |
| 685 | QSSGRhiShaderPipelinePtr shaderPipeline; |
| 686 | |
| 687 | // This just references inFeatureSet and inRenderable.shaderDescription - |
| 688 | // cheap to construct and is good enough for the find() |
| 689 | QSSGShaderMapKey skey = QSSGShaderMapKey(QByteArray(), |
| 690 | inFeatureSet, |
| 691 | inRenderable.shaderDescription); |
| 692 | auto it = shaderMap.find(key: skey); |
| 693 | if (it == shaderMap.end()) { |
| 694 | Q_TRACE_SCOPE(QSSG_generateShader); |
| 695 | Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DGenerateShader); |
| 696 | shaderPipeline = QSSGRendererPrivate::generateRhiShaderPipeline(renderer, inRenderable, inFeatureSet); |
| 697 | Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DGenerateShader, 0, inRenderable.material.profilingId); |
| 698 | // make skey useable as a key for the QHash (makes a copy of the materialKey, instead of just referencing) |
| 699 | skey.detach(); |
| 700 | // insert it no matter what, no point in trying over and over again |
| 701 | shaderMap.insert(key: skey, value: shaderPipeline); |
| 702 | } else { |
| 703 | shaderPipeline = it.value(); |
| 704 | } |
| 705 | |
| 706 | if (shaderPipeline != nullptr) { |
| 707 | if (m_currentLayer && !m_currentLayer->renderedCameras.isEmpty()) |
| 708 | m_currentLayer->ensureCachedCameraDatas(); |
| 709 | } |
| 710 | |
| 711 | const auto &rhiContext = renderer.m_contextInterface->rhiContext(); |
| 712 | QSSGRhiContextStats::get(rhiCtx&: *rhiContext).registerMaterialShaderGenerationTime(ms: timer.elapsed()); |
| 713 | |
| 714 | return shaderPipeline; |
| 715 | } |
| 716 | |
| 717 | QT_END_NAMESPACE |
| 718 | |