1// Copyright (C) 2020 Klaralvdalens Datakonsult AB (KDAB).
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "calcboundingvolumejob_p.h"
5
6#include <Qt3DCore/qattribute.h>
7#include <Qt3DCore/qboundingvolume.h>
8#include <Qt3DCore/qbuffer.h>
9#include <Qt3DCore/qgeometryview.h>
10#include <Qt3DCore/qcoreaspect.h>
11#include <Qt3DCore/private/job_common_p.h>
12#include <Qt3DCore/private/qcoreaspect_p.h>
13#include <Qt3DCore/private/qaspectjob_p.h>
14#include <Qt3DCore/private/qaspectmanager_p.h>
15#include <Qt3DCore/private/qattribute_p.h>
16#include <Qt3DCore/private/qboundingvolume_p.h>
17#include <Qt3DCore/private/qbuffer_p.h>
18#include <Qt3DCore/private/qentity_p.h>
19#include <Qt3DCore/private/qgeometry_p.h>
20#include <Qt3DCore/private/qgeometryview_p.h>
21#include <Qt3DCore/private/qnodevisitor_p.h>
22#include <Qt3DCore/private/qthreadpooler_p.h>
23
24#include <QtCore/qmath.h>
25#if QT_CONFIG(concurrent)
26#include <QtConcurrent/QtConcurrent>
27#endif
28
29QT_BEGIN_NAMESPACE
30
31namespace Qt3DCore {
32
33namespace {
34
35BoundingVolumeComputeData findBoundingVolumeComputeData(QGeometryView *node)
36{
37 if (!node->isEnabled())
38 return {};
39
40 if (node->primitiveType() == QGeometryView::Patches)
41 return {};
42
43 QGeometry *geom = node->geometry();
44 QGeometryPrivate *dgeom = QGeometryPrivate::get(q: geom);
45 if (!geom)
46 return {};
47
48 int drawVertexCount = node->vertexCount(); // may be 0, gets changed below if so
49
50 QAttribute *positionAttribute = dgeom->m_boundingVolumePositionAttribute;
51 const QList<Qt3DCore::QAttribute *> attributes = geom->attributes();
52
53 // Use the default position attribute if attribute is null
54 if (!positionAttribute) {
55 for (QAttribute *attr : attributes) {
56 if (attr->name() == QAttribute::defaultPositionAttributeName()) {
57 positionAttribute = attr;
58 break;
59 }
60 }
61 }
62
63 if (!positionAttribute
64 || positionAttribute->attributeType() != QAttribute::VertexAttribute
65 || positionAttribute->vertexBaseType() != QAttribute::Float
66 || positionAttribute->vertexSize() < 3) {
67 qWarning(msg: "findBoundingVolumeComputeData: Position attribute not suited for bounding volume computation");
68 return {};
69 }
70
71 Qt3DCore::QBuffer *positionBuffer = positionAttribute->buffer();
72 // No point in continuing if the positionAttribute doesn't have a suitable buffer
73 if (!positionBuffer) {
74 qWarning(msg: "findBoundingVolumeComputeData: Position attribute not referencing a valid buffer");
75 return {};
76 }
77
78 // Check if there is an index attribute.
79 QAttribute *indexAttribute = nullptr;
80 Qt3DCore::QBuffer *indexBuffer = nullptr;
81
82 for (const auto attr : attributes) {
83 if (attr->attributeType() == QAttribute::IndexAttribute) {
84 indexBuffer = attr->buffer();
85 if (indexBuffer) {
86 indexAttribute = attr;
87
88 if (!drawVertexCount)
89 drawVertexCount = static_cast<int>(indexAttribute->count());
90
91 static const QAttribute::VertexBaseType validIndexTypes[] = {
92 QAttribute::UnsignedShort,
93 QAttribute::UnsignedInt,
94 QAttribute::UnsignedByte
95 };
96
97 if (std::find(first: std::begin(arr: validIndexTypes),
98 last: std::end(arr: validIndexTypes),
99 val: indexAttribute->vertexBaseType()) == std::end(arr: validIndexTypes)) {
100 qWarning() << "findBoundingVolumeComputeData: Unsupported index attribute type" << indexAttribute->name() << indexAttribute->vertexBaseType();
101 return {};
102 }
103
104 break;
105 }
106 }
107 }
108
109 if (!indexAttribute && !drawVertexCount)
110 drawVertexCount = static_cast<int>(positionAttribute->count());
111
112 return { .entity: nullptr, .provider: nullptr, .positionAttribute: positionAttribute, .indexAttribute: indexAttribute, .vertexCount: drawVertexCount };
113}
114
115bool isTreeEnabled(QEntity *entity) {
116 if (!entity->isEnabled())
117 return false;
118
119 QEntity *parent = entity->parentEntity();
120 while (parent) {
121 if (!parent->isEnabled())
122 return false;
123 parent = parent->parentEntity();
124 }
125
126 return true;
127}
128
129struct UpdateBoundFunctor
130{
131 // This define is required to work with QtConcurrent
132 typedef std::vector<BoundingVolumeComputeResult> result_type;
133 result_type operator ()(const BoundingVolumeComputeData &data)
134 {
135 return { data.compute() };
136 }
137};
138
139struct ReduceUpdateBoundFunctor
140{
141 void operator ()(std::vector<BoundingVolumeComputeResult> &result, const std::vector<BoundingVolumeComputeResult> &values)
142 {
143 for (const auto &value : values) {
144 if (value.valid()) {
145 result.push_back(x: value);
146 }
147 }
148 }
149};
150
151} // anonymous
152
153
154BoundingVolumeComputeData BoundingVolumeComputeData::fromView(QGeometryView *view)
155{
156 return findBoundingVolumeComputeData(node: view);
157}
158
159BoundingVolumeComputeResult BoundingVolumeComputeData::compute() const
160{
161 BoundingVolumeCalculator calculator;
162 if (calculator.apply(positionAttribute, indexAttribute, drawVertexCount: vertexCount,
163 primitiveRestartEnabled: provider->view()->primitiveRestartEnabled(),
164 primitiveRestartIndex: provider->view()->restartIndexValue()))
165 return {
166 .entity: entity, .provider: provider, .positionAttribute: positionAttribute, .indexAttribute: indexAttribute,
167 .m_min: calculator.min(), .m_max: calculator.max(),
168 .m_center: calculator.center(), .m_radius: calculator.radius()
169 };
170 return {};
171}
172
173
174CalculateBoundingVolumeJob::CalculateBoundingVolumeJob(QCoreAspect *aspect)
175 : Qt3DCore::QAspectJob()
176 , m_aspect(aspect)
177 , m_root(nullptr)
178{
179 SET_JOB_RUN_STAT_TYPE(this, JobTypes::CalcBoundingVolume, 0)
180}
181
182bool CalculateBoundingVolumeJob::isRequired()
183{
184 if (!m_aspect)
185 return true;
186
187 auto daspect = QCoreAspectPrivate::get(aspect: m_aspect);
188 return daspect->m_boundingVolumesEnabled;
189}
190
191void CalculateBoundingVolumeJob::run()
192{
193 using QBufferPrivate = Qt3DCore::QBufferPrivate;
194
195 // There's 2 bounding volume jobs, one here in Core, the other in Render.
196 // - This one computes bounding volumes for entities that have QBoundingVolume
197 // components and use QGeometryViews.
198 // In that case the component is updated directly by this job (since core
199 // aspect does not maintain backend objects for the component)
200 // - The one in render does 2 things:
201 // . Copy the results of this job to the backend object for entities that
202 // use QBoundingVolume and QGeometryView (computed results arrive one
203 // frame later, explicit results arrive on time)
204 // . Compute the BV for old style QGeometryRenderer which use a QGeometry
205 // directly without a QGeometryView
206 //
207 // (see more details in Qt3DRender::CalculateBoundingVolumeJob::run)
208
209 m_results.clear();
210
211 QHash<QEntity *, BoundingVolumeComputeData> dirtyEntities;
212 QNodeVisitor visitor;
213 visitor.traverse(rootNode_: m_root, fN: [](QNode *) {}, fE: [&dirtyEntities, this](QEntity *entity) {
214 if (!isTreeEnabled(entity))
215 return;
216
217 const auto bvProviders = entity->componentsOfType<QBoundingVolume>();
218 if (bvProviders.isEmpty())
219 return;
220
221 // we go through the list until be find a dirty provider,
222 // or THE primary provider
223 bool foundBV = false;
224 for (auto bv: bvProviders) {
225 auto dbv = QBoundingVolumePrivate::get(q: bv);
226 if (foundBV && !dbv->m_primaryProvider)
227 continue;
228
229 BoundingVolumeComputeData bvdata;
230 if (dbv->m_explicitPointsValid) {
231 // we have data explicitly set by the user, pass it to the
232 // watchers as computed data
233 BoundingVolumeComputeResult r;
234 r.entity = entity;
235 r.provider = bv;
236 r.m_min = dbv->m_minPoint;
237 r.m_max = dbv->m_maxPoint;
238 const auto diagonal = r.m_max - r.m_min;
239 r.m_center = r.m_min + diagonal * .5f;
240 r.m_radius = diagonal.length();
241
242 for (auto w: m_watchers) {
243 auto wp = w.toStrongRef();
244 if (wp)
245 wp->process(result: r, computedResult: false);
246 }
247 continue;
248 } else if (bv->view()) {
249 bvdata = findBoundingVolumeComputeData(node: bv->view());
250 if (!bvdata.valid())
251 continue;
252 bvdata.entity = entity;
253 bvdata.provider = bv;
254 } else {
255 // no view, can't compute
256 continue;
257 }
258
259 bool dirty = QEntityPrivate::get(q: entity)->m_dirty;
260 dirty |= QGeometryViewPrivate::get(q: bv->view())->m_dirty;
261 dirty |= QGeometryPrivate::get(q: bv->view()->geometry())->m_dirty;
262 dirty |= QAttributePrivate::get(q: bvdata.positionAttribute)->m_dirty;
263 dirty |= QBufferPrivate::get(q: bvdata.positionAttribute->buffer())->m_dirty;
264 if (bvdata.indexAttribute) {
265 dirty |= QAttributePrivate::get(q: bvdata.indexAttribute)->m_dirty;
266 dirty |= QBufferPrivate::get(q: bvdata.indexAttribute->buffer())->m_dirty;
267 }
268
269 if (dbv->m_primaryProvider) {
270 if (dirty)
271 dirtyEntities[entity] = bvdata;
272 break;
273 } else if (dirty) {
274 dirtyEntities[entity] = bvdata;
275 foundBV = true;
276 }
277 }
278 });
279
280#if QT_CONFIG(concurrent)
281 if (dirtyEntities.size() > 1 && QAspectJobManager::idealThreadCount() > 1) {
282 UpdateBoundFunctor functor;
283 ReduceUpdateBoundFunctor reduceFunctor;
284 m_results = QtConcurrent::blockingMappedReduced<decltype(m_results)>(sequence&: dirtyEntities, map&: functor, reduce&: reduceFunctor);
285 } else
286#endif
287 {
288 for (auto it = dirtyEntities.begin(); it != dirtyEntities.end(); ++it) {
289 auto res = it.value().compute();
290 if (res.valid())
291 m_results.push_back(x: res); // How do we push it to the backends????
292 }
293 }
294
295 // pass the computed results to the watchers
296 for (auto &watcher: m_watchers) {
297 auto watcherPtr = watcher.toStrongRef();
298 if (watcherPtr) {
299 for (const auto &r: m_results)
300 watcherPtr->process(result: r, computedResult: true);
301 }
302 }
303}
304
305void CalculateBoundingVolumeJob::postFrame(QAspectEngine *aspectEngine)
306{
307 Q_UNUSED(aspectEngine);
308
309 for (auto result: m_results) {
310 // set the results
311 QBoundingVolumePrivate::get(q: result.provider)->setImplicitBounds(minPoint: result.m_min, maxPoint: result.m_max, center: result.m_center, radius: result.m_radius);
312
313 // reset dirty flags
314 QEntityPrivate::get(q: result.entity)->m_dirty = false;
315 QGeometryViewPrivate::get(q: result.provider->view())->m_dirty = false;
316 QGeometryPrivate::get(q: result.provider->view()->geometry())->m_dirty = false;
317 QAttributePrivate::get(q: result.positionAttribute)->m_dirty = false;
318 QBufferPrivate::get(q: result.positionAttribute->buffer())->m_dirty = false;
319 if (result.indexAttribute) {
320 QAttributePrivate::get(q: result.indexAttribute)->m_dirty = false;
321 QBufferPrivate::get(q: result.indexAttribute->buffer())->m_dirty = false;
322 }
323 }
324
325 m_results.clear();
326}
327
328void CalculateBoundingVolumeJob::addWatcher(QWeakPointer<BoundingVolumeJobProcessor> watcher)
329{
330 m_watchers.push_back(x: watcher);
331}
332
333void CalculateBoundingVolumeJob::removeWatcher(QWeakPointer<BoundingVolumeJobProcessor> watcher)
334{
335 if (watcher.isNull()) {
336 m_watchers.erase(first: std::remove_if(first: m_watchers.begin(), last: m_watchers.end(), pred: [](const QWeakPointer<BoundingVolumeJobProcessor> &w) { return w.isNull(); }),
337 last: m_watchers.end());
338 } else {
339 m_watchers.erase(first: std::remove(first: m_watchers.begin(), last: m_watchers.end(), value: watcher),
340 last: m_watchers.end());
341 }
342}
343
344} // namespace Qt3DCore
345
346QT_END_NAMESPACE
347
348

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qt3d/src/core/jobs/calcboundingvolumejob.cpp