Revision control

++ // Copyright (C) 2017 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include <Qt3DCore/private/qscene_p.h>
++ #include <Qt3DQuickScene2D/qscene2d.h>
++ #include <Qt3DRender/qpicktriangleevent.h>
++ #include <Qt3DRender/qobjectpicker.h>
++
++ #include <QtCore/qthread.h>
++ #include <QtCore/qatomic.h>
++ #include <QtGui/qevent.h>
++ #include <QtGui/rhi/qrhi.h>
++ #include <QtQuick/QQuickGraphicsDevice>
++ #include <QtQuick/QQuickRenderTarget>
++
++ #include <private/qscene2d_p.h>
++ #include <private/scene2d_p.h>
++ #include <private/scene2dmanager_p.h>
++ #include <private/scene2devent_p.h>
++ #include <private/texture_p.h>
++ #include <private/nodemanagers_p.h>
++ #include <private/resourceaccessor_p.h>
++ #include <private/attachmentpack_p.h>
++ #include <private/qt3dquickscene2d_logging_p.h>
++ #include <private/qbackendnode_p.h>
++ #include <private/qobjectpicker_p.h>
++ #include <private/qpickevent_p.h>
++ #include <private/qpicktriangleevent_p.h>
++ #include <private/entity_p.h>
++ #include <private/trianglesvisitor_p.h>
++
++ QT_BEGIN_NAMESPACE
++
++ #ifndef GL_DEPTH24_STENCIL8
++ #define GL_DEPTH24_STENCIL8 0x88F0
++ #endif
++
++
++ namespace Qt3DRender {
++
++ namespace Render {
++
++ namespace Quick {
++
++ using namespace Qt3DRender::Quick;
++
++ Q_GLOBAL_STATIC(QThread, renderThread)
++ Q_GLOBAL_STATIC(QAtomicInt, renderThreadClientCount)
++
++ RenderQmlEventHandler::RenderQmlEventHandler(Scene2D *node)
++ : QObject()
++ , m_node(node)
++ {
++ }
++
++ // Event handler for the RenderQmlToTexture::renderThread
++ bool RenderQmlEventHandler::event(QEvent *e)
++ {
++ switch (static_cast<Scene2DEvent::Type>(e->type())) {
++
++ case Scene2DEvent::Render: {
++ m_node->render();
++ return true;
++ }
++
++ case Scene2DEvent::Initialize: {
++ m_node->initializeRender();
++ return true;
++ }
++
++ case Scene2DEvent::Invalidate: {
++ m_node->invalidate();
++ return true;
++ }
++
++ case Scene2DEvent::Quit: {
++ m_node->cleanup();
++ return true;
++ }
++
++ default:
++ break;
++ }
++ return QObject::event(e);
++ }
++
++ Scene2D::Scene2D()
++ : Qt3DRender::Render::BackendNode(Qt3DCore::QBackendNode::ReadWrite)
++ , m_surface(nullptr)
++ , m_context(nullptr)
++ , m_shareContext(nullptr)
++ , m_rhi(nullptr)
++ , m_renderThread(nullptr)
++ , m_sharedObject(nullptr)
++ , m_initialized(false)
++ , m_renderInitialized(false)
++ , m_mouseEnabled(true)
++ , m_renderPolicy(Qt3DRender::Quick::QScene2D::Continuous)
++ {
++
++ }
++
++ Scene2D::~Scene2D()
++ {
++ for (auto connection: std::as_const(m_connections))
++ QObject::disconnect(connection);
++ m_connections.clear();
++ }
++
++ void Scene2D::setOutput(Qt3DCore::QNodeId outputId)
++ {
++ m_outputId = outputId;
++ }
++
++ void Scene2D::initializeSharedObject()
++ {
++ if (!m_initialized) {
++ // bail out if we're running autotests
++ if (!qgetenv("QT3D_SCENE2D_DISABLE_RENDERING").isEmpty())
++ return;
++
++ renderThreadClientCount->fetchAndAddAcquire(1);
++
++ renderThread->setObjectName(QStringLiteral("Scene2D::renderThread"));
++ m_renderThread = renderThread;
++ m_sharedObject->m_renderThread = m_renderThread;
++
++ // Create event handler for the render thread
++ m_sharedObject->m_renderObject = new RenderQmlEventHandler(this);
++ m_sharedObject->m_renderObject->moveToThread(m_sharedObject->m_renderThread);
++ if (!m_sharedObject->m_renderThread->isRunning())
++ m_sharedObject->m_renderThread->start();
++
++ // Notify main thread we have been initialized
++ QCoreApplication::postEvent(m_sharedObject->m_renderManager,
++ new Scene2DEvent(Scene2DEvent::Initialized));
++ // Initialize render thread
++ QCoreApplication::postEvent(m_sharedObject->m_renderObject,
++ new Scene2DEvent(Scene2DEvent::Initialize));
++
++ m_initialized = true;
++ }
++ }
++
++ void Scene2D::syncFromFrontEnd(const Qt3DCore::QNode *frontEnd, bool firstTime)
++ {
++ Qt3DRender::Render::BackendNode::syncFromFrontEnd(frontEnd, firstTime);
++ const QScene2D *node = qobject_cast<const QScene2D *>(frontEnd);
++ if (!node)
++ return;
++ const QScene2DPrivate *dnode = static_cast<const QScene2DPrivate *>(QScene2DPrivate::get(node));
++
++ if (m_mouseEnabled != node->isMouseEnabled()) {
++ m_mouseEnabled = node->isMouseEnabled();
++ if (!firstTime && m_mouseEnabled && m_cachedPickEvent) {
++ handlePickEvent(QEvent::MouseButtonPress, m_cachedPickEvent.data());
++ m_cachedPickEvent.clear();
++ }
++ }
++
++ m_renderPolicy = node->renderPolicy();
++ auto id = Qt3DCore::qIdForNode(node->output());
++ if (id != m_outputId)
++ setOutput(id);
++
++ if (m_mouseEnabled) {
++ auto ids = Qt3DCore::qIdsForNodes(node->entities());
++ std::sort(std::begin(ids), std::end(ids));
++ Qt3DCore::QNodeIdVector addedEntities;
++ Qt3DCore::QNodeIdVector removedEntities;
++ std::set_difference(std::begin(ids), std::end(ids),
++ std::begin(m_entities), std::end(m_entities),
++ std::inserter(addedEntities, addedEntities.end()));
++ std::set_difference(std::begin(m_entities), std::end(m_entities),
++ std::begin(ids), std::end(ids),
++ std::inserter(removedEntities, removedEntities.end()));
++ for (const auto &id: addedEntities) {
++ Qt3DCore::QEntity *entity = qobject_cast<Qt3DCore::QEntity *>(dnode->m_scene->lookupNode(id));
++ if (!entity)
++ return;
++
++ if (registerObjectPickerEvents(entity))
++ m_entities.push_back(id);
++ else
++ Qt3DCore::QNodePrivate::get(const_cast<Qt3DCore::QNode *>(frontEnd))->update();
++ }
++ for (const auto &id: removedEntities) {
++ m_entities.removeOne(id);
++ unregisterObjectPickerEvents(id);
++ }
++ std::sort(std::begin(m_entities), std::end(m_entities));
++ }
++
++ if (firstTime)
++ setSharedObject(dnode->m_renderManager->m_sharedObject);
++ }
++
++ void Scene2D::setSharedObject(Qt3DRender::Quick::Scene2DSharedObjectPtr sharedObject)
++ {
++ m_sharedObject = sharedObject;
++ if (!m_initialized)
++ initializeSharedObject();
++ }
++
++ void Scene2D::initializeRender()
++ {
++ if (!m_renderInitialized && m_sharedObject.data() != nullptr) {
++ m_shareContext = renderer()->shareContext();
++ if (!m_shareContext){
++ qCDebug(Qt3DRender::Quick::Scene2D) << Q_FUNC_INFO << "Renderer not initialized.";
++ QCoreApplication::postEvent(m_sharedObject->m_renderObject,
++ new Scene2DEvent(Scene2DEvent::Initialize));
++ return;
++ }
++
++ m_context = new QOpenGLContext();
++ m_context->setFormat(m_shareContext->format());
++ m_context->setShareContext(m_shareContext);
++ m_context->create();
++
++ m_surface = new QOffscreenSurface;
++ m_surface->setFormat(m_context->format());
++ m_surface->create();
++
++ const QSurfaceFormat format = m_sharedObject->m_quickWindow->requestedFormat();
++ QRhiGles2InitParams rhiParams;
++ rhiParams.format = format;
++ rhiParams.fallbackSurface = m_surface;
++ rhiParams.window = m_sharedObject->m_quickWindow;
++ QRhiGles2NativeHandles importDev;
++ importDev.context = m_context;
++ m_rhi = QRhi::create(QRhi::OpenGLES2, &rhiParams, { }, &importDev);
++
++ const QQuickGraphicsDevice graphicsDevice = QQuickGraphicsDevice::fromRhi(m_rhi);
++ m_sharedObject->m_quickWindow->setGraphicsDevice(graphicsDevice);
++ m_sharedObject->m_renderControl->initialize();
++
++ QCoreApplication::postEvent(m_sharedObject->m_renderManager,
++ new Scene2DEvent(Scene2DEvent::Prepare));
++ m_renderInitialized = true;
++ }
++ }
++
++ void Scene2D::syncRenderControl()
++ {
++ if (m_sharedObject->isSyncRequested()) {
++
++ m_sharedObject->clearSyncRequest();
++
++ m_sharedObject->m_renderControl->beginFrame();
++ m_sharedObject->m_renderControl->sync();
++ m_sharedObject->m_renderControl->endFrame();
++
++ // gui thread can now continue
++ m_sharedObject->wake();
++ }
++ }
++
++ void Scene2D::render()
++ {
++ if (m_initialized && m_renderInitialized && m_sharedObject.data() != nullptr) {
++ QMutexLocker lock(&m_sharedObject->m_mutex);
++
++ QOpenGLTexture *texture = nullptr;
++ const Qt3DRender::Render::Attachment *attachmentData = nullptr;
++ QMutex *textureLock = nullptr;
++
++ if (resourceAccessor()->accessResource(RenderBackendResourceAccessor::OutputAttachment,
++ m_outputId, (void**)&attachmentData, nullptr)) {
++ auto syncAndRequestRender = [this] {
++ syncRenderControl();
++ QCoreApplication::postEvent(m_sharedObject->m_renderObject,
++ new Scene2DEvent(Scene2DEvent::Render));
++ };
++ if (!resourceAccessor()->accessResource(RenderBackendResourceAccessor::OGLTextureWrite,
++ attachmentData->m_textureUuid,
++ (void **)&texture, &textureLock)) {
++ qCDebug(Qt3DRender::Quick::Scene2D) << Q_FUNC_INFO << "Texture not in use.";
++ // Need to call sync even if the texture is not in use
++ syncAndRequestRender();
++ return;
++ }
++ textureLock->lock();
++ if (!texture) {
++ textureLock->unlock();
++ qCDebug(Qt3DRender::Quick::Scene2D) << Q_FUNC_INFO << "Missing texture.";
++ syncAndRequestRender();
++ return;
++ }
++ const QSize textureSize = QSize(texture->width(), texture->height());
++ if (m_attachmentData.m_textureUuid != attachmentData->m_textureUuid
++ || m_attachmentData.m_point != attachmentData->m_point
++ || m_attachmentData.m_face != attachmentData->m_face
++ || m_attachmentData.m_layer != attachmentData->m_layer
++ || m_attachmentData.m_mipLevel != attachmentData->m_mipLevel
++ || m_textureSize != textureSize) {
++ m_textureSize = textureSize;
++ m_attachmentData = *attachmentData;
++ m_sharedObject->m_quickWindow->setRenderTarget(QQuickRenderTarget::fromOpenGLTexture(texture->textureId(), m_textureSize));
++ }
++ }
++
++ // Call disallow rendering while mutex is locked
++ if (m_renderPolicy == QScene2D::SingleShot)
++ m_sharedObject->disallowRender();
++
++ m_sharedObject->m_renderControl->beginFrame();
++
++ // Sync
++ if (m_sharedObject->isSyncRequested()) {
++
++ m_sharedObject->clearSyncRequest();
++
++ m_sharedObject->m_renderControl->sync();
++ }
++
++ // Render
++ m_sharedObject->m_renderControl->render();
++
++ m_sharedObject->m_renderControl->endFrame();
++
++ // Tell main thread we are done so it can begin cleanup if this is final frame
++ if (m_renderPolicy == QScene2D::SingleShot)
++ QCoreApplication::postEvent(m_sharedObject->m_renderManager,
++ new Scene2DEvent(Scene2DEvent::Rendered));
++
++ if (texture->isAutoMipMapGenerationEnabled()) {
++ m_context->makeCurrent(m_surface);
++ texture->generateMipMaps();
++ m_context->doneCurrent();
++ }
++ textureLock->unlock();
++
++ // gui thread can now continue
++ m_sharedObject->wake();
++ }
++ }
++
++ // this function gets called while the main thread is waiting
++ void Scene2D::invalidate()
++ {
++ if (m_renderInitialized && m_initialized) {
++ m_sharedObject->m_renderControl->invalidate();
++ m_sharedObject->m_quickWindow->setRenderTarget({ });
++ m_sharedObject->m_quickWindow->setGraphicsDevice({ });
++ }
++ if (m_sharedObject) {
++ // wake up the main thread
++ m_sharedObject->wake();
++ }
++ }
++
++ // this function gets called while the main thread is waiting
++ void Scene2D::cleanup()
++ {
++ if (m_renderInitialized && m_initialized) {
++ delete m_rhi;
++ m_rhi = nullptr;
++ delete m_context;
++ m_context = nullptr;
++ delete m_surface;
++ m_surface = nullptr;
++ m_renderInitialized = false;
++ }
++ if (m_initialized) {
++ delete m_sharedObject->m_renderObject;
++ m_sharedObject->m_renderObject = nullptr;
++ m_initialized = false;
++ }
++ if (m_sharedObject) {
++ // wake up the main thread
++ m_sharedObject->wake();
++ m_sharedObject = nullptr;
++ }
++ if (m_renderThread) {
++ renderThreadClientCount->fetchAndSubAcquire(1);
++ if (renderThreadClientCount->loadRelaxed() == 0)
++ renderThread->quit();
++ }
++ }
++
++ bool Scene2D::registerObjectPickerEvents(Qt3DCore::QEntity *qentity)
++ {
++ Entity *entity = nullptr;
++ if (!resourceAccessor()->accessResource(RenderBackendResourceAccessor::EntityHandle,
++ qentity->id(), (void**)&entity, nullptr)) {
++ qCWarning(Qt3DRender::Quick::Scene2D) << Q_FUNC_INFO
++ << "Entity not yet available in backend";
++ return false;
++ }
++
++ if (!entity->containsComponentsOfType<ObjectPicker>() ||
++ !entity->containsComponentsOfType<GeometryRenderer>()) {
++ qCWarning(Qt3DRender::Quick::Scene2D) << Q_FUNC_INFO
++ << "Entity does not contain required components: ObjectPicker and GeometryRenderer";
++ return false;
++ }
++
++ QObjectPicker *picker = qentity->componentsOfType<QObjectPicker>().front();
++ m_connections << QObject::connect(picker, &QObjectPicker::pressed, qentity, [this](Qt3DRender::QPickEvent *pick) {
++ handlePickEvent(QEvent::MouseButtonPress, pick);
++ });
++ m_connections << QObject::connect(picker, &QObjectPicker::released, qentity, [this](Qt3DRender::QPickEvent *pick) {
++ handlePickEvent(QEvent::MouseButtonRelease, pick);
++ });
++ m_connections << QObject::connect(picker, &QObjectPicker::moved, qentity, [this](Qt3DRender::QPickEvent *pick) {
++ handlePickEvent(QEvent::MouseMove, pick);
++ });
++
++ return true;
++ }
++
++ void Scene2D::unregisterObjectPickerEvents(Qt3DCore::QNodeId entityId)
++ {
++ Entity *entity = nullptr;
++ if (!resourceAccessor()->accessResource(RenderBackendResourceAccessor::EntityHandle,
++ entityId, (void**)&entity, nullptr))
++ return;
++ }
++
++ void Scene2D::handlePickEvent(int type, const Qt3DRender::QPickEvent *ev)
++ {
++ if (!isEnabled())
++ return;
++ if (m_mouseEnabled) {
++ const QPickTriangleEvent *pickTriangle = static_cast<const QPickTriangleEvent *>(ev);
++ Q_ASSERT(pickTriangle->entity());
++ Entity *entity = nullptr;
++ if (!resourceAccessor()->accessResource(RenderBackendResourceAccessor::EntityHandle,
++ Qt3DCore::qIdForNode(pickTriangle->entity()),
++ (void**)&entity, nullptr))
++ return;
++
++ CoordinateReader reader(renderer()->nodeManagers());
++ if (reader.setGeometry(entity->renderComponent<GeometryRenderer>(),
++ Qt3DCore::QAttribute::defaultTextureCoordinateAttributeName())) {
++ Vector4D c0 = reader.getCoordinate(pickTriangle->vertex1Index());
++ Vector4D c1 = reader.getCoordinate(pickTriangle->vertex2Index());
++ Vector4D c2 = reader.getCoordinate(pickTriangle->vertex3Index());
++ Vector4D ci = c0 * pickTriangle->uvw().x()
++ + c1 * pickTriangle->uvw().y() + c2 * pickTriangle->uvw().z();
++ ci.setW(1.0f);
++
++ const QSize size = m_sharedObject->m_quickWindow->size();
++ QPointF pos = QPointF(ci.x() * size.width(), (1.0f - ci.y()) * size.height());
++ QMouseEvent *mouseEvent
++ = new QMouseEvent(static_cast<QEvent::Type>(type),
++ pos, pos, pos,
++ static_cast<Qt::MouseButton>(pickTriangle->button()),
++ static_cast<Qt::MouseButtons>(pickTriangle->buttons()),
++ static_cast<Qt::KeyboardModifiers>(pickTriangle->modifiers()),
++ Qt::MouseEventSynthesizedByApplication);
++
++ QCoreApplication::postEvent(m_sharedObject->m_quickWindow, mouseEvent);
++ }
++ } else if (type == QEvent::MouseButtonPress) {
++ const QPickTriangleEvent *pickTriangle = static_cast<const QPickTriangleEvent *>(ev);
++ const QPickTriangleEventPrivate *dpick = QPickTriangleEventPrivate::get(pickTriangle);
++ m_cachedPickEvent = QPickEventPtr(dpick->clone());
++ } else {
++ m_cachedPickEvent.clear();
++ }
++ }
++
++ } // namespace Quick
++ } // namespace Render
++ } // namespace Qt3DRender
++
++ QT_END_NAMESPACE
++
++ #include "moc_scene2d_p.cpp"