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
32QT_BEGIN_NAMESPACE
33
34#ifndef GL_DEPTH24_STENCIL8
35#define GL_DEPTH24_STENCIL8 0x88F0
36#endif
37
38using namespace Qt3DRender::Quick;
39
40namespace Qt3DRender {
41
42namespace Render {
43
44namespace Quick {
45
46Q_GLOBAL_STATIC(QThread, renderThread)
47Q_GLOBAL_STATIC(QAtomicInt, renderThreadClientCount)
48
49RenderQmlEventHandler::RenderQmlEventHandler(Scene2D *node)
50 : QObject()
51 , m_node(node)
52{
53}
54
55// Event handler for the RenderQmlToTexture::renderThread
56bool 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
81Scene2D::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
97Scene2D::~Scene2D()
98{
99 for (auto connection: std::as_const(t&: m_connections))
100 QObject::disconnect(connection);
101 m_connections.clear();
102}
103
104void Scene2D::setOutput(Qt3DCore::QNodeId outputId)
105{
106 m_outputId = outputId;
107}
108
109void 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
139void 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
192void Scene2D::setSharedObject(Qt3DRender::Quick::Scene2DSharedObjectPtr sharedObject)
193{
194 m_sharedObject = sharedObject;
195 if (!m_initialized)
196 initializeSharedObject();
197}
198
199void 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
224bool 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
249void 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
262void 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
346void 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
376bool 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
407void 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
415void 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
463QT_END_NAMESPACE
464
465#include "moc_scene2d_p.cpp"
466

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