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