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
38
39namespace Qt3DRender {
40
41namespace Render {
42
43namespace Quick {
44
45using namespace Qt3DRender::Quick;
46
47Q_GLOBAL_STATIC(QThread, renderThread)
48Q_GLOBAL_STATIC(QAtomicInt, renderThreadClientCount)
49
50RenderQmlEventHandler::RenderQmlEventHandler(Scene2D *node)
51 : QObject()
52 , m_node(node)
53{
54}
55
56// Event handler for the RenderQmlToTexture::renderThread
57bool 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
82Scene2D::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
98Scene2D::~Scene2D()
99{
100 for (auto connection: std::as_const(t&: m_connections))
101 QObject::disconnect(connection);
102 m_connections.clear();
103}
104
105void Scene2D::setOutput(Qt3DCore::QNodeId outputId)
106{
107 m_outputId = outputId;
108}
109
110void 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
140void 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
193void Scene2D::setSharedObject(Qt3DRender::Quick::Scene2DSharedObjectPtr sharedObject)
194{
195 m_sharedObject = sharedObject;
196 if (!m_initialized)
197 initializeSharedObject();
198}
199
200void 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
225bool 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
250void 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
263void 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
347void 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
377bool 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
408void 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
416void 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
464QT_END_NAMESPACE
465
466#include "moc_scene2d_p.cpp"
467

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