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