| /**************************************************************************** |
| ** |
| ** Copyright (C) 2015 Klaralvdalens Datakonsult AB (KDAB). |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the Qt3D module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "scene3ditem_p.h" |
| |
| #include <Qt3DCore/qt3dcore_global.h> |
| #include <Qt3DCore/qentity.h> |
| #include <Qt3DCore/QAspectEngine> |
| |
| #if QT_CONFIG(qt3d_input) |
| #include <Qt3DInput/QInputAspect> |
| #include <Qt3DInput/qinputsettings.h> |
| #endif |
| |
| #if QT_CONFIG(qt3d_logic) |
| #include <Qt3DLogic/qlogicaspect.h> |
| #endif |
| |
| #if QT_CONFIG(qt3d_animation) |
| #include <Qt3DAnimation/qanimationaspect.h> |
| #endif |
| |
| #include <Qt3DRender/QRenderAspect> |
| #include <Qt3DRender/qcamera.h> |
| #include <Qt3DRender/qrendersurfaceselector.h> |
| |
| #include <QtGui/qguiapplication.h> |
| #include <QtGui/qoffscreensurface.h> |
| #include <QtQuick/qquickwindow.h> |
| #include <QtQuick/qquickrendercontrol.h> |
| |
| #include <Qt3DRender/private/qrendersurfaceselector_p.h> |
| #include <Qt3DRender/private/qrenderaspect_p.h> |
| #include <Qt3DRender/private/rendersettings_p.h> |
| #include <scene3dlogging_p.h> |
| #include <scene3drenderer_p.h> |
| #include <scene3dsgnode_p.h> |
| #include <scene3dview_p.h> |
| |
| #include <Qt3DCore/private/qaspectengine_p.h> |
| #include <Qt3DCore/private/qaspectmanager_p.h> |
| #include <QThread> |
| |
| QT_BEGIN_NAMESPACE |
| |
| namespace Qt3DRender { |
| |
| /*! |
| \class Qt3DRender::Scene3DItem |
| \internal |
| |
| \brief The Scene3DItem class is a QQuickItem subclass used to integrate |
| a Qt3D scene into a QtQuick 2 scene. |
| |
| The Scene3DItem class renders a Qt3D scene, provided by a Qt3DCore::QEntity |
| into a multisampled Framebuffer object that is later blitted into a |
| non-multisampled Framebuffer object to be then rendered through the use of a |
| Qt3DCore::Scene3DSGNode with premultiplied alpha. |
| */ |
| |
| /*! |
| \qmltype Scene3D |
| \inherits Item |
| \inqmlmodule QtQuick.Scene3D |
| |
| \preliminary |
| |
| \brief The Scene3D type is used to integrate a Qt3D scene into a QtQuick 2 |
| scene. |
| |
| The Scene3D type renders a Qt3D scene, provided by an \l Entity, into a |
| multisampled Framebuffer object. This object is later blitted into a |
| non-multisampled Framebuffer object, which is then rendered with |
| premultiplied alpha. If multisampling is not required, it can be avoided |
| by setting the \l multisample property to \c false. In this case |
| Scene3D will render directly into the non-multisampled Framebuffer object. |
| |
| If the scene to be rendered includes non-opaque materials, you may need to |
| modify those materials with custom blend arguments in order for them to be |
| rendered correctly. For example, if working with a \l PhongAlphaMaterial and |
| a scene with an opaque clear color, you will likely want to add: |
| |
| \qml |
| sourceAlphaArg: BlendEquationArguments.Zero |
| destinationAlphaArg: BlendEquationArguments.One |
| \endqml |
| |
| to that material. |
| |
| It is not recommended to instantiate more than a single Scene3D instance |
| per application. The reason for this is that a Scene3D instance |
| instantiates the entire Qt 3D engine (memory managers, thread pool, render |
| ...) under the scene. You should instead look into using \l Scene3DView |
| instances in conjunction with a single Scene3D instance. |
| |
| When using Scene3D with Scene3DViews the following conditions are expected: |
| \list |
| \li The compositingMode is set to FBO |
| \li The Scene3D is sized to occupy the full window size |
| \li The Scene3D instance is instantiated prior to any Scene3DView |
| \li The Scene3D entity property is left unset |
| \endlist |
| |
| \note Åšetting the visibility of the Scene3D element to false will halt the |
| Qt 3D simulation loop. This means that binding the visible property to an |
| expression that depends on property updates driven by the Qt 3D simulation |
| loop (FrameAction) will never reavaluates. |
| */ |
| Scene3DItem::Scene3DItem(QQuickItem *parent) |
| : QQuickItem(parent) |
| , m_entity(nullptr) |
| , m_viewHolderEntity(nullptr) |
| , m_viewHolderFG(nullptr) |
| , m_aspectEngine(new Qt3DCore::QAspectEngine()) |
| , m_aspectToDelete(nullptr) |
| , m_renderAspect(nullptr) |
| , m_renderer(nullptr) |
| , m_multisample(true) |
| , m_dirty(true) |
| , m_dirtyViews(false) |
| , m_clearsWindowByDefault(true) |
| , m_disableClearWindow(false) |
| , m_cameraAspectRatioMode(AutomaticAspectRatio) |
| , m_compositingMode(FBO) |
| , m_dummySurface(nullptr) |
| { |
| setFlag(QQuickItem::ItemHasContents, true); |
| setAcceptedMouseButtons(Qt::MouseButtonMask); |
| setAcceptHoverEvents(true); |
| // TO DO: register the event source in the main thread |
| |
| // Use manual drive mode when using Scene3D |
| m_aspectEngine->setRunMode(Qt3DCore::QAspectEngine::Manual); |
| |
| // Give a default size so that if nothing is specified by the user |
| // we still won't get ignored by the QtQuick SG when in Underlay mode |
| setWidth(1); |
| setHeight(1); |
| } |
| |
| Scene3DItem::~Scene3DItem() |
| { |
| // When the window is closed, it first destroys all of its children. At |
| // this point, Scene3DItem is destroyed but the Renderer, AspectEngine and |
| // Scene3DSGNode still exist and will perform their cleanup on their own. |
| m_aspectEngine->deleteLater(); |
| m_renderer->deleteLater(); |
| if (m_dummySurface) |
| m_dummySurface->deleteLater(); |
| } |
| |
| /*! |
| \qmlproperty list<string> Scene3D::aspects |
| |
| The list of aspects that should be registered for the 3D scene. |
| |
| For example, if the scene makes use of FrameAction, the \c "logic" aspect should be included in the list. |
| |
| The \c "render" aspect is hardwired and does not need to be explicitly listed. |
| */ |
| QStringList Scene3DItem::aspects() const |
| { |
| return m_aspects; |
| } |
| |
| /*! |
| \qmlproperty Entity Scene3D::entity |
| |
| \default |
| |
| The root entity of the 3D scene to be displayed. |
| */ |
| Qt3DCore::QEntity *Scene3DItem::entity() const |
| { |
| return m_entity.data(); |
| } |
| |
| void Scene3DItem::applyAspects() |
| { |
| // Aspects are owned by the aspect engine |
| for (const QString &aspect : qAsConst(m_aspects)) { |
| if (aspect == QLatin1String("render")) // This one is hardwired anyway |
| continue; |
| if (aspect == QLatin1String("input")) { |
| #if QT_CONFIG(qt3d_input) |
| m_aspectEngine->registerAspect(new Qt3DInput::QInputAspect); |
| continue; |
| #else |
| qFatal("Scene3D requested the Qt 3D input aspect but Qt 3D wasn't configured to build the Qt 3D Input aspect"); |
| #endif |
| } |
| if (aspect == QLatin1String("logic")) { |
| #if QT_CONFIG(qt3d_logic) |
| m_aspectEngine->registerAspect(new Qt3DLogic::QLogicAspect); |
| continue; |
| #else |
| qFatal("Scene3D requested the Qt 3D logic aspect but Qt 3D wasn't configured to build the Qt 3D Logic aspect"); |
| #endif |
| } |
| if (aspect == QLatin1String("animation")) { |
| #if QT_CONFIG(qt3d_animation) |
| m_aspectEngine->registerAspect(new Qt3DAnimation::QAnimationAspect); |
| continue; |
| #else |
| qFatal("Scene3D requested the Qt 3D animation aspect but Qt 3D wasn't configured to build the Qt 3D Animation aspect"); |
| #endif |
| } |
| m_aspectEngine->registerAspect(aspect); |
| } |
| } |
| |
| void Scene3DItem::setAspects(const QStringList &aspects) |
| { |
| if (!m_aspects.isEmpty()) { |
| qWarning() << "Aspects already set on the Scene3D, ignoring"; |
| return; |
| } |
| |
| m_aspects = aspects; |
| applyAspects(); |
| |
| emit aspectsChanged(); |
| } |
| |
| void Scene3DItem::setEntity(Qt3DCore::QEntity *entity) |
| { |
| if (entity == m_entity.data()) |
| return; |
| |
| m_entity.reset(entity); |
| emit entityChanged(); |
| } |
| |
| void Scene3DItem::setCameraAspectRatioMode(CameraAspectRatioMode mode) |
| { |
| if (m_cameraAspectRatioMode == mode) |
| return; |
| |
| m_cameraAspectRatioMode = mode; |
| setCameraAspectModeHelper(); |
| emit cameraAspectRatioModeChanged(mode); |
| } |
| |
| void Scene3DItem::setHoverEnabled(bool enabled) |
| { |
| if (enabled != acceptHoverEvents()) { |
| setAcceptHoverEvents(enabled); |
| emit hoverEnabledChanged(); |
| } |
| } |
| |
| /*! |
| \qmlproperty enumeration Scene3D::compositingMode |
| |
| \value FBO |
| Scene is rendered into a Frame Buffer Object which can be costly on |
| some platform and hardware but allows a greater amount of |
| flexibility. Automatic aspect ratio. This is the compositing mode to |
| choose if your Scene3D element shouldn't occupy the entire screen |
| and if you optionally plan on having it resized or animated. In this |
| mode, the position of the Scene3D in the QML file controls its |
| stacking order with regard to the other Qt Quick elements. |
| |
| \value Underlay |
| Suitable for full screen 3D scenes where using an FBO might be too |
| resource intensive. Scene3D behaves as a QtQuick underlay. |
| Please note that when using this mode, the size of the Scene3D and |
| its transformations are ignored and the rendering will occupy the |
| whole screen. The position of the Scene3D in the QML file won't have |
| any effect either. The Qt 3D content will be drawn prior to any Qt |
| Quick content. Care has to be taken not to overdraw and hide the Qt |
| 3D content by overlapping Qt Quick content. |
| Additionally when using this mode, the window clearBeforeRendering |
| will be set to false automatically. |
| |
| The default value is \c FBO. |
| \since 5.14 |
| */ |
| void Scene3DItem::setCompositingMode(Scene3DItem::CompositingMode mode) |
| { |
| if (m_compositingMode == mode) |
| return; |
| m_compositingMode = mode; |
| emit compositingModeChanged(); |
| |
| QQuickItem::update(); |
| } |
| |
| /*! |
| \qmlproperty enumeration Scene3D::cameraAspectRatioMode |
| |
| \value Scene3D.AutomaticAspectRatio |
| Automatic aspect ratio. |
| |
| \value Scene3D.UserAspectRatio |
| User defined aspect ratio. |
| \brief \TODO |
| */ |
| Scene3DItem::CameraAspectRatioMode Scene3DItem::cameraAspectRatioMode() const |
| { |
| return m_cameraAspectRatioMode; |
| } |
| |
| Scene3DItem::CompositingMode Scene3DItem::compositingMode() const |
| { |
| return m_compositingMode; |
| } |
| |
| // MainThread called by Scene3DView |
| void Scene3DItem::addView(Scene3DView *view) |
| { |
| if (m_views.contains(view)) |
| return; |
| |
| Qt3DRender::QFrameGraphNode *viewFG = view->viewFrameGraph(); |
| Qt3DCore::QEntity *subtreeRoot = view->viewSubtree(); |
| |
| if (m_viewHolderEntity == nullptr) { |
| m_viewHolderEntity = new Qt3DCore::QEntity; |
| |
| if (m_entity != nullptr) { |
| qCWarning(Scene3D) << "Scene3DView is not supported if the Scene3D entity property has been set"; |
| } |
| |
| Qt3DRender::QRenderSettings *settings = new Qt3DRender::QRenderSettings(); |
| Qt3DRender::QRenderSurfaceSelector *surfaceSelector = new Qt3DRender::QRenderSurfaceSelector(); |
| m_viewHolderFG = surfaceSelector; |
| surfaceSelector->setSurface(window()); |
| |
| // Copy setting properties from first View |
| QVector<Qt3DRender::QRenderSettings *> viewRenderSettings = subtreeRoot->componentsOfType<Qt3DRender::QRenderSettings>(); |
| if (viewRenderSettings.size() > 0) { |
| Qt3DRender::QRenderSettings *viewRenderSetting = viewRenderSettings.first(); |
| settings->setRenderPolicy(viewRenderSetting->renderPolicy()); |
| settings->pickingSettings()->setPickMethod(viewRenderSetting->pickingSettings()->pickMethod()); |
| settings->pickingSettings()->setPickResultMode(viewRenderSetting->pickingSettings()->pickResultMode()); |
| } |
| settings->setActiveFrameGraph(m_viewHolderFG); |
| m_viewHolderEntity->addComponent(settings); |
| |
| setEntity(m_viewHolderEntity); |
| } |
| |
| // Parent FG and Subtree |
| viewFG->setParent(m_viewHolderFG); |
| subtreeRoot->setParent(m_viewHolderEntity); |
| |
| m_views.push_back(view); |
| m_dirtyViews |= true; |
| } |
| |
| // MainThread called by Scene3DView |
| void Scene3DItem::removeView(Scene3DView *view) |
| { |
| if (!m_views.contains(view)) |
| return; |
| |
| Qt3DRender::QFrameGraphNode *viewFG = view->viewFrameGraph(); |
| Qt3DCore::QEntity *subtreeRoot = view->viewSubtree(); |
| |
| // Unparent FG and Subtree |
| viewFG->setParent(Q_NODE_NULLPTR); |
| subtreeRoot->setParent(Q_NODE_NULLPTR); |
| |
| m_views.removeOne(view); |
| m_dirtyViews |= true; |
| } |
| |
| void Scene3DItem::applyRootEntityChange() |
| { |
| if (m_aspectEngine->rootEntity() != m_entity.data()) { |
| m_aspectEngine->setRootEntity(m_entity); |
| |
| /* If we changed window, the old aspect engine must be deleted only after we have set |
| the root entity for the new one so that it doesn't delete the root node. */ |
| if (m_aspectToDelete) { |
| delete m_aspectToDelete; |
| m_aspectToDelete = nullptr; |
| } |
| |
| // Set the render surface |
| if (!m_entity) |
| return; |
| |
| setWindowSurface(entity()); |
| |
| if (m_cameraAspectRatioMode == AutomaticAspectRatio) { |
| // Set aspect ratio of first camera to match the window |
| QList<Qt3DRender::QCamera *> cameras |
| = m_entity->findChildren<Qt3DRender::QCamera *>(); |
| if (cameras.isEmpty()) { |
| qCDebug(Scene3D) << "No camera found and automatic aspect ratio requested"; |
| } else { |
| m_camera = cameras.first(); |
| setCameraAspectModeHelper(); |
| } |
| } |
| |
| #if QT_CONFIG(qt3d_input) |
| // Set ourselves up as a source of input events for the input aspect |
| Qt3DInput::QInputSettings *inputSettings = m_entity->findChild<Qt3DInput::QInputSettings *>(); |
| if (inputSettings) { |
| inputSettings->setEventSource(this); |
| } else { |
| qCDebug(Scene3D) << "No Input Settings found, keyboard and mouse events won't be handled"; |
| } |
| #endif |
| } |
| } |
| |
| bool Scene3DItem::needsRender() |
| { |
| // We need the dirty flag which is connected to the change arbiter |
| // receiving updates to know whether something in the scene has changed |
| |
| // Ideally we would use shouldRender() alone but given that it becomes true |
| // only after the arbiter has sync the changes and might be reset before |
| // process jobs is completed, we cannot fully rely on it. It would require |
| // splitting processFrame in 2 parts. |
| |
| // We only use it for cases where Qt3D render may require several loops of |
| // the simulation to fully process a frame (e.g shaders are loaded in frame |
| // n and we can only build render commands for the new shader at frame n + |
| // This is where renderer->shouldRender() comes into play as it knows |
| // whether some states remain dirty or not (even after processFrame is |
| // called) |
| |
| auto renderAspectPriv = static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(m_renderAspect)); |
| const bool dirty = m_dirty |
| || (renderAspectPriv |
| && renderAspectPriv->m_renderer |
| && renderAspectPriv->m_renderer->shouldRender()); |
| m_dirty = false; |
| return dirty; |
| } |
| |
| // This function is triggered in the context of the Main Thread |
| // when afterAnimating is emitted |
| |
| // The QtQuick SG proceeds like indicated below: |
| // afterAnimating (Main Thread) |
| // beforeSynchronizing (SG Thread and MainThread locked) |
| // afterSynchronizing (SG Thread and MainThread locked) |
| // beforeRendering (SG Thread) |
| |
| // Note: we connect to afterAnimating rather than beforeSynchronizing as a |
| // direct connection on beforeSynchronizing is executed within the SG Render |
| // Thread context. This is executed before the RenderThread is asked to |
| // synchronize and render |
| // Note: we might still not be done rendering when this is called but |
| // processFrame will block and wait for renderer to have been finished |
| void Scene3DItem::onBeforeSync() |
| { |
| static bool dontRenderWhenHidden = !qgetenv("QT3D_SCENE3D_STOP_RENDER_HIDDEN").isEmpty(); |
| |
| // If we are not visible, don't processFrame changes as we would end up |
| // waiting forever for the scene to be rendered which won't happen |
| // if the Scene3D item is not visible |
| if (!isVisible() && dontRenderWhenHidden) |
| return; |
| if (m_renderer->m_resetRequested) |
| return; |
| |
| Q_ASSERT(QThread::currentThread() == thread()); |
| |
| // Since we are in manual mode, trigger jobs for the next frame |
| Qt3DCore::QAspectEnginePrivate *aspectEnginePriv = static_cast<Qt3DCore::QAspectEnginePrivate *>(QObjectPrivate::get(m_aspectEngine)); |
| if (!aspectEnginePriv->m_initialized || !m_renderer) |
| return; |
| |
| // Set compositing mode on renderer |
| m_renderer->setCompositingMode(m_compositingMode); |
| const bool usesFBO = m_compositingMode == FBO; |
| |
| // Make renderer aware of any Scene3DView we are dealing with |
| if (m_dirtyViews) { |
| // Scene3DViews checks |
| if (entity() != m_viewHolderEntity) { |
| qCWarning(Scene3D) << "Scene3DView is not supported if the Scene3D entity property has been set"; |
| } |
| if (!usesFBO) { |
| qCWarning(Scene3D) << "Scene3DView is only supported when Scene3D compositingMode is set to FBO"; |
| } |
| // The Scene3DRender will take care of providing the texture containing the 3D scene |
| m_renderer->setScene3DViews(m_views); |
| m_dirtyViews = false; |
| } |
| |
| Q_ASSERT(m_aspectEngine->runMode() == Qt3DCore::QAspectEngine::Manual); |
| m_aspectEngine->processFrame(); |
| // The above essentially sets the number of RV for the RenderQueue and |
| // processes the jobs for the frame (it's blocking) When |
| // Scene3DRender::updatePaintNode is called, following this step, we know |
| // that the RenderQueue target count has been set and that everything |
| // should be ready for rendering |
| |
| |
| // processFrame() must absolutely be followed by a single call to |
| // render |
| // At startup, we have no garantee that the QtQuick Render Thread doesn't |
| // start rendering before this function has been called |
| // We add in a safety to skip such frames as this could otherwise |
| // make Qt3D enter a locked state |
| m_renderer->setSkipFrame(!needsRender()); |
| m_renderer->allowRender(); |
| |
| // Note: it's too early to request an update at this point as |
| // beforeSync() triggered by afterAnimating is considered |
| // to be as being part of the current frame update |
| } |
| |
| void Scene3DItem::requestUpdate() |
| { |
| // When using the FBO mode, only the QQuickItem needs to be updated |
| // When using the Underlay mode, the whole windows needs updating |
| const bool usesFBO = m_compositingMode == FBO; |
| if (usesFBO) { |
| QQuickItem::update(); |
| for (Scene3DView *view : m_views) |
| view->update(); |
| } else { |
| window()->update(); |
| } |
| } |
| |
| void Scene3DItem::updateWindowSurface() |
| { |
| if (!m_entity || !m_dummySurface) |
| return; |
| Qt3DRender::QRenderSurfaceSelector *surfaceSelector = |
| Qt3DRender::QRenderSurfaceSelectorPrivate::find(entity()); |
| if (surfaceSelector) { |
| if (QWindow *rw = QQuickRenderControl::renderWindowFor(this->window())) { |
| m_dummySurface->deleteLater(); |
| createDummySurface(rw, surfaceSelector); |
| } |
| } |
| } |
| |
| void Scene3DItem::setWindowSurface(QObject *rootObject) |
| { |
| Qt3DRender::QRenderSurfaceSelector *surfaceSelector = Qt3DRender::QRenderSurfaceSelectorPrivate::find(rootObject); |
| |
| // Set the item's window surface if it appears |
| // the surface wasn't set on the surfaceSelector |
| if (surfaceSelector && !surfaceSelector->surface()) { |
| // We may not have a real, exposed QQuickWindow when the Quick rendering |
| // is redirected via QQuickRenderControl (f.ex. QQuickWidget). |
| if (QWindow *rw = QQuickRenderControl::renderWindowFor(this->window())) { |
| createDummySurface(rw, surfaceSelector); |
| } else { |
| surfaceSelector->setSurface(this->window()); |
| } |
| } |
| } |
| |
| void Scene3DItem::createDummySurface(QWindow *rw, Qt3DRender::QRenderSurfaceSelector *surfaceSelector) |
| { |
| // rw is the top-level window that is backed by a native window. Do |
| // not use that though since we must not clash with e.g. the widget |
| // backingstore compositor in the gui thread. |
| m_dummySurface = new QOffscreenSurface; |
| m_dummySurface->setParent(qGuiApp); // parent to something suitably long-living |
| m_dummySurface->setFormat(rw->format()); |
| m_dummySurface->setScreen(rw->screen()); |
| m_dummySurface->create(); |
| surfaceSelector->setSurface(m_dummySurface); |
| } |
| /*! |
| \qmlmethod void Scene3D::setItemAreaAndDevicePixelRatio(size area, real devicePixelRatio) |
| |
| Sets the item area to \a area and the pixel ratio to \a devicePixelRatio. |
| */ |
| void Scene3DItem::setItemAreaAndDevicePixelRatio(QSize area, qreal devicePixelRatio) |
| { |
| Qt3DRender::QRenderSurfaceSelector *surfaceSelector |
| = Qt3DRender::QRenderSurfaceSelectorPrivate::find(entity()); |
| if (surfaceSelector) { |
| surfaceSelector->setExternalRenderTargetSize(area); |
| surfaceSelector->setSurfacePixelRatio(devicePixelRatio); |
| } |
| } |
| |
| /*! |
| \qmlproperty bool Scene3D::hoverEnabled |
| |
| \c true if hover events are accepted. |
| */ |
| bool Scene3DItem::isHoverEnabled() const |
| { |
| return acceptHoverEvents(); |
| } |
| |
| void Scene3DItem::setCameraAspectModeHelper() |
| { |
| if (m_compositingMode == FBO) { |
| switch (m_cameraAspectRatioMode) { |
| case AutomaticAspectRatio: |
| connect(this, &Scene3DItem::widthChanged, this, &Scene3DItem::updateCameraAspectRatio); |
| connect(this, &Scene3DItem::heightChanged, this, &Scene3DItem::updateCameraAspectRatio); |
| // Update the aspect ratio the first time the surface is set |
| updateCameraAspectRatio(); |
| break; |
| case UserAspectRatio: |
| disconnect(this, &Scene3DItem::widthChanged, this, &Scene3DItem::updateCameraAspectRatio); |
| disconnect(this, &Scene3DItem::heightChanged, this, &Scene3DItem::updateCameraAspectRatio); |
| break; |
| } |
| } else { |
| // In Underlay mode, we rely on the window for aspect ratio and not the size of the Scene3DItem |
| switch (m_cameraAspectRatioMode) { |
| case AutomaticAspectRatio: |
| connect(window(), &QWindow::widthChanged, this, &Scene3DItem::updateCameraAspectRatio); |
| connect(window(), &QWindow::heightChanged, this, &Scene3DItem::updateCameraAspectRatio); |
| // Update the aspect ratio the first time the surface is set |
| updateCameraAspectRatio(); |
| break; |
| case UserAspectRatio: |
| disconnect(window(), &QWindow::widthChanged, this, &Scene3DItem::updateCameraAspectRatio); |
| disconnect(window(), &QWindow::heightChanged, this, &Scene3DItem::updateCameraAspectRatio); |
| break; |
| } |
| } |
| } |
| |
| void Scene3DItem::updateCameraAspectRatio() |
| { |
| if (m_camera) { |
| if (m_compositingMode == FBO) |
| m_camera->setAspectRatio(static_cast<float>(width()) / |
| static_cast<float>(height())); |
| else |
| m_camera->setAspectRatio(static_cast<float>(window()->width()) / |
| static_cast<float>(window()->height())); |
| } |
| } |
| |
| /*! |
| \qmlproperty bool Scene3D::multisample |
| |
| \c true if a multisample render buffer is requested. |
| |
| By default multisampling is enabled. If the OpenGL implementation has no |
| support for multisample renderbuffers or framebuffer blits, the request to |
| use multisampling is ignored. |
| |
| \note Refrain from changing the value frequently as it involves expensive |
| and potentially slow initialization of framebuffers and other OpenGL |
| resources. |
| */ |
| bool Scene3DItem::multisample() const |
| { |
| return m_multisample; |
| } |
| |
| void Scene3DItem::setMultisample(bool enable) |
| { |
| if (m_multisample != enable) { |
| m_multisample = enable; |
| emit multisampleChanged(); |
| update(); |
| } |
| } |
| |
| QSGNode *Scene3DItem::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *) |
| { |
| // m_resetRequested is set to true by Scene3DRenderer::shutdown() |
| if (m_renderer && m_renderer->m_resetRequested) { |
| qCWarning(Scene3D) << "Renderer for Scene3DItem has requested a reset due to the item " |
| "moving to another window"; |
| QObject::disconnect(m_windowConnection); |
| m_aspectEngine->unregisterAspect(m_renderAspect); // Deletes the renderAspect |
| m_renderAspect = nullptr; |
| m_aspectToDelete = m_aspectEngine; |
| m_aspectEngine = new Qt3DCore::QAspectEngine(); |
| m_aspectEngine->setRunMode(Qt3DCore::QAspectEngine::Manual); |
| applyAspects(); |
| // Needs to belong in the same thread as the item which is the same as the original |
| // QAspectEngine |
| m_aspectEngine->moveToThread(thread()); |
| m_renderer->m_resetRequested = false; |
| } |
| // If the render aspect wasn't created yet, do so now |
| if (m_renderAspect == nullptr) { |
| m_renderAspect = new QRenderAspect(QRenderAspect::Synchronous); |
| auto *rw = QQuickRenderControl::renderWindowFor(window()); |
| static_cast<Qt3DRender::QRenderAspectPrivate *>(Qt3DRender::QRenderAspectPrivate::get(m_renderAspect))->m_screen = |
| (rw ? rw->screen() : window()->screen()); |
| m_aspectEngine->registerAspect(m_renderAspect); |
| |
| // Before Synchronizing is in the SG Thread, we want beforeSync to be triggered |
| // in the context of the main thread |
| m_windowConnection = QObject::connect(window(), &QQuickWindow::afterAnimating, |
| this, &Scene3DItem::onBeforeSync, Qt::DirectConnection); |
| auto renderAspectPriv = static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(m_renderAspect)); |
| QObject::connect(renderAspectPriv->m_aspectManager->changeArbiter(), &Qt3DCore::QChangeArbiter::receivedChange, |
| this, [this] { m_dirty = true; }, Qt::DirectConnection); |
| } |
| |
| if (m_renderer == nullptr) { |
| m_renderer = new Scene3DRenderer(); |
| m_renderer->init(this, m_aspectEngine, m_renderAspect); |
| } else if (m_renderer->renderAspect() != m_renderAspect) { |
| // If the renderer's renderAspect is not equal to the aspect used |
| // by the item, then it means that we have created a new one due to |
| // the fact that shutdown() was called on the renderer previously. |
| // This is a typical situation when the window the item is in has |
| // moved from one screen to another. |
| updateWindowSurface(); |
| m_renderer->init(this, m_aspectEngine, m_renderAspect); |
| } |
| const bool usesFBO = m_compositingMode == FBO; |
| const bool hasScene3DViews = !m_views.empty(); |
| Scene3DSGNode *fboNode = static_cast<Scene3DSGNode *>(node); |
| |
| // When usin Scene3DViews or Scene3D in Underlay mode |
| // we shouldn't be managing a Scene3DSGNode |
| if (!usesFBO || hasScene3DViews) { |
| if (fboNode != nullptr) { |
| delete fboNode; |
| fboNode = nullptr; |
| m_renderer->setSGNode(fboNode); |
| } |
| } else { |
| // Regular Scene3D only case |
| // Create SGNode if using FBO and no Scene3DViews |
| if (fboNode == nullptr) { |
| fboNode = new Scene3DSGNode(); |
| m_renderer->setSGNode(fboNode); |
| } |
| fboNode->setRect(boundingRect()); |
| } |
| |
| if (usesFBO) { |
| // Reset clear flag if we've set it to false it's still set to that |
| if (m_disableClearWindow && !window()->clearBeforeRendering()) |
| window()->setClearBeforeRendering(m_clearsWindowByDefault); |
| m_disableClearWindow = false; |
| } else { |
| // Record clearBeforeRendering value before we force it to false |
| m_clearsWindowByDefault = window()->clearBeforeRendering(); |
| m_disableClearWindow = true; |
| if (m_clearsWindowByDefault) |
| window()->setClearBeforeRendering(false); |
| } |
| |
| // Request update for next frame so that we can check whether we need to |
| // render again or not |
| static int requestUpdateMethodIdx = Scene3DItem::staticMetaObject.indexOfMethod("requestUpdate()"); |
| static QMetaMethod requestUpdateMethod =Scene3DItem::staticMetaObject.method(requestUpdateMethodIdx); |
| requestUpdateMethod.invoke(this, Qt::QueuedConnection); |
| |
| return fboNode; |
| } |
| |
| void Scene3DItem::mousePressEvent(QMouseEvent *event) |
| { |
| Q_UNUSED(event); |
| //Prevent subsequent move and release events being disregarded my the default event->ignore() from QQuickItem |
| } |
| |
| } // namespace Qt3DRender |
| |
| QT_END_NAMESPACE |