1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2019 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 "scene3dview_p.h" |
41 | #include <Qt3DCore/QEntity> |
42 | #include <Qt3DRender/QRenderSettings> |
43 | #include <Qt3DRender/QFrameGraphNode> |
44 | #include <Qt3DRender/QLayer> |
45 | #include <Qt3DRender/QLayerFilter> |
46 | #include <Qt3DRender/QViewport> |
47 | #include <scene3dsgnode_p.h> |
48 | #include <scene3ditem_p.h> |
49 | #include <QQuickWindow> |
50 | |
51 | QT_BEGIN_NAMESPACE |
52 | |
53 | namespace Qt3DRender { |
54 | |
55 | /*! |
56 | \qmltype Scene3DView |
57 | \inherits Item |
58 | \inqmlmodule QtQuick.Scene3D |
59 | \since 5.14 |
60 | |
61 | \preliminary |
62 | |
63 | \brief The Scene3DView type is used to integrate a Qt 3D sub scene into a |
64 | QtQuick 2 scene using Scene3D. Whereas you should only use a single Scene3D |
65 | instance per application, you can have multiple Scene3DView instances. |
66 | |
67 | Essentially, if you need to render multiple scenes each in a separate view, |
68 | you should use a single Scene3D instance and as many Scene3DView items as |
69 | you have scenes to render. |
70 | |
71 | Typical usage looks like: |
72 | \qml |
73 | Scene3D { |
74 | id: mainScene3D |
75 | anchors.fill: parent |
76 | } |
77 | |
78 | Scene3DView { |
79 | id: view1 |
80 | scene3D: mainScene3D |
81 | width: 200 |
82 | height: 200 |
83 | Entity { |
84 | ... |
85 | } |
86 | } |
87 | |
88 | Scene3DView { |
89 | id: view2 |
90 | scene3D: mainScene3D |
91 | width: 200 |
92 | height: 200 |
93 | x: 200 |
94 | Entity { |
95 | ... |
96 | } |
97 | } |
98 | \endqml |
99 | |
100 | There are a few limitations when using Scene3DView: |
101 | \list |
102 | \li The Scene3D compositingMode has to be set to FBO |
103 | \li The Scene3D is sized to occupy the full window size (at the very least |
104 | it must be sized as wide as the area occupied by all Scene3DViews) |
105 | \li The Scene3D instance is instantiated prior to any Scene3DView |
106 | \li The Scene3D entity property is left unset |
107 | \endlist |
108 | |
109 | Scene3D behaves likes a texture atlas from which all Scene3DView instances. |
110 | For this reason, care should be taken that only the first Scene3DView |
111 | declared in the scene clears the color/depth. Additionally overlapping |
112 | Scene3DView instances is discouraged as this might not produce the expected |
113 | output. |
114 | |
115 | It is expected that a Scene3DView's Entity provide a RenderSettings with a |
116 | valid SceneGraph. Please note that only the RenderSettings of the first |
117 | Scene3DView instantiated will be taken into account. |
118 | |
119 | There are no restriction on the sharing of elements between different scenes |
120 | in different Scene3DView instances. |
121 | |
122 | By default, you are in charge of ensuring the lifetime of the referenced |
123 | Entity. If you wish to transfer this duty to the Scene3DView, the |
124 | ownsEntity property can be set to true (defaults to false). |
125 | */ |
126 | |
127 | namespace { |
128 | |
129 | Qt3DRender::QFrameGraphNode *frameGraphFromEntity(Qt3DCore::QEntity *entity) |
130 | { |
131 | const auto renderSettingsComponents = entity->componentsOfType<Qt3DRender::QRenderSettings>(); |
132 | |
133 | if (renderSettingsComponents.size() > 0) { |
134 | Qt3DRender::QRenderSettings *renderSettings = renderSettingsComponents.first(); |
135 | return renderSettings->activeFrameGraph(); |
136 | } |
137 | return nullptr; |
138 | } |
139 | |
140 | } |
141 | |
142 | Scene3DView::Scene3DView(QQuickItem *parent) |
143 | : QQuickItem(parent) |
144 | , m_scene3D(nullptr) |
145 | , m_entity(nullptr) |
146 | , m_previousFGParent(nullptr) |
147 | , m_holderEntity(new Qt3DCore::QEntity()) |
148 | , m_holderLayer(new Qt3DRender::QLayer()) |
149 | , m_holderLayerFilter(new Qt3DRender::QLayerFilter()) |
150 | , m_holderViewport(new Qt3DRender::QViewport()) |
151 | , m_dirtyFlags(DirtyNode|DirtyTexture) |
152 | , m_texture(nullptr) |
153 | , m_ownsEntity(false) |
154 | { |
155 | setFlag(flag: QQuickItem::ItemHasContents, enabled: true);\ |
156 | |
157 | m_holderLayer->setRecursive(true); |
158 | m_holderEntity->addComponent(comp: m_holderLayer); |
159 | m_holderLayerFilter->setParent(m_holderViewport); |
160 | m_holderLayerFilter->addLayer(layer: m_holderLayer); |
161 | } |
162 | |
163 | Scene3DView::~Scene3DView() |
164 | { |
165 | if (m_entity) { |
166 | abandonSubtree(subtree: m_entity.data()); |
167 | if (m_ownsEntity) |
168 | m_entity->deleteLater(); |
169 | } |
170 | |
171 | if (m_scene3D) |
172 | m_scene3D->removeView(view: this); |
173 | } |
174 | |
175 | Qt3DCore::QEntity *Scene3DView::entity() const |
176 | { |
177 | return m_entity.data(); |
178 | } |
179 | |
180 | Scene3DItem *Scene3DView::scene3D() const |
181 | { |
182 | return m_scene3D; |
183 | } |
184 | |
185 | Qt3DCore::QEntity *Scene3DView::viewSubtree() const |
186 | { |
187 | return m_holderEntity; |
188 | } |
189 | |
190 | QFrameGraphNode *Scene3DView::viewFrameGraph() const |
191 | { |
192 | return m_holderViewport; |
193 | } |
194 | |
195 | // Called by Scene3DRender::beforeSynchronizing in RenderThread |
196 | void Scene3DView::setTexture(QSGTexture *texture) |
197 | { |
198 | m_dirtyFlags |= DirtyTexture; |
199 | m_texture = texture; |
200 | QQuickItem::update(); |
201 | } |
202 | |
203 | QSGTexture *Scene3DView::texture() const |
204 | { |
205 | return m_texture; |
206 | } |
207 | |
208 | bool Scene3DView::ownsEntity() const |
209 | { |
210 | return m_ownsEntity; |
211 | } |
212 | |
213 | // Called by Scene3DRender::beforeSynchronizing in RenderThread |
214 | void Scene3DView::markSGNodeDirty() |
215 | { |
216 | m_dirtyFlags |= DirtyNode; |
217 | QQuickItem::update(); |
218 | } |
219 | |
220 | // Main Thread |
221 | void Scene3DView::setEntity(Qt3DCore::QEntity *entity) |
222 | { |
223 | if (m_entity.data() == entity) |
224 | return; |
225 | |
226 | if (m_entity) { |
227 | abandonSubtree(subtree: m_entity.data()); |
228 | if (m_ownsEntity) |
229 | m_entity->deleteLater(); |
230 | } |
231 | |
232 | m_entity = entity; |
233 | emit entityChanged(); |
234 | |
235 | if (m_entity) |
236 | adoptSubtree(subtree: m_entity.data()); |
237 | } |
238 | |
239 | // Main Thread |
240 | void Scene3DView::setScene3D(Scene3DItem *scene3D) |
241 | { |
242 | if (m_scene3D == scene3D) |
243 | return; |
244 | |
245 | if (m_scene3D) { |
246 | m_scene3D->removeView(view: this); |
247 | QObject::disconnect(m_scene3DDestroyedConnection); |
248 | } |
249 | |
250 | setTexture(nullptr); |
251 | m_scene3D = scene3D; |
252 | emit scene3DChanged(); |
253 | |
254 | |
255 | if (m_scene3D) { |
256 | m_scene3DDestroyedConnection = QObject::connect(sender: m_scene3D, |
257 | signal: &Scene3DItem::destroyed, |
258 | context: this, |
259 | slot: [this] { |
260 | m_scene3D = nullptr; |
261 | }); |
262 | m_scene3D->addView(view: this); |
263 | } |
264 | } |
265 | |
266 | void Scene3DView::setOwnsEntity(bool ownsEntity) |
267 | { |
268 | if (ownsEntity == m_ownsEntity) |
269 | return; |
270 | m_ownsEntity = ownsEntity; |
271 | emit ownsEntityChanged(); |
272 | } |
273 | |
274 | // Render Thread |
275 | QSGNode *Scene3DView::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *) |
276 | { |
277 | Scene3DSGNode *fboNode = static_cast<Scene3DSGNode *>(node); |
278 | if (fboNode == nullptr) |
279 | fboNode = new Scene3DSGNode(); |
280 | |
281 | // We only need to draw a sub part of the texture based |
282 | // on our size, Scene3D essentially acts as a TextureAtlas |
283 | const QRectF itemRect(mapRectToScene(rect: boundingRect())); |
284 | const QSize winSize = window() ? window()->size() : QSize(); |
285 | const QRectF normalizedViewportRect(itemRect.x() / winSize.width(), |
286 | itemRect.y() / winSize.height(), |
287 | itemRect.width() / winSize.width(), |
288 | itemRect.height() / winSize.height()); |
289 | // Swap Y axis to match GL coordinates |
290 | const QRectF textureRect(itemRect.x() / winSize.width(), |
291 | 1.0f - (itemRect.y() / winSize.height()), |
292 | itemRect.width() / winSize.width(), |
293 | -(itemRect.height() / winSize.height())); |
294 | |
295 | // TO DO: Should be done from main thread |
296 | // updateViewport |
297 | m_holderViewport->setNormalizedRect(normalizedViewportRect); |
298 | |
299 | // update node rect and texture coordinates |
300 | fboNode->setRect(rect: boundingRect(), textureRect); |
301 | |
302 | if (m_dirtyFlags & DirtyTexture) { |
303 | fboNode->setTexture(m_texture); |
304 | m_dirtyFlags.setFlag(flag: DirtyTexture, on: false); |
305 | // Show FBO Node at this point |
306 | fboNode->show(); |
307 | } |
308 | if (m_dirtyFlags & DirtyNode) { |
309 | fboNode->markDirty(bits: QSGNode::DirtyMaterial); |
310 | m_dirtyFlags.setFlag(flag: DirtyNode, on: false); |
311 | } |
312 | |
313 | return fboNode; |
314 | } |
315 | |
316 | // Main Thread |
317 | void Scene3DView::adoptSubtree(Qt3DCore::QEntity *subtree) |
318 | { |
319 | // Reparent FrameGraph |
320 | Qt3DRender::QFrameGraphNode *fgNode = frameGraphFromEntity(entity: subtree); |
321 | if (fgNode) { |
322 | m_previousFGParent = fgNode->parentNode(); |
323 | fgNode->setParent(m_holderLayerFilter); |
324 | } |
325 | |
326 | // Insert Entity Subtree |
327 | subtree->setParent(m_holderEntity); |
328 | } |
329 | |
330 | // Main Thread |
331 | void Scene3DView::abandonSubtree(Qt3DCore::QEntity *subtree) |
332 | { |
333 | // Remove FrameGraph part |
334 | Qt3DRender::QFrameGraphNode *fgNode = frameGraphFromEntity(entity: subtree); |
335 | if (fgNode) |
336 | fgNode->setParent(m_previousFGParent); |
337 | |
338 | // Remove Entity Subtree |
339 | subtree->setParent(Q_NODE_NULLPTR); |
340 | } |
341 | |
342 | } // Qt3DRender |
343 | |
344 | QT_END_NAMESPACE |
345 | |