1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: http://www.qt.io/licensing/
5**
6** This file is part of the Qt3D module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL3$
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 http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free
28** Software Foundation and appearing in the file LICENSE.GPL included in
29** the packaging of this file. Please review the following information to
30** ensure the GNU General Public License version 2.0 requirements will be
31** met: http://www.gnu.org/licenses/gpl-2.0.html.
32**
33** $QT_END_LICENSE$
34**
35****************************************************************************/
36
37#include <Qt3DCore/qpropertyupdatedchange.h>
38#include <Qt3DCore/qpropertynodeaddedchange.h>
39#include <Qt3DCore/qpropertynoderemovedchange.h>
40#include <Qt3DQuickScene2D/qscene2d.h>
41#include <Qt3DRender/qpicktriangleevent.h>
42#include <Qt3DRender/qobjectpicker.h>
43
44#include <QtCore/qthread.h>
45#include <QtCore/qatomic.h>
46#include <QtGui/qevent.h>
47
48#include <private/qscene2d_p.h>
49#include <private/scene2d_p.h>
50#include <private/scene2dmanager_p.h>
51#include <private/scene2devent_p.h>
52#include <private/graphicscontext_p.h>
53#include <private/texture_p.h>
54#include <private/nodemanagers_p.h>
55#include <private/resourceaccessor_p.h>
56#include <private/attachmentpack_p.h>
57#include <private/qt3dquickscene2d_logging_p.h>
58#include <private/qbackendnode_p.h>
59#include <private/qobjectpicker_p.h>
60#include <private/qpickevent_p.h>
61#include <private/entity_p.h>
62#include <private/platformsurfacefilter_p.h>
63#include <private/trianglesvisitor_p.h>
64
65
66QT_BEGIN_NAMESPACE
67
68#ifndef GL_DEPTH24_STENCIL8
69#define GL_DEPTH24_STENCIL8 0x88F0
70#endif
71
72using namespace Qt3DRender::Quick;
73
74namespace Qt3DRender {
75
76namespace Render {
77
78namespace Quick {
79
80Q_GLOBAL_STATIC(QThread, renderThread)
81Q_GLOBAL_STATIC(QAtomicInt, renderThreadClientCount)
82
83RenderQmlEventHandler::RenderQmlEventHandler(Scene2D *node)
84 : QObject()
85 , m_node(node)
86{
87}
88
89// Event handler for the RenderQmlToTexture::renderThread
90bool RenderQmlEventHandler::event(QEvent *e)
91{
92 switch (static_cast<Scene2DEvent::Type>(e->type())) {
93
94 case Scene2DEvent::Render: {
95 m_node->render();
96 return true;
97 }
98
99 case Scene2DEvent::Initialize: {
100 m_node->initializeRender();
101 return true;
102 }
103
104 case Scene2DEvent::Quit: {
105 m_node->cleanup();
106 return true;
107 }
108
109 default:
110 break;
111 }
112 return QObject::event(e);
113}
114
115Scene2D::Scene2D()
116 : Qt3DRender::Render::BackendNode(Qt3DCore::QBackendNode::ReadWrite)
117 , m_context(nullptr)
118 , m_shareContext(nullptr)
119 , m_renderThread(nullptr)
120 , m_sharedObject(nullptr)
121 , m_fbo(0)
122 , m_rbo(0)
123 , m_initialized(false)
124 , m_renderInitialized(false)
125 , m_mouseEnabled(true)
126 , m_renderPolicy(Qt3DRender::Quick::QScene2D::Continuous)
127{
128
129}
130
131Scene2D::~Scene2D()
132{
133 stopGrabbing();
134}
135
136void Scene2D::setOutput(Qt3DCore::QNodeId outputId)
137{
138 m_outputId = outputId;
139}
140
141void Scene2D::initializeSharedObject()
142{
143 if (!m_initialized) {
144
145 // bail out if we're running autotests
146 if (!m_sharedObject->m_renderManager
147 || m_sharedObject->m_renderManager->thread() == QThread::currentThread()) {
148 return;
149 }
150
151 renderThreadClientCount->fetchAndAddAcquire(1);
152
153 renderThread->setObjectName(QStringLiteral("Scene2D::renderThread"));
154 m_renderThread = renderThread;
155 m_sharedObject->m_renderThread = m_renderThread;
156
157 // Create event handler for the render thread
158 m_sharedObject->m_renderObject = new RenderQmlEventHandler(this);
159 m_sharedObject->m_renderObject->moveToThread(m_sharedObject->m_renderThread);
160 if (!m_sharedObject->m_renderThread->isRunning())
161 m_sharedObject->m_renderThread->start();
162
163 // Notify main thread we have been initialized
164 QCoreApplication::postEvent(m_sharedObject->m_renderManager,
165 new Scene2DEvent(Scene2DEvent::Initialized));
166 // Initialize render thread
167 QCoreApplication::postEvent(m_sharedObject->m_renderObject,
168 new Scene2DEvent(Scene2DEvent::Initialize));
169
170 m_initialized = true;
171 }
172}
173
174void Scene2D::initializeFromPeer(const Qt3DCore::QNodeCreatedChangeBasePtr &change)
175{
176 const auto typedChange = qSharedPointerCast<Qt3DCore::QNodeCreatedChange<QScene2DData>>(change);
177 const auto &data = typedChange->data;
178 m_renderPolicy = data.renderPolicy;
179 setSharedObject(data.sharedObject);
180 setOutput(data.output);
181 m_entities = data.entityIds;
182 m_mouseEnabled = data.mouseEnabled;
183}
184
185void Scene2D::sceneChangeEvent(const Qt3DCore::QSceneChangePtr &e)
186{
187 switch (e->type()) {
188
189 case Qt3DCore::PropertyUpdated: {
190
191 Qt3DCore::QPropertyUpdatedChangePtr propertyChange
192 = qSharedPointerCast<Qt3DCore::QPropertyUpdatedChange>(e);
193 if (propertyChange->propertyName() == QByteArrayLiteral("renderPolicy")) {
194 m_renderPolicy = propertyChange->value().value<QScene2D::RenderPolicy>();
195 } else if (propertyChange->propertyName() == QByteArrayLiteral("output")) {
196 Qt3DCore::QNodeId outputId = propertyChange->value().value<Qt3DCore::QNodeId>();
197 setOutput(outputId);
198 } else if (propertyChange->propertyName() == QByteArrayLiteral("pressed")) {
199 QObjectPickerEvent ev = propertyChange->value().value<QObjectPickerEvent>();
200 QPickEventPtr pickEvent = ev.event;
201 handlePickEvent(QEvent::MouseButtonPress, pickEvent);
202 } else if (propertyChange->propertyName() == QByteArrayLiteral("released")) {
203 QObjectPickerEvent ev = propertyChange->value().value<QObjectPickerEvent>();
204 QPickEventPtr pickEvent = ev.event;
205 handlePickEvent(QEvent::MouseButtonRelease, pickEvent);
206 } else if (propertyChange->propertyName() == QByteArrayLiteral("moved")) {
207 QObjectPickerEvent ev = propertyChange->value().value<QObjectPickerEvent>();
208 QPickEventPtr pickEvent = ev.event;
209 handlePickEvent(QEvent::MouseMove, pickEvent);
210 } else if (propertyChange->propertyName() == QByteArrayLiteral("mouseEnabled")) {
211 m_mouseEnabled = propertyChange->value().toBool();
212 if (m_mouseEnabled && !m_cachedPickEvent.isNull()) {
213 handlePickEvent(QEvent::MouseButtonPress, m_cachedPickEvent);
214 m_cachedPickEvent.clear();
215 }
216 } else if (propertyChange->propertyName() == QByteArrayLiteral("sceneInitialized")) {
217 startGrabbing();
218 }
219 break;
220 }
221
222 case Qt3DCore::PropertyValueAdded: {
223 const auto change = qSharedPointerCast<Qt3DCore::QPropertyNodeAddedChange>(e);
224 if (change->propertyName() == QByteArrayLiteral("entities")) {
225 m_entities.push_back(change->addedNodeId());
226 registerObjectPickerEvents(change->addedNodeId());
227 }
228 break;
229 }
230
231 case Qt3DCore::PropertyValueRemoved: {
232 const auto change = qSharedPointerCast<Qt3DCore::QPropertyNodeRemovedChange>(e);
233 if (change->propertyName() == QByteArrayLiteral("entities")) {
234 m_entities.removeOne(change->removedNodeId());
235 unregisterObjectPickerEvents(change->removedNodeId());
236 }
237 break;
238 }
239
240 default:
241 break;
242 }
243 BackendNode::sceneChangeEvent(e);
244}
245
246void Scene2D::setSharedObject(Qt3DRender::Quick::Scene2DSharedObjectPtr sharedObject)
247{
248 m_sharedObject = sharedObject;
249 if (!m_initialized)
250 initializeSharedObject();
251}
252
253void Scene2D::initializeRender()
254{
255 if (!m_renderInitialized && m_sharedObject.data() != nullptr) {
256 m_shareContext = renderer()->shareContext();
257 if (!m_shareContext){
258 qCDebug(Qt3DRender::Quick::Scene2D) << Q_FUNC_INFO << "Renderer not initialized.";
259 QCoreApplication::postEvent(m_sharedObject->m_renderObject,
260 new Scene2DEvent(Scene2DEvent::Initialize));
261 return;
262 }
263 m_context = new QOpenGLContext();
264 m_context->setFormat(m_shareContext->format());
265 m_context->setShareContext(m_shareContext);
266 m_context->create();
267
268 m_context->makeCurrent(m_sharedObject->m_surface);
269 m_sharedObject->m_renderControl->initialize(m_context);
270#ifdef QT_OPENGL_ES_2_ANGLE
271 m_usingAngle = false;
272 if (m_context->isOpenGLES()) {
273 const char *versionStr = reinterpret_cast<const char *>(
274 m_context->functions()->glGetString(GL_VERSION));
275 if (strstr(versionStr, "ANGLE"))
276 m_usingAngle = true;
277 }
278#endif
279 m_context->doneCurrent();
280
281 QCoreApplication::postEvent(m_sharedObject->m_renderManager,
282 new Scene2DEvent(Scene2DEvent::Prepare));
283 m_renderInitialized = true;
284 }
285}
286
287bool Scene2D::updateFbo(QOpenGLTexture *texture)
288{
289 QOpenGLFunctions *gl = m_context->functions();
290 if (m_fbo == 0) {
291 gl->glGenFramebuffers(1, &m_fbo);
292 gl->glGenRenderbuffers(1, &m_rbo);
293 }
294 // TODO: Add another codepath when GL_DEPTH24_STENCIL8 is not supported
295 gl->glBindRenderbuffer(GL_RENDERBUFFER, m_rbo);
296 gl->glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8,
297 m_textureSize.width(), m_textureSize.height());
298 gl->glBindRenderbuffer(GL_RENDERBUFFER, 0);
299
300 gl->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
301 gl->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
302 GL_TEXTURE_2D, texture->textureId(), 0);
303 gl->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_rbo);
304 GLenum status = gl->glCheckFramebufferStatus(GL_FRAMEBUFFER);
305 gl->glBindFramebuffer(GL_FRAMEBUFFER, 0);
306
307 if (status != GL_FRAMEBUFFER_COMPLETE)
308 return false;
309 return true;
310}
311
312void Scene2D::syncRenderControl()
313{
314 if (m_sharedObject->isSyncRequested()) {
315
316 m_sharedObject->clearSyncRequest();
317
318 m_sharedObject->m_renderControl->sync();
319
320 // gui thread can now continue
321 m_sharedObject->wake();
322 }
323}
324
325void Scene2D::render()
326{
327 if (m_initialized && m_renderInitialized && m_sharedObject.data() != nullptr) {
328
329 QMutexLocker lock(&m_sharedObject->m_mutex);
330
331 QOpenGLTexture *texture = nullptr;
332 const Qt3DRender::Render::Attachment *attachmentData = nullptr;
333 QMutex *textureLock = nullptr;
334
335#ifdef QT_OPENGL_ES_2_ANGLE
336 QScopedPointer<SurfaceLocker> surfaceLocker;
337 if (m_usingAngle)
338 surfaceLocker.reset(new SurfaceLocker(m_sharedObject->m_surface));
339#endif
340 m_context->makeCurrent(m_sharedObject->m_surface);
341
342 if (resourceAccessor()->accessResource(RenderBackendResourceAccessor::OutputAttachment,
343 m_outputId, (void**)&attachmentData, nullptr)) {
344 if (!resourceAccessor()->accessResource(RenderBackendResourceAccessor::OGLTextureWrite,
345 attachmentData->m_textureUuid,
346 (void**)&texture, &textureLock)) {
347 // Need to call sync even if the texture is not in use
348 syncRenderControl();
349 m_context->doneCurrent();
350 qCDebug(Qt3DRender::Quick::Scene2D) << Q_FUNC_INFO << "Texture not in use.";
351 QCoreApplication::postEvent(m_sharedObject->m_renderObject,
352 new Scene2DEvent(Scene2DEvent::Render));
353 return;
354 }
355#ifdef QT_OPENGL_ES_2_ANGLE
356 if (m_usingAngle == false)
357 textureLock->lock();
358#else
359 textureLock->lock();
360#endif
361 const QSize textureSize = QSize(texture->width(), texture->height());
362 if (m_attachmentData.m_textureUuid != attachmentData->m_textureUuid
363 || m_attachmentData.m_point != attachmentData->m_point
364 || m_attachmentData.m_face != attachmentData->m_face
365 || m_attachmentData.m_layer != attachmentData->m_layer
366 || m_attachmentData.m_mipLevel != attachmentData->m_mipLevel
367 || m_textureSize != textureSize) {
368 m_textureSize = textureSize;
369 m_attachmentData = *attachmentData;
370 if (!updateFbo(texture)) {
371 // Need to call sync even if the fbo is not usable
372 syncRenderControl();
373 textureLock->unlock();
374 m_context->doneCurrent();
375 qCWarning(Qt3DRender::Quick::Scene2D) << Q_FUNC_INFO << "Fbo not initialized.";
376 return;
377 }
378 }
379 }
380
381 if (m_fbo != m_sharedObject->m_quickWindow->renderTargetId())
382 m_sharedObject->m_quickWindow->setRenderTarget(m_fbo, m_textureSize);
383
384 // Call disallow rendering while mutex is locked
385 if (m_renderPolicy == QScene2D::SingleShot)
386 m_sharedObject->disallowRender();
387
388 // Sync
389 if (m_sharedObject->isSyncRequested()) {
390
391 m_sharedObject->clearSyncRequest();
392
393 m_sharedObject->m_renderControl->sync();
394 }
395
396 // Render
397 m_sharedObject->m_renderControl->render();
398
399 // Tell main thread we are done so it can begin cleanup if this is final frame
400 if (m_renderPolicy == QScene2D::SingleShot)
401 QCoreApplication::postEvent(m_sharedObject->m_renderManager,
402 new Scene2DEvent(Scene2DEvent::Rendered));
403
404 m_sharedObject->m_quickWindow->resetOpenGLState();
405 m_context->functions()->glFlush();
406 if (texture->isAutoMipMapGenerationEnabled())
407 texture->generateMipMaps();
408#ifdef QT_OPENGL_ES_2_ANGLE
409 if (m_usingAngle == false)
410 textureLock->unlock();
411#else
412 textureLock->unlock();
413#endif
414 m_context->doneCurrent();
415
416 // gui thread can now continue
417 m_sharedObject->wake();
418 }
419}
420
421// this function gets called while the main thread is waiting
422void Scene2D::cleanup()
423{
424 if (m_renderInitialized && m_initialized) {
425 m_context->makeCurrent(m_sharedObject->m_surface);
426 m_sharedObject->m_renderControl->invalidate();
427 m_context->functions()->glDeleteFramebuffers(1, &m_fbo);
428 m_context->functions()->glDeleteRenderbuffers(1, &m_rbo);
429 m_context->doneCurrent();
430 m_renderInitialized = false;
431 }
432 if (m_initialized) {
433 delete m_sharedObject->m_renderObject;
434 m_sharedObject->m_renderObject = nullptr;
435 delete m_context;
436 m_context = nullptr;
437 m_initialized = false;
438 }
439 if (m_sharedObject) {
440 // wake up the main thread
441 m_sharedObject->wake();
442 m_sharedObject = nullptr;
443 }
444 if (m_renderThread) {
445 renderThreadClientCount->fetchAndSubAcquire(1);
446 if (renderThreadClientCount->load() == 0)
447 renderThread->quit();
448 }
449}
450
451
452bool Scene2D::registerObjectPickerEvents(Qt3DCore::QNodeId entityId)
453{
454 Entity *entity = nullptr;
455 if (!resourceAccessor()->accessResource(RenderBackendResourceAccessor::EntityHandle,
456 entityId, (void**)&entity, nullptr)) {
457 return false;
458 }
459 if (!entity->containsComponentsOfType<ObjectPicker>() ||
460 !entity->containsComponentsOfType<GeometryRenderer>()) {
461 qCWarning(Qt3DRender::Quick::Scene2D) << Q_FUNC_INFO
462 << "Entity does not contain required components: ObjectPicker and GeometryRenderer";
463 return false;
464 }
465 Qt3DCore::QBackendNodePrivate *priv = Qt3DCore::QBackendNodePrivate::get(this);
466 Qt3DCore::QChangeArbiter *arbiter = static_cast<Qt3DCore::QChangeArbiter*>(priv->m_arbiter);
467 arbiter->registerObserver(d_ptr, entity->componentUuid<ObjectPicker>());
468 return true;
469}
470
471void Scene2D::unregisterObjectPickerEvents(Qt3DCore::QNodeId entityId)
472{
473 Entity *entity = nullptr;
474 if (!resourceAccessor()->accessResource(RenderBackendResourceAccessor::EntityHandle,
475 entityId, (void**)&entity, nullptr)) {
476 return;
477 }
478 Qt3DCore::QBackendNodePrivate *priv = Qt3DCore::QBackendNodePrivate::get(this);
479 Qt3DCore::QChangeArbiter *arbiter = static_cast<Qt3DCore::QChangeArbiter*>(priv->m_arbiter);
480 arbiter->unregisterObserver(d_ptr, entity->componentUuid<ObjectPicker>());
481}
482
483void Scene2D::handlePickEvent(int type, const Qt3DRender::QPickEventPtr &ev)
484{
485 if (!isEnabled())
486 return;
487 if (m_mouseEnabled) {
488 QPickTriangleEvent *pickTriangle = static_cast<QPickTriangleEvent *>(ev.data());
489 Entity *entity = nullptr;
490 if (!resourceAccessor()->accessResource(RenderBackendResourceAccessor::EntityHandle,
491 QPickEventPrivate::get(pickTriangle)->m_entity,
492 (void**)&entity, nullptr)) {
493 return;
494 }
495 CoordinateReader reader(renderer()->nodeManagers());
496 if (reader.setGeometry(entity->renderComponent<GeometryRenderer>(),
497 QAttribute::defaultTextureCoordinateAttributeName())) {
498 Vector4D c0 = reader.getCoordinate(pickTriangle->vertex1Index());
499 Vector4D c1 = reader.getCoordinate(pickTriangle->vertex2Index());
500 Vector4D c2 = reader.getCoordinate(pickTriangle->vertex3Index());
501 Vector4D ci = c0 * pickTriangle->uvw().x()
502 + c1 * pickTriangle->uvw().y() + c2 * pickTriangle->uvw().z();
503 ci.setW(1.0f);
504
505 const QSize size = m_sharedObject->m_quickWindow->size();
506 QPointF pos = QPointF(ci.x() * size.width(), (1.0f - ci.y()) * size.height());
507 QMouseEvent *mouseEvent
508 = new QMouseEvent(static_cast<QEvent::Type>(type),
509 pos, pos, pos,
510 static_cast<Qt::MouseButton>(pickTriangle->button()),
511 static_cast<Qt::MouseButtons>(pickTriangle->buttons()),
512 static_cast<Qt::KeyboardModifiers>(pickTriangle->modifiers()),
513 Qt::MouseEventSynthesizedByApplication);
514
515 QCoreApplication::postEvent(m_sharedObject->m_quickWindow, mouseEvent);
516 }
517 } else if (type == QEvent::MouseButtonPress) {
518 m_cachedPickEvent = ev;
519 } else {
520 m_cachedPickEvent.clear();
521 }
522}
523
524void Scene2D::startGrabbing()
525{
526 for (Qt3DCore::QNodeId e : qAsConst(m_entities))
527 registerObjectPickerEvents(e);
528}
529
530void Scene2D::stopGrabbing()
531{
532 for (Qt3DCore::QNodeId e : qAsConst(m_entities))
533 unregisterObjectPickerEvents(e);
534}
535
536} // namespace Quick
537} // namespace Render
538} // namespace Qt3DRender
539
540QT_END_NAMESPACE
541