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