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 | |
29 | QT_BEGIN_NAMESPACE |
30 | |
31 | namespace Qt3DCore { |
32 | |
33 | namespace { |
34 | |
35 | BoundingVolumeComputeData 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 | |
115 | bool 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 | |
129 | struct 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 | |
139 | struct ReduceUpdateBoundFunctor |
140 | { |
141 | void operator ()(std::vector<BoundingVolumeComputeResult> &result, const std::vector<BoundingVolumeComputeResult> &values) |
142 | { |
143 | result.insert(position: result.end(), |
144 | first: std::make_move_iterator(i: values.begin()), |
145 | last: std::make_move_iterator(i: values.end())); |
146 | } |
147 | }; |
148 | |
149 | } // anonymous |
150 | |
151 | |
152 | BoundingVolumeComputeData BoundingVolumeComputeData::fromView(QGeometryView *view) |
153 | { |
154 | return findBoundingVolumeComputeData(node: view); |
155 | } |
156 | |
157 | BoundingVolumeComputeResult BoundingVolumeComputeData::compute() const |
158 | { |
159 | BoundingVolumeCalculator calculator; |
160 | if (calculator.apply(positionAttribute, indexAttribute, drawVertexCount: vertexCount, |
161 | primitiveRestartEnabled: provider->view()->primitiveRestartEnabled(), |
162 | primitiveRestartIndex: provider->view()->restartIndexValue())) |
163 | return { |
164 | .entity: entity, .provider: provider, .positionAttribute: positionAttribute, .indexAttribute: indexAttribute, |
165 | .m_min: calculator.min(), .m_max: calculator.max(), |
166 | .m_center: calculator.center(), .m_radius: calculator.radius() |
167 | }; |
168 | return {}; |
169 | } |
170 | |
171 | |
172 | CalculateBoundingVolumeJob::CalculateBoundingVolumeJob(QCoreAspect *aspect) |
173 | : Qt3DCore::QAspectJob() |
174 | , m_aspect(aspect) |
175 | , m_root(nullptr) |
176 | { |
177 | SET_JOB_RUN_STAT_TYPE(this, JobTypes::CalcBoundingVolume, 0) |
178 | } |
179 | |
180 | bool CalculateBoundingVolumeJob::isRequired() |
181 | { |
182 | if (!m_aspect) |
183 | return true; |
184 | |
185 | auto daspect = QCoreAspectPrivate::get(aspect: m_aspect); |
186 | return daspect->m_boundingVolumesEnabled; |
187 | } |
188 | |
189 | void CalculateBoundingVolumeJob::run() |
190 | { |
191 | // There's 2 bounding volume jobs, one here in Core, the other in Render. |
192 | // - This one computes bounding volumes for entities that have QBoundingVolume |
193 | // components and use QGeometryViews. |
194 | // In that case the component is updated directly by this job (since core |
195 | // aspect does not maintain backend objects for the component) |
196 | // - The one in render does 2 things: |
197 | // . Copy the results of this job to the backend object for entities that |
198 | // use QBoundingVolume and QGeometryView (computed results arrive one |
199 | // frame later, explicit results arrive on time) |
200 | // . Compute the BV for old style QGeometryRenderer which use a QGeometry |
201 | // directly without a QGeometryView |
202 | // |
203 | // (see more details in Qt3DRender::CalculateBoundingVolumeJob::run) |
204 | |
205 | m_results.clear(); |
206 | |
207 | QHash<QEntity *, BoundingVolumeComputeData> dirtyEntities; |
208 | QNodeVisitor visitor; |
209 | visitor.traverse(rootNode_: m_root, fN: [](QNode *) {}, fE: [&dirtyEntities, this](QEntity *entity) { |
210 | if (!isTreeEnabled(entity)) |
211 | return; |
212 | |
213 | const auto bvProviders = entity->componentsOfType<QBoundingVolume>(); |
214 | if (bvProviders.isEmpty()) |
215 | return; |
216 | |
217 | // we go through the list until be find a dirty provider, |
218 | // or THE primary provider |
219 | bool foundBV = false; |
220 | for (auto bv: bvProviders) { |
221 | auto dbv = QBoundingVolumePrivate::get(q: bv); |
222 | if (foundBV && !dbv->m_primaryProvider) |
223 | continue; |
224 | |
225 | BoundingVolumeComputeData bvdata; |
226 | if (dbv->m_explicitPointsValid) { |
227 | // we have data explicitly set by the user, pass it to the |
228 | // watchers as computed data |
229 | BoundingVolumeComputeResult r; |
230 | r.entity = entity; |
231 | r.provider = bv; |
232 | r.m_min = dbv->m_minPoint; |
233 | r.m_max = dbv->m_maxPoint; |
234 | const auto diagonal = r.m_max - r.m_min; |
235 | r.m_center = r.m_min + diagonal * .5f; |
236 | r.m_radius = diagonal.length(); |
237 | |
238 | for (auto w: m_watchers) { |
239 | auto wp = w.toStrongRef(); |
240 | if (wp) |
241 | wp->process(result: r, computedResult: false); |
242 | } |
243 | continue; |
244 | } else if (bv->view()) { |
245 | bvdata = findBoundingVolumeComputeData(node: bv->view()); |
246 | if (!bvdata.valid()) |
247 | continue; |
248 | bvdata.entity = entity; |
249 | bvdata.provider = bv; |
250 | } else { |
251 | // no view, can't compute |
252 | continue; |
253 | } |
254 | |
255 | bool dirty = QEntityPrivate::get(q: entity)->m_dirty; |
256 | dirty |= QGeometryViewPrivate::get(q: bv->view())->m_dirty; |
257 | dirty |= QGeometryPrivate::get(q: bv->view()->geometry())->m_dirty; |
258 | dirty |= QAttributePrivate::get(q: bvdata.positionAttribute)->m_dirty; |
259 | dirty |= QBufferPrivate::get(q: bvdata.positionAttribute->buffer())->m_dirty; |
260 | if (bvdata.indexAttribute) { |
261 | dirty |= QAttributePrivate::get(q: bvdata.indexAttribute)->m_dirty; |
262 | dirty |= QBufferPrivate::get(q: bvdata.indexAttribute->buffer())->m_dirty; |
263 | } |
264 | |
265 | if (dbv->m_primaryProvider) { |
266 | if (dirty) |
267 | dirtyEntities[entity] = bvdata; |
268 | break; |
269 | } else if (dirty) { |
270 | dirtyEntities[entity] = bvdata; |
271 | foundBV = true; |
272 | } |
273 | } |
274 | }); |
275 | |
276 | #if QT_CONFIG(concurrent) |
277 | if (dirtyEntities.size() > 1 && QAspectJobManager::idealThreadCount() > 1) { |
278 | UpdateBoundFunctor functor; |
279 | ReduceUpdateBoundFunctor reduceFunctor; |
280 | m_results = QtConcurrent::blockingMappedReduced<decltype(m_results)>(sequence&: dirtyEntities, map&: functor, reduce&: reduceFunctor); |
281 | } else |
282 | #endif |
283 | { |
284 | for (auto it = dirtyEntities.begin(); it != dirtyEntities.end(); ++it) { |
285 | auto res = it.value().compute(); |
286 | if (res.valid()) |
287 | m_results.push_back(x: res); // How do we push it to the backends???? |
288 | } |
289 | } |
290 | |
291 | // pass the computed results to the watchers |
292 | for (auto &watcher: m_watchers) { |
293 | auto watcherPtr = watcher.toStrongRef(); |
294 | if (watcherPtr) { |
295 | for (const auto &r: m_results) |
296 | watcherPtr->process(result: r, computedResult: true); |
297 | } |
298 | } |
299 | } |
300 | |
301 | void CalculateBoundingVolumeJob::postFrame(QAspectEngine *aspectEngine) |
302 | { |
303 | Q_UNUSED(aspectEngine); |
304 | |
305 | for (auto result: m_results) { |
306 | // set the results |
307 | QBoundingVolumePrivate::get(q: result.provider)->setImplicitBounds(minPoint: result.m_min, maxPoint: result.m_max, center: result.m_center, radius: result.m_radius); |
308 | |
309 | // reset dirty flags |
310 | QEntityPrivate::get(q: result.entity)->m_dirty = false; |
311 | QGeometryViewPrivate::get(q: result.provider->view())->m_dirty = false; |
312 | QGeometryPrivate::get(q: result.provider->view()->geometry())->m_dirty = false; |
313 | QAttributePrivate::get(q: result.positionAttribute)->m_dirty = false; |
314 | QBufferPrivate::get(q: result.positionAttribute->buffer())->m_dirty = false; |
315 | if (result.indexAttribute) { |
316 | QAttributePrivate::get(q: result.indexAttribute)->m_dirty = false; |
317 | QBufferPrivate::get(q: result.indexAttribute->buffer())->m_dirty = false; |
318 | } |
319 | } |
320 | |
321 | m_results.clear(); |
322 | } |
323 | |
324 | void CalculateBoundingVolumeJob::addWatcher(QWeakPointer<BoundingVolumeJobProcessor> watcher) |
325 | { |
326 | m_watchers.push_back(x: watcher); |
327 | } |
328 | |
329 | void CalculateBoundingVolumeJob::removeWatcher(QWeakPointer<BoundingVolumeJobProcessor> watcher) |
330 | { |
331 | if (watcher.isNull()) { |
332 | m_watchers.erase(first: std::remove_if(first: m_watchers.begin(), last: m_watchers.end(), pred: [](const QWeakPointer<BoundingVolumeJobProcessor> &w) { return w.isNull(); }), |
333 | last: m_watchers.end()); |
334 | } else { |
335 | m_watchers.erase(first: std::remove(first: m_watchers.begin(), last: m_watchers.end(), value: watcher), |
336 | last: m_watchers.end()); |
337 | } |
338 | } |
339 | |
340 | } // namespace Qt3DCore |
341 | |
342 | QT_END_NAMESPACE |
343 | |
344 | |