| 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 |  | 
| 68 | QT_BEGIN_NAMESPACE | 
| 69 |  | 
| 70 | #ifndef GL_DEPTH24_STENCIL8 | 
| 71 | #define GL_DEPTH24_STENCIL8 0x88F0 | 
| 72 | #endif | 
| 73 |  | 
| 74 | using namespace Qt3DRender::Quick; | 
| 75 |  | 
| 76 | namespace Qt3DRender { | 
| 77 |  | 
| 78 | namespace Render { | 
| 79 |  | 
| 80 | namespace Quick { | 
| 81 |  | 
| 82 | Q_GLOBAL_STATIC(QThread, renderThread) | 
| 83 | Q_GLOBAL_STATIC(QAtomicInt, renderThreadClientCount) | 
| 84 |  | 
| 85 | RenderQmlEventHandler::RenderQmlEventHandler(Scene2D *node) | 
| 86 |     : QObject() | 
| 87 |     , m_node(node) | 
| 88 | { | 
| 89 | } | 
| 90 |  | 
| 91 | // Event handler for the RenderQmlToTexture::renderThread | 
| 92 | bool 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 |  | 
| 117 | Scene2D::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 |  | 
| 133 | Scene2D::~Scene2D() | 
| 134 | { | 
| 135 |     for (auto connection: qAsConst(t&: m_connections)) | 
| 136 |         QObject::disconnect(connection); | 
| 137 |     m_connections.clear(); | 
| 138 | } | 
| 139 |  | 
| 140 | void Scene2D::setOutput(Qt3DCore::QNodeId outputId) | 
| 141 | { | 
| 142 |     m_outputId = outputId; | 
| 143 | } | 
| 144 |  | 
| 145 | void 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 |  | 
| 175 | void 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 |  | 
| 226 | void Scene2D::setSharedObject(Qt3DRender::Quick::Scene2DSharedObjectPtr sharedObject) | 
| 227 | { | 
| 228 |     m_sharedObject = sharedObject; | 
| 229 |     if (!m_initialized) | 
| 230 |         initializeSharedObject(); | 
| 231 | } | 
| 232 |  | 
| 233 | void 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 |  | 
| 267 | bool 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 |  | 
| 292 | void 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 |  | 
| 305 | void 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 | 
| 402 | void 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 |  | 
| 432 | bool 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 |  | 
| 466 | void 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 |  | 
| 478 | void 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 |  | 
| 526 | QT_END_NAMESPACE | 
| 527 |  |