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 &m_currentLayer = renderer.m_currentLayer; |
277 | auto &m_generatedShaderString = renderer.m_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: m_currentLayer->defaultMaterialShaderKeyProperties, featureSet: inFeatureSet, shaderString&: m_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::beginLayerRender(QSSGLayerRenderData &inLayer) |
403 | { |
404 | m_currentLayer = &inLayer; |
405 | } |
406 | void QSSGRenderer::endLayerRender() |
407 | { |
408 | m_currentLayer = nullptr; |
409 | } |
410 | |
411 | using RenderableList = QVarLengthArray<const QSSGRenderNode *>; |
412 | static void dfs(const QSSGRenderNode &node, RenderableList &renderables) |
413 | { |
414 | if (QSSGRenderGraphObject::isRenderable(type: node.type)) |
415 | renderables.push_back(t: &node); |
416 | |
417 | for (const auto &child : node.children) |
418 | dfs(node: child, renderables); |
419 | } |
420 | |
421 | void QSSGRendererPrivate::getLayerHitObjectList(const QSSGRenderLayer &layer, |
422 | QSSGBufferManager &bufferManager, |
423 | const QSSGRenderRay &ray, |
424 | bool inPickEverything, |
425 | PickResultList &outIntersectionResult) |
426 | { |
427 | RenderableList renderables; |
428 | for (const auto &childNode : layer.children) |
429 | dfs(node: childNode, renderables); |
430 | |
431 | for (int idx = renderables.size() - 1; idx >= 0; --idx) { |
432 | const auto &pickableObject = renderables.at(idx); |
433 | if (inPickEverything || pickableObject->getLocalState(stateFlag: QSSGRenderNode::LocalState::Pickable)) |
434 | intersectRayWithSubsetRenderable(bufferManager, inRay: ray, node: *pickableObject, outIntersectionResultList&: outIntersectionResult); |
435 | } |
436 | } |
437 | |
438 | void QSSGRendererPrivate::intersectRayWithSubsetRenderable(QSSGBufferManager &bufferManager, |
439 | const QSSGRenderRay &inRay, |
440 | const QSSGRenderNode &node, |
441 | PickResultList &outIntersectionResultList) |
442 | { |
443 | // Item2D's requires special handling |
444 | if (node.type == QSSGRenderGraphObject::Type::Item2D) { |
445 | const QSSGRenderItem2D &item2D = static_cast<const QSSGRenderItem2D &>(node); |
446 | intersectRayWithItem2D(inRay, item2D, outIntersectionResultList); |
447 | return; |
448 | } |
449 | |
450 | if (node.type != QSSGRenderGraphObject::Type::Model) |
451 | return; |
452 | |
453 | const QSSGRenderModel &model = static_cast<const QSSGRenderModel &>(node); |
454 | |
455 | // We have to have a guard here, as the meshes are usually loaded on the render thread, |
456 | // and we assume all meshes are loaded before picking and none are removed, which |
457 | // is usually true, except for custom geometry which can be updated at any time. So this |
458 | // guard should really only be locked whenever a custom geometry buffer is being updated |
459 | // on the render thread. Still naughty though because this can block the render thread. |
460 | QMutexLocker mutexLocker(bufferManager.meshUpdateMutex()); |
461 | auto mesh = bufferManager.getMeshForPicking(model); |
462 | if (!mesh) |
463 | return; |
464 | |
465 | const auto &subMeshes = mesh->subsets; |
466 | QSSGBounds3 modelBounds; |
467 | for (const auto &subMesh : subMeshes) |
468 | modelBounds.include(b: subMesh.bounds); |
469 | |
470 | if (modelBounds.isEmpty()) |
471 | return; |
472 | |
473 | const bool instancing = model.instancing(); // && instancePickingEnabled |
474 | int instanceCount = instancing ? model.instanceTable->count() : 1; |
475 | |
476 | for (int instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) { |
477 | |
478 | QMatrix4x4 modelTransform; |
479 | if (instancing) { |
480 | modelTransform = model.globalInstanceTransform * model.instanceTable->getTransform(index: instanceIndex) * model.localInstanceTransform; |
481 | } else { |
482 | modelTransform = model.globalTransform; |
483 | } |
484 | auto rayData = QSSGRenderRay::createRayData(globalTransform: modelTransform, ray: inRay); |
485 | |
486 | auto hit = QSSGRenderRay::intersectWithAABBv2(data: rayData, bounds: modelBounds); |
487 | |
488 | // If we don't intersect with the model at all, then there's no need to go furher down! |
489 | if (!hit.intersects()) |
490 | continue; |
491 | |
492 | // Check each submesh to find the closest intersection point |
493 | float minRayLength = std::numeric_limits<float>::max(); |
494 | QSSGRenderRay::IntersectionResult intersectionResult; |
495 | QVector<QSSGRenderRay::IntersectionResult> results; |
496 | |
497 | int subset = 0; |
498 | int resultSubset = 0; |
499 | for (const auto &subMesh : subMeshes) { |
500 | QSSGRenderRay::IntersectionResult result; |
501 | if (!subMesh.bvhRoot.isNull()) { |
502 | hit = QSSGRenderRay::intersectWithAABBv2(data: rayData, bounds: subMesh.bvhRoot->boundingData); |
503 | if (hit.intersects()) { |
504 | results.clear(); |
505 | inRay.intersectWithBVH(data: rayData, bvh: static_cast<const QSSGMeshBVHNode *>(subMesh.bvhRoot), mesh, intersections&: results); |
506 | float subMeshMinRayLength = std::numeric_limits<float>::max(); |
507 | for (const auto &subMeshResult : std::as_const(t&: results)) { |
508 | if (subMeshResult.rayLengthSquared < subMeshMinRayLength) { |
509 | result = subMeshResult; |
510 | subMeshMinRayLength = result.rayLengthSquared; |
511 | } |
512 | } |
513 | } |
514 | } else { |
515 | hit = QSSGRenderRay::intersectWithAABBv2(data: rayData, bounds: subMesh.bounds); |
516 | if (hit.intersects()) |
517 | result = QSSGRenderRay::createIntersectionResult(data: rayData, hit); |
518 | } |
519 | if (result.intersects && result.rayLengthSquared < minRayLength) { |
520 | intersectionResult = result; |
521 | minRayLength = intersectionResult.rayLengthSquared; |
522 | resultSubset = subset; |
523 | } |
524 | subset++; |
525 | } |
526 | |
527 | if (intersectionResult.intersects) |
528 | outIntersectionResultList.push_back(t: QSSGRenderPickResult { .m_hitObject: &model, |
529 | .m_distanceSq: intersectionResult.rayLengthSquared, |
530 | .m_localUVCoords: intersectionResult.relXY, |
531 | .m_scenePosition: intersectionResult.scenePosition, |
532 | .m_localPosition: intersectionResult.localPosition, |
533 | .m_faceNormal: intersectionResult.faceNormal, |
534 | .m_subset: resultSubset, |
535 | .m_instanceIndex: instanceIndex |
536 | }); |
537 | } |
538 | } |
539 | |
540 | void QSSGRendererPrivate::intersectRayWithItem2D(const QSSGRenderRay &inRay, const QSSGRenderItem2D &item2D, PickResultList &outIntersectionResultList) |
541 | { |
542 | // Get the plane (and normal) that the item 2D is on |
543 | const QVector3D p0 = item2D.getGlobalPos(); |
544 | const QVector3D normal = -item2D.getDirection(); |
545 | |
546 | const float d = QVector3D::dotProduct(v1: inRay.direction, v2: normal); |
547 | float intersectionTime = 0; |
548 | if (d > 1e-6f) { |
549 | const QVector3D p0l0 = p0 - inRay.origin; |
550 | intersectionTime = QVector3D::dotProduct(v1: p0l0, v2: normal) / d; |
551 | if (intersectionTime >= 0) { |
552 | // Intersection |
553 | const QVector3D intersectionPoint = inRay.origin + inRay.direction * intersectionTime; |
554 | const QMatrix4x4 inverseGlobalTransform = item2D.globalTransform.inverted(); |
555 | const QVector3D localIntersectionPoint = QSSGUtils::mat44::transform(m: inverseGlobalTransform, v: intersectionPoint); |
556 | const QVector2D qmlCoordinate(localIntersectionPoint.x(), -localIntersectionPoint.y()); |
557 | outIntersectionResultList.push_back(t: QSSGRenderPickResult { .m_hitObject: &item2D, |
558 | .m_distanceSq: intersectionTime * intersectionTime, |
559 | .m_localUVCoords: qmlCoordinate, |
560 | .m_scenePosition: intersectionPoint, |
561 | .m_localPosition: localIntersectionPoint, |
562 | .m_faceNormal: -normal }); |
563 | } |
564 | } |
565 | } |
566 | |
567 | QSSGRhiShaderPipelinePtr QSSGRendererPrivate::getShaderPipelineForDefaultMaterial(QSSGRenderer &renderer, |
568 | QSSGSubsetRenderable &inRenderable, |
569 | const QSSGShaderFeatures &inFeatureSet) |
570 | { |
571 | auto *m_currentLayer = renderer.m_currentLayer; |
572 | QSSG_ASSERT(m_currentLayer != nullptr, return {}); |
573 | |
574 | // This function is the main entry point for retrieving the shaders for a |
575 | // default material, and is called for every material for every model in |
576 | // every frame. Therefore, like with custom materials, employ a first level |
577 | // cache (a simple hash table), with a key that's quick to |
578 | // generate/hash/compare. Even though there are other levels of caching in |
579 | // the components that get invoked from here, those may not be suitable |
580 | // performance wise. So bail out right here as soon as possible. |
581 | auto &shaderMap = m_currentLayer->shaderMap; |
582 | |
583 | QElapsedTimer timer; |
584 | timer.start(); |
585 | |
586 | QSSGRhiShaderPipelinePtr shaderPipeline; |
587 | |
588 | // This just references inFeatureSet and inRenderable.shaderDescription - |
589 | // cheap to construct and is good enough for the find() |
590 | QSSGShaderMapKey skey = QSSGShaderMapKey(QByteArray(), |
591 | inFeatureSet, |
592 | inRenderable.shaderDescription); |
593 | auto it = shaderMap.find(key: skey); |
594 | if (it == shaderMap.end()) { |
595 | Q_TRACE_SCOPE(QSSG_generateShader); |
596 | Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DGenerateShader); |
597 | shaderPipeline = QSSGRendererPrivate::generateRhiShaderPipeline(renderer, inRenderable, inFeatureSet); |
598 | Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DGenerateShader, 0, inRenderable.material.profilingId); |
599 | // make skey useable as a key for the QHash (makes a copy of the materialKey, instead of just referencing) |
600 | skey.detach(); |
601 | // insert it no matter what, no point in trying over and over again |
602 | shaderMap.insert(key: skey, value: shaderPipeline); |
603 | } else { |
604 | shaderPipeline = it.value(); |
605 | } |
606 | |
607 | if (shaderPipeline != nullptr) { |
608 | if (m_currentLayer && !m_currentLayer->renderedCameras.isEmpty()) |
609 | m_currentLayer->ensureCachedCameraDatas(); |
610 | } |
611 | |
612 | const auto &rhiContext = renderer.m_contextInterface->rhiContext(); |
613 | QSSGRhiContextStats::get(rhiCtx&: *rhiContext).registerMaterialShaderGenerationTime(ms: timer.elapsed()); |
614 | |
615 | return shaderPipeline; |
616 | } |
617 | |
618 | QT_END_NAMESPACE |
619 | |