| 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 | |