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 <Qt3DCore/private/qscene_p.h>
41#include <Qt3DQuickScene2D/qscene2d.h>
42#include <Qt3DRender/qpicktriangleevent.h>
43#include <Qt3DRender/qobjectpicker.h>
44
45#include <QtCore/qthread.h>
46#include <QtCore/qatomic.h>
47#include <QtGui/qevent.h>
48#include <QtGui/QOpenGLFunctions>
49
50#include <private/qscene2d_p.h>
51#include <private/scene2d_p.h>
52#include <private/scene2dmanager_p.h>
53#include <private/scene2devent_p.h>
54#include <private/texture_p.h>
55#include <private/nodemanagers_p.h>
56#include <private/resourceaccessor_p.h>
57#include <private/attachmentpack_p.h>
58#include <private/qt3dquickscene2d_logging_p.h>
59#include <private/qbackendnode_p.h>
60#include <private/qobjectpicker_p.h>
61#include <private/qpickevent_p.h>
62#include <private/qpicktriangleevent_p.h>
63#include <private/entity_p.h>
64#include <private/platformsurfacefilter_p.h>
65#include <private/trianglesvisitor_p.h>
66
67
68QT_BEGIN_NAMESPACE
69
70#ifndef GL_DEPTH24_STENCIL8
71#define GL_DEPTH24_STENCIL8 0x88F0
72#endif
73
74using namespace Qt3DRender::Quick;
75
76namespace Qt3DRender {
77
78namespace Render {
79
80namespace Quick {
81
82Q_GLOBAL_STATIC(QThread, renderThread)
83Q_GLOBAL_STATIC(QAtomicInt, renderThreadClientCount)
84
85RenderQmlEventHandler::RenderQmlEventHandler(Scene2D *node)
86 : QObject()
87 , m_node(node)
88{
89}
90
91// Event handler for the RenderQmlToTexture::renderThread
92bool RenderQmlEventHandler::event(QEvent *e)
93{
94 switch (static_cast<Scene2DEvent::Type>(e->type())) {
95
96 case Scene2DEvent::Render: {
97 m_node->render();
98 return true;
99 }
100
101 case Scene2DEvent::Initialize: {
102 m_node->initializeRender();
103 return true;
104 }
105
106 case Scene2DEvent::Quit: {
107 m_node->cleanup();
108 return true;
109 }
110
111 default:
112 break;
113 }
114 return QObject::event(event: e);
115}
116
117Scene2D::Scene2D()
118 : Qt3DRender::Render::BackendNode(Qt3DCore::QBackendNode::ReadWrite)
119 , m_context(nullptr)
120 , m_shareContext(nullptr)
121 , m_renderThread(nullptr)
122 , m_sharedObject(nullptr)
123 , m_fbo(0)
124 , m_rbo(0)
125 , m_initialized(false)
126 , m_renderInitialized(false)
127 , m_mouseEnabled(true)
128 , m_renderPolicy(Qt3DRender::Quick::QScene2D::Continuous)
129{
130
131}
132
133Scene2D::~Scene2D()
134{
135 for (auto connection: qAsConst(t&: m_connections))
136 QObject::disconnect(connection);
137 m_connections.clear();
138}
139
140void Scene2D::setOutput(Qt3DCore::QNodeId outputId)
141{
142 m_outputId = outputId;
143}
144
145void Scene2D::initializeSharedObject()
146{
147 if (!m_initialized) {
148 // bail out if we're running autotests
149 if (!qgetenv(varName: "QT3D_SCENE2D_DISABLE_RENDERING").isEmpty())
150 return;
151
152 renderThreadClientCount->fetchAndAddAcquire(valueToAdd: 1);
153
154 renderThread->setObjectName(QStringLiteral("Scene2D::renderThread"));
155 m_renderThread = renderThread;
156 m_sharedObject->m_renderThread = m_renderThread;
157
158 // Create event handler for the render thread
159 m_sharedObject->m_renderObject = new RenderQmlEventHandler(this);
160 m_sharedObject->m_renderObject->moveToThread(thread: m_sharedObject->m_renderThread);
161 if (!m_sharedObject->m_renderThread->isRunning())
162 m_sharedObject->m_renderThread->start();
163
164 // Notify main thread we have been initialized
165 QCoreApplication::postEvent(receiver: m_sharedObject->m_renderManager,
166 event: new Scene2DEvent(Scene2DEvent::Initialized));
167 // Initialize render thread
168 QCoreApplication::postEvent(receiver: m_sharedObject->m_renderObject,
169 event: new Scene2DEvent(Scene2DEvent::Initialize));
170
171 m_initialized = true;
172 }
173}
174
175void Scene2D::syncFromFrontEnd(const Qt3DCore::QNode *frontEnd, bool firstTime)
176{
177 Qt3DRender::Render::BackendNode::syncFromFrontEnd(frontEnd, firstTime);
178 const QScene2D *node = qobject_cast<const QScene2D *>(object: frontEnd);
179 if (!node)
180 return;
181 const QScene2DPrivate *dnode = static_cast<const QScene2DPrivate *>(QScene2DPrivate::get(q: node));
182
183 if (m_mouseEnabled != node->isMouseEnabled()) {
184 m_mouseEnabled = node->isMouseEnabled();
185 if (!firstTime && m_mouseEnabled && m_cachedPickEvent) {
186 handlePickEvent(type: QEvent::MouseButtonPress, ev: m_cachedPickEvent.data());
187 m_cachedPickEvent.clear();
188 }
189 }
190
191 m_renderPolicy = node->renderPolicy();
192 auto id = Qt3DCore::qIdForNode(node: node->output());
193 if (id != m_outputId)
194 setOutput(id);
195
196 auto ids = Qt3DCore::qIdsForNodes(nodes: node->entities());
197 std::sort(first: std::begin(cont&: ids), last: std::end(cont&: ids));
198 Qt3DCore::QNodeIdVector addedEntities;
199 Qt3DCore::QNodeIdVector removedEntities;
200 std::set_difference(first1: std::begin(cont&: ids), last1: std::end(cont&: ids),
201 first2: std::begin(cont&: m_entities), last2: std::end(cont&: m_entities),
202 result: std::inserter(x&: addedEntities, i: addedEntities.end()));
203 std::set_difference(first1: std::begin(cont&: m_entities), last1: std::end(cont&: m_entities),
204 first2: std::begin(cont&: ids), last2: std::end(cont&: ids),
205 result: std::inserter(x&: removedEntities, i: removedEntities.end()));
206 for (const auto &id: addedEntities) {
207 Qt3DCore::QEntity *entity = qobject_cast<Qt3DCore::QEntity *>(object: dnode->m_scene->lookupNode(id));
208 if (!entity)
209 return;
210
211 if (registerObjectPickerEvents(qentity: entity))
212 m_entities.push_back(t: id);
213 else
214 Qt3DCore::QNodePrivate::get(q: const_cast<Qt3DCore::QNode *>(frontEnd))->update();
215 }
216 for (const auto &id: removedEntities) {
217 m_entities.removeOne(t: id);
218 unregisterObjectPickerEvents(entityId: id);
219 }
220 std::sort(first: std::begin(cont&: m_entities), last: std::end(cont&: m_entities));
221
222 if (firstTime)
223 setSharedObject(dnode->m_renderManager->m_sharedObject);
224}
225
226void Scene2D::setSharedObject(Qt3DRender::Quick::Scene2DSharedObjectPtr sharedObject)
227{
228 m_sharedObject = sharedObject;
229 if (!m_initialized)
230 initializeSharedObject();
231}
232
233void Scene2D::initializeRender()
234{
235 if (!m_renderInitialized && m_sharedObject.data() != nullptr) {
236 m_shareContext = renderer()->shareContext();
237 if (!m_shareContext){
238 qCDebug(Qt3DRender::Quick::Scene2D) << Q_FUNC_INFO << "Renderer not initialized.";
239 QCoreApplication::postEvent(receiver: m_sharedObject->m_renderObject,
240 event: new Scene2DEvent(Scene2DEvent::Initialize));
241 return;
242 }
243 m_context = new QOpenGLContext();
244 m_context->setFormat(m_shareContext->format());
245 m_context->setShareContext(m_shareContext);
246 m_context->create();
247
248 m_context->makeCurrent(surface: m_sharedObject->m_surface);
249 m_sharedObject->m_renderControl->initialize(gl: m_context);
250#ifdef QT_OPENGL_ES_2_ANGLE
251 m_usingAngle = false;
252 if (m_context->isOpenGLES()) {
253 const char *versionStr = reinterpret_cast<const char *>(
254 m_context->functions()->glGetString(GL_VERSION));
255 if (strstr(versionStr, "ANGLE"))
256 m_usingAngle = true;
257 }
258#endif
259 m_context->doneCurrent();
260
261 QCoreApplication::postEvent(receiver: m_sharedObject->m_renderManager,
262 event: new Scene2DEvent(Scene2DEvent::Prepare));
263 m_renderInitialized = true;
264 }
265}
266
267bool Scene2D::updateFbo(QOpenGLTexture *texture)
268{
269 QOpenGLFunctions *gl = m_context->functions();
270 if (m_fbo == 0) {
271 gl->glGenFramebuffers(n: 1, framebuffers: &m_fbo);
272 gl->glGenRenderbuffers(n: 1, renderbuffers: &m_rbo);
273 }
274 // TODO: Add another codepath when GL_DEPTH24_STENCIL8 is not supported
275 gl->glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer: m_rbo);
276 gl->glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8,
277 width: m_textureSize.width(), height: m_textureSize.height());
278 gl->glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer: 0);
279
280 gl->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: m_fbo);
281 gl->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
282 GL_TEXTURE_2D, texture: texture->textureId(), level: 0);
283 gl->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderbuffer: m_rbo);
284 GLenum status = gl->glCheckFramebufferStatus(GL_FRAMEBUFFER);
285 gl->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: 0);
286
287 if (status != GL_FRAMEBUFFER_COMPLETE)
288 return false;
289 return true;
290}
291
292void Scene2D::syncRenderControl()
293{
294 if (m_sharedObject->isSyncRequested()) {
295
296 m_sharedObject->clearSyncRequest();
297
298 m_sharedObject->m_renderControl->sync();
299
300 // gui thread can now continue
301 m_sharedObject->wake();
302 }
303}
304
305void Scene2D::render()
306{
307 if (m_initialized && m_renderInitialized && m_sharedObject.data() != nullptr) {
308
309 QMutexLocker lock(&m_sharedObject->m_mutex);
310
311 QOpenGLTexture *texture = nullptr;
312 const Qt3DRender::Render::Attachment *attachmentData = nullptr;
313 QMutex *textureLock = nullptr;
314
315#ifdef QT_OPENGL_ES_2_ANGLE
316 QScopedPointer<SurfaceLocker> surfaceLocker;
317 if (m_usingAngle)
318 surfaceLocker.reset(new SurfaceLocker(m_sharedObject->m_surface));
319#endif
320 m_context->makeCurrent(surface: m_sharedObject->m_surface);
321
322 if (resourceAccessor()->accessResource(type: RenderBackendResourceAccessor::OutputAttachment,
323 nodeId: m_outputId, handle: (void**)&attachmentData, lock: nullptr)) {
324 if (!resourceAccessor()->accessResource(type: RenderBackendResourceAccessor::OGLTextureWrite,
325 nodeId: attachmentData->m_textureUuid,
326 handle: (void**)&texture, lock: &textureLock)) {
327 // Need to call sync even if the texture is not in use
328 syncRenderControl();
329 m_context->doneCurrent();
330 qCDebug(Qt3DRender::Quick::Scene2D) << Q_FUNC_INFO << "Texture not in use.";
331 QCoreApplication::postEvent(receiver: m_sharedObject->m_renderObject,
332 event: new Scene2DEvent(Scene2DEvent::Render));
333 return;
334 }
335#ifdef QT_OPENGL_ES_2_ANGLE
336 if (m_usingAngle == false)
337 textureLock->lock();
338#else
339 textureLock->lock();
340#endif
341 const QSize textureSize = QSize(texture->width(), texture->height());
342 if (m_attachmentData.m_textureUuid != attachmentData->m_textureUuid
343 || m_attachmentData.m_point != attachmentData->m_point
344 || m_attachmentData.m_face != attachmentData->m_face
345 || m_attachmentData.m_layer != attachmentData->m_layer
346 || m_attachmentData.m_mipLevel != attachmentData->m_mipLevel
347 || m_textureSize != textureSize) {
348 m_textureSize = textureSize;
349 m_attachmentData = *attachmentData;
350 if (!updateFbo(texture)) {
351 // Need to call sync even if the fbo is not usable
352 syncRenderControl();
353 textureLock->unlock();
354 m_context->doneCurrent();
355 qCWarning(Qt3DRender::Quick::Scene2D) << Q_FUNC_INFO << "Fbo not initialized.";
356 return;
357 }
358 }
359 }
360
361 if (m_fbo != m_sharedObject->m_quickWindow->renderTargetId())
362 m_sharedObject->m_quickWindow->setRenderTarget(fboId: m_fbo, size: m_textureSize);
363
364 // Call disallow rendering while mutex is locked
365 if (m_renderPolicy == QScene2D::SingleShot)
366 m_sharedObject->disallowRender();
367
368 // Sync
369 if (m_sharedObject->isSyncRequested()) {
370
371 m_sharedObject->clearSyncRequest();
372
373 m_sharedObject->m_renderControl->sync();
374 }
375
376 // Render
377 m_sharedObject->m_renderControl->render();
378
379 // Tell main thread we are done so it can begin cleanup if this is final frame
380 if (m_renderPolicy == QScene2D::SingleShot)
381 QCoreApplication::postEvent(receiver: m_sharedObject->m_renderManager,
382 event: new Scene2DEvent(Scene2DEvent::Rendered));
383
384 m_sharedObject->m_quickWindow->resetOpenGLState();
385 m_context->functions()->glFlush();
386 if (texture->isAutoMipMapGenerationEnabled())
387 texture->generateMipMaps();
388#ifdef QT_OPENGL_ES_2_ANGLE
389 if (m_usingAngle == false)
390 textureLock->unlock();
391#else
392 textureLock->unlock();
393#endif
394 m_context->doneCurrent();
395
396 // gui thread can now continue
397 m_sharedObject->wake();
398 }
399}
400
401// this function gets called while the main thread is waiting
402void Scene2D::cleanup()
403{
404 if (m_renderInitialized && m_initialized) {
405 m_context->makeCurrent(surface: m_sharedObject->m_surface);
406 m_sharedObject->m_renderControl->invalidate();
407 m_context->functions()->glDeleteFramebuffers(n: 1, framebuffers: &m_fbo);
408 m_context->functions()->glDeleteRenderbuffers(n: 1, renderbuffers: &m_rbo);
409 m_context->doneCurrent();
410 m_renderInitialized = false;
411 }
412 if (m_initialized) {
413 delete m_sharedObject->m_renderObject;
414 m_sharedObject->m_renderObject = nullptr;
415 delete m_context;
416 m_context = nullptr;
417 m_initialized = false;
418 }
419 if (m_sharedObject) {
420 // wake up the main thread
421 m_sharedObject->wake();
422 m_sharedObject = nullptr;
423 }
424 if (m_renderThread) {
425 renderThreadClientCount->fetchAndSubAcquire(valueToAdd: 1);
426 if (renderThreadClientCount->loadRelaxed() == 0)
427 renderThread->quit();
428 }
429}
430
431
432bool Scene2D::registerObjectPickerEvents(Qt3DCore::QEntity *qentity)
433{
434 Entity *entity = nullptr;
435 if (!resourceAccessor()->accessResource(type: RenderBackendResourceAccessor::EntityHandle,
436 nodeId: qentity->id(), handle: (void**)&entity, lock: nullptr)) {
437 qCWarning(Qt3DRender::Quick::Scene2D) << Q_FUNC_INFO
438 << "Entity not yet available in backend";
439 return false;
440 }
441
442 if (!entity->containsComponentsOfType<ObjectPicker>() ||
443 !entity->containsComponentsOfType<GeometryRenderer>()) {
444 qCWarning(Qt3DRender::Quick::Scene2D) << Q_FUNC_INFO
445 << "Entity does not contain required components: ObjectPicker and GeometryRenderer";
446 return false;
447 }
448
449 QObjectPicker *picker = qentity->componentsOfType<QObjectPicker>().front();
450 m_connections << QObject::connect(sender: picker, signal: &QObjectPicker::pressed, context: qentity, slot: [this](Qt3DRender::QPickEvent *pick) {
451 handlePickEvent(type: QEvent::MouseButtonPress, ev: pick);
452 });
453 m_connections << QObject::connect(sender: picker, signal: &QObjectPicker::released, context: qentity, slot: [this](Qt3DRender::QPickEvent *pick) {
454 handlePickEvent(type: QEvent::MouseButtonRelease, ev: pick);
455 });
456 m_connections << QObject::connect(sender: picker, signal: &QObjectPicker::moved, context: qentity, slot: [this](Qt3DRender::QPickEvent *pick) {
457 handlePickEvent(type: QEvent::MouseMove, ev: pick);
458 });
459
460 Qt3DCore::QBackendNodePrivate *priv = Qt3DCore::QBackendNodePrivate::get(n: this);
461 Qt3DCore::QChangeArbiter *arbiter = static_cast<Qt3DCore::QChangeArbiter*>(priv->m_arbiter);
462 arbiter->registerObserver(observer: d_ptr, nodeId: entity->componentUuid<ObjectPicker>());
463 return true;
464}
465
466void Scene2D::unregisterObjectPickerEvents(Qt3DCore::QNodeId entityId)
467{
468 Entity *entity = nullptr;
469 if (!resourceAccessor()->accessResource(type: RenderBackendResourceAccessor::EntityHandle,
470 nodeId: entityId, handle: (void**)&entity, lock: nullptr))
471 return;
472
473 Qt3DCore::QBackendNodePrivate *priv = Qt3DCore::QBackendNodePrivate::get(n: this);
474 Qt3DCore::QChangeArbiter *arbiter = static_cast<Qt3DCore::QChangeArbiter*>(priv->m_arbiter);
475 arbiter->unregisterObserver(observer: d_ptr, nodeId: entity->componentUuid<ObjectPicker>());
476}
477
478void Scene2D::handlePickEvent(int type, const Qt3DRender::QPickEvent *ev)
479{
480 if (!isEnabled())
481 return;
482 if (m_mouseEnabled) {
483 const QPickTriangleEvent *pickTriangle = static_cast<const QPickTriangleEvent *>(ev);
484 Q_ASSERT(pickTriangle->entity());
485 Entity *entity = nullptr;
486 if (!resourceAccessor()->accessResource(type: RenderBackendResourceAccessor::EntityHandle,
487 nodeId: Qt3DCore::qIdForNode(node: pickTriangle->entity()),
488 handle: (void**)&entity, lock: nullptr))
489 return;
490
491 CoordinateReader reader(renderer()->nodeManagers());
492 if (reader.setGeometry(renderer: entity->renderComponent<GeometryRenderer>(),
493 attributeName: QAttribute::defaultTextureCoordinateAttributeName())) {
494 Vector4D c0 = reader.getCoordinate(vertexIndex: pickTriangle->vertex1Index());
495 Vector4D c1 = reader.getCoordinate(vertexIndex: pickTriangle->vertex2Index());
496 Vector4D c2 = reader.getCoordinate(vertexIndex: pickTriangle->vertex3Index());
497 Vector4D ci = c0 * pickTriangle->uvw().x()
498 + c1 * pickTriangle->uvw().y() + c2 * pickTriangle->uvw().z();
499 ci.setW(1.0f);
500
501 const QSize size = m_sharedObject->m_quickWindow->size();
502 QPointF pos = QPointF(ci.x() * size.width(), (1.0f - ci.y()) * size.height());
503 QMouseEvent *mouseEvent
504 = new QMouseEvent(static_cast<QEvent::Type>(type),
505 pos, pos, pos,
506 static_cast<Qt::MouseButton>(pickTriangle->button()),
507 static_cast<Qt::MouseButtons>(pickTriangle->buttons()),
508 static_cast<Qt::KeyboardModifiers>(pickTriangle->modifiers()),
509 Qt::MouseEventSynthesizedByApplication);
510
511 QCoreApplication::postEvent(receiver: m_sharedObject->m_quickWindow, event: mouseEvent);
512 }
513 } else if (type == QEvent::MouseButtonPress) {
514 const QPickTriangleEvent *pickTriangle = static_cast<const QPickTriangleEvent *>(ev);
515 const QPickTriangleEventPrivate *dpick = QPickTriangleEventPrivate::get(ev: pickTriangle);
516 m_cachedPickEvent = QPickEventPtr(dpick->clone());
517 } else {
518 m_cachedPickEvent.clear();
519 }
520}
521
522} // namespace Quick
523} // namespace Render
524} // namespace Qt3DRender
525
526QT_END_NAMESPACE
527

source code of qt3d/src/quick3d/quick3dscene2d/items/scene2d.cpp