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