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