1/****************************************************************************
2**
3** Copyright (C) 2015 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 "scene3ditem_p.h"
41
42#include <Qt3DCore/qt3dcore_global.h>
43#include <Qt3DCore/qentity.h>
44#include <Qt3DCore/QAspectEngine>
45
46#if QT_CONFIG(qt3d_input)
47#include <Qt3DInput/QInputAspect>
48#include <Qt3DInput/qinputsettings.h>
49#endif
50
51#if QT_CONFIG(qt3d_logic)
52#include <Qt3DLogic/qlogicaspect.h>
53#endif
54
55#if QT_CONFIG(qt3d_animation)
56#include <Qt3DAnimation/qanimationaspect.h>
57#endif
58
59#include <Qt3DRender/QRenderAspect>
60#include <Qt3DRender/qcamera.h>
61#include <Qt3DRender/qrendersurfaceselector.h>
62
63#include <QtGui/qguiapplication.h>
64#include <QtGui/qoffscreensurface.h>
65#include <QtQuick/qquickwindow.h>
66#include <QtQuick/qquickrendercontrol.h>
67
68#include <Qt3DRender/private/qrendersurfaceselector_p.h>
69#include <scene3dcleaner_p.h>
70#include <scene3dlogging_p.h>
71#include <scene3drenderer_p.h>
72#include <scene3dsgnode_p.h>
73
74QT_BEGIN_NAMESPACE
75
76namespace Qt3DRender {
77
78/*!
79 \class Qt3DRender::Scene3DItem
80 \internal
81
82 \brief The Scene3DItem class is a QQuickItem subclass used to integrate
83 a Qt3D scene into a QtQuick 2 scene.
84
85 The Scene3DItem class renders a Qt3D scene, provided by a Qt3DCore::QEntity
86 into a multisampled Framebuffer object that is later blitted into a
87 non-multisampled Framebuffer object to be then rendered through the use of a
88 Qt3DCore::Scene3DSGNode with premultiplied alpha.
89 */
90
91/*!
92 \qmltype Scene3D
93 \inherits Item
94 \inqmlmodule QtQuick.Scene3D
95
96 \preliminary
97
98 \brief The Scene3D type is used to integrate a Qt3D scene into a QtQuick 2
99 scene.
100
101 The Scene3D type renders a Qt3D scene, provided by an \l Entity, into a
102 multisampled Framebuffer object. This object is later blitted into a
103 non-multisampled Framebuffer object, which is then rendered with
104 premultiplied alpha. If multisampling is not required, it can be avoided
105 by setting the \l multisample property to \c false. In this case
106 Scene3D will render directly into the non-multisampled Framebuffer object.
107
108 If the scene to be rendered includes non-opaque materials, you may need to
109 modify those materials with custom blend arguments in order for them to be
110 rendered correctly. For example, if working with a \l PhongAlphaMaterial and
111 a scene with an opaque clear color, you will likely want to add:
112
113 \qml
114 sourceAlphaArg: BlendEquationArguments.Zero
115 destinationAlphaArg: BlendEquationArguments.One
116 \endqml
117
118 to that material.
119 */
120Scene3DItem::Scene3DItem(QQuickItem *parent)
121 : QQuickItem(parent)
122 , m_entity(nullptr)
123 , m_aspectEngine(new Qt3DCore::QAspectEngine())
124 , m_renderAspect(nullptr)
125 , m_renderer(nullptr)
126 , m_rendererCleaner(new Scene3DCleaner())
127 , m_multisample(true)
128 , m_cameraAspectRatioMode(AutomaticAspectRatio)
129{
130 setFlag(QQuickItem::ItemHasContents, true);
131 setAcceptedMouseButtons(Qt::MouseButtonMask);
132 // TO DO: register the event source in the main thread
133}
134
135Scene3DItem::~Scene3DItem()
136{
137 // When the window is closed, it first destroys all of its children. At
138 // this point, Scene3DItem is destroyed but the Renderer, AspectEngine and
139 // Scene3DSGNode still exist and will perform their cleanup on their own.
140}
141
142/*!
143 \qmlproperty list<string> Scene3D::aspects
144
145 The list of aspects that should be registered for the 3D scene.
146
147 For example, if the scene makes use of FrameAction, the \c "logic" aspect should be included in the list.
148
149 The \c "render" aspect is hardwired and does not need to be explicitly listed.
150*/
151QStringList Scene3DItem::aspects() const
152{
153 return m_aspects;
154}
155
156/*!
157 \qmlproperty Entity Scene3D::entity
158
159 \default
160
161 The root entity of the 3D scene to be displayed.
162 */
163Qt3DCore::QEntity *Scene3DItem::entity() const
164{
165 return m_entity;
166}
167
168void Scene3DItem::setAspects(const QStringList &aspects)
169{
170 if (!m_aspects.isEmpty()) {
171 qWarning() << "Aspects already set on the Scene3D, ignoring";
172 return;
173 }
174
175 m_aspects = aspects;
176
177 // Aspects are owned by the aspect engine
178 for (const QString &aspect : qAsConst(m_aspects)) {
179 if (aspect == QLatin1String("render")) // This one is hardwired anyway
180 continue;
181 if (aspect == QLatin1String("input")) {
182#if QT_CONFIG(qt3d_input)
183 m_aspectEngine->registerAspect(new Qt3DInput::QInputAspect);
184 continue;
185#else
186 qFatal("Scene3D requested the Qt 3D input aspect but Qt 3D wasn't configured to build the Qt 3D Input aspect");
187#endif
188 }
189 if (aspect == QLatin1String("logic")) {
190#if QT_CONFIG(qt3d_logic)
191 m_aspectEngine->registerAspect(new Qt3DLogic::QLogicAspect);
192 continue;
193#else
194 qFatal("Scene3D requested the Qt 3D logic aspect but Qt 3D wasn't configured to build the Qt 3D Logic aspect");
195#endif
196 }
197 if (aspect == QLatin1String("animation")) {
198#if QT_CONFIG(qt3d_animation)
199 m_aspectEngine->registerAspect(new Qt3DAnimation::QAnimationAspect);
200 continue;
201#else
202 qFatal("Scene3D requested the Qt 3D animation aspect but Qt 3D wasn't configured to build the Qt 3D Animation aspect");
203#endif
204 }
205 m_aspectEngine->registerAspect(aspect);
206 }
207
208 emit aspectsChanged();
209}
210
211void Scene3DItem::setEntity(Qt3DCore::QEntity *entity)
212{
213 if (entity == m_entity)
214 return;
215
216 m_entity = entity;
217 emit entityChanged();
218}
219
220void Scene3DItem::setCameraAspectRatioMode(CameraAspectRatioMode mode)
221{
222 if (m_cameraAspectRatioMode == mode)
223 return;
224
225 m_cameraAspectRatioMode = mode;
226 setCameraAspectModeHelper();
227 emit cameraAspectRatioModeChanged(mode);
228}
229
230void Scene3DItem::setHoverEnabled(bool enabled)
231{
232 if (enabled != acceptHoverEvents()) {
233 setAcceptHoverEvents(enabled);
234 emit hoverEnabledChanged();
235 }
236}
237
238/*!
239 \qmlproperty enumeration Scene3D::cameraAspectRatioMode
240
241 \value Scene3D.AutomaticAspectRatio
242 Automatic aspect ratio.
243
244 \value Scene3D.UserAspectRatio
245 User defined aspect ratio.
246 \brief \TODO
247 */
248Scene3DItem::CameraAspectRatioMode Scene3DItem::cameraAspectRatioMode() const
249{
250 return m_cameraAspectRatioMode;
251}
252
253void Scene3DItem::applyRootEntityChange()
254{
255 if (m_aspectEngine->rootEntity() != m_entity) {
256 m_aspectEngine->setRootEntity(Qt3DCore::QEntityPtr(m_entity));
257
258 // Set the render surface
259 if (!m_entity)
260 return;
261
262 setWindowSurface(m_entity);
263
264 if (m_cameraAspectRatioMode == AutomaticAspectRatio) {
265 // Set aspect ratio of first camera to match the window
266 QList<Qt3DRender::QCamera *> cameras
267 = m_entity->findChildren<Qt3DRender::QCamera *>();
268 if (cameras.isEmpty()) {
269 qCDebug(Scene3D) << "No camera found and automatic aspect ratio requested";
270 } else {
271 m_camera = cameras.first();
272 setCameraAspectModeHelper();
273 }
274 }
275
276#if QT_CONFIG(qt3d_input)
277 // Set ourselves up as a source of input events for the input aspect
278 Qt3DInput::QInputSettings *inputSettings = m_entity->findChild<Qt3DInput::QInputSettings *>();
279 if (inputSettings) {
280 inputSettings->setEventSource(this);
281 } else {
282 qCDebug(Scene3D) << "No Input Settings found, keyboard and mouse events won't be handled";
283 }
284#endif
285 }
286}
287
288void Scene3DItem::setWindowSurface(QObject *rootObject)
289{
290 Qt3DRender::QRenderSurfaceSelector *surfaceSelector = Qt3DRender::QRenderSurfaceSelectorPrivate::find(rootObject);
291
292 // Set the item's window surface if it appears
293 // the surface wasn't set on the surfaceSelector
294 if (surfaceSelector && !surfaceSelector->surface()) {
295 // We may not have a real, exposed QQuickWindow when the Quick rendering
296 // is redirected via QQuickRenderControl (f.ex. QQuickWidget).
297 if (QWindow *rw = QQuickRenderControl::renderWindowFor(this->window())) {
298 // rw is the top-level window that is backed by a native window. Do
299 // not use that though since we must not clash with e.g. the widget
300 // backingstore compositor in the gui thread.
301 m_dummySurface = new QOffscreenSurface;
302 m_dummySurface->setParent(qGuiApp); // parent to something suitably long-living
303 m_dummySurface->setFormat(rw->format());
304 m_dummySurface->create();
305 surfaceSelector->setSurface(m_dummySurface);
306 } else {
307 surfaceSelector->setSurface(this->window());
308 }
309 }
310}
311/*!
312 \qmlmethod void Scene3D::setItemAreaAndDevicePixelRatio(size area, real devicePixelRatio)
313
314 \brief \TODO
315 */
316void Scene3DItem::setItemAreaAndDevicePixelRatio(QSize area, qreal devicePixelRatio)
317{
318 Qt3DRender::QRenderSurfaceSelector *surfaceSelector = Qt3DRender::QRenderSurfaceSelectorPrivate::find(m_entity);
319 if (surfaceSelector) {
320 surfaceSelector->setExternalRenderTargetSize(area);
321 surfaceSelector->setSurfacePixelRatio(devicePixelRatio);
322 }
323}
324
325/*!
326 \qmlproperty bool Scene3D::hoverEnabled
327
328 \c true if hover events are accepted.
329 */
330bool Scene3DItem::isHoverEnabled() const
331{
332 return acceptHoverEvents();
333}
334
335void Scene3DItem::setCameraAspectModeHelper()
336{
337 switch (m_cameraAspectRatioMode) {
338 case AutomaticAspectRatio:
339 connect(this, &Scene3DItem::widthChanged, this, &Scene3DItem::updateCameraAspectRatio);
340 connect(this, &Scene3DItem::heightChanged, this, &Scene3DItem::updateCameraAspectRatio);
341 // Update the aspect ratio the first time the surface is set
342 updateCameraAspectRatio();
343 break;
344 case UserAspectRatio:
345 disconnect(this, &Scene3DItem::widthChanged, this, &Scene3DItem::updateCameraAspectRatio);
346 disconnect(this, &Scene3DItem::heightChanged, this, &Scene3DItem::updateCameraAspectRatio);
347 break;
348 }
349}
350
351void Scene3DItem::updateCameraAspectRatio()
352{
353 if (m_camera) {
354 m_camera->setAspectRatio(static_cast<float>(width()) /
355 static_cast<float>(height()));
356 }
357}
358
359/*!
360 \qmlproperty bool Scene3D::multisample
361
362 \c true if a multisample render buffer is requested.
363
364 By default multisampling is enabled. If the OpenGL implementation has no
365 support for multisample renderbuffers or framebuffer blits, the request to
366 use multisampling is ignored.
367
368 \note Refrain from changing the value frequently as it involves expensive
369 and potentially slow initialization of framebuffers and other OpenGL
370 resources.
371 */
372bool Scene3DItem::multisample() const
373{
374 return m_multisample;
375}
376
377void Scene3DItem::setMultisample(bool enable)
378{
379 if (m_multisample != enable) {
380 m_multisample = enable;
381 emit multisampleChanged();
382 update();
383 }
384}
385
386QSGNode *Scene3DItem::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *)
387{
388 // If the render aspect wasn't created yet, do so now
389 if (m_renderAspect == nullptr) {
390 m_renderAspect = new QRenderAspect(QRenderAspect::Synchronous);
391 m_aspectEngine->registerAspect(m_renderAspect);
392 }
393
394 if (m_renderer == nullptr) {
395 m_renderer = new Scene3DRenderer(this, m_aspectEngine, m_renderAspect);
396 m_renderer->setCleanerHelper(m_rendererCleaner);
397 }
398
399 Scene3DSGNode *fboNode = static_cast<Scene3DSGNode *>(node);
400 if (fboNode == nullptr) {
401 fboNode = new Scene3DSGNode();
402 m_renderer->setSGNode(fboNode);
403 }
404 fboNode->setRect(boundingRect());
405
406 return fboNode;
407}
408
409void Scene3DItem::mousePressEvent(QMouseEvent *event)
410{
411 Q_UNUSED(event);
412 //Prevent subsequent move and release events being disregarded my the default event->ignore() from QQuickItem
413}
414
415} // namespace Qt3DRender
416
417QT_END_NAMESPACE
418