/**************************************************************************** ** ** Copyright (C) 2019 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of Qt 3D Studio. ** ** $QT_BEGIN_LICENSE:LGPL3$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see http://www.qt.io/terms-conditions. For further ** information use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPLv3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or later as published by the Free ** Software Foundation and appearing in the file LICENSE.GPL included in ** the packaging of this file. Please review the following information to ** ensure the GNU General Public License version 2.0 requirements will be ** met: http://www.gnu.org/licenses/gpl-2.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE #ifndef GL_DEPTH24_STENCIL8 #define GL_DEPTH24_STENCIL8 0x88F0 #endif namespace Qt3DRender { using namespace Quick; namespace Dragon { Q_GLOBAL_STATIC(QThread, renderThread) Q_GLOBAL_STATIC(QAtomicInt, renderThreadClientCount) RenderQmlEventHandler::RenderQmlEventHandler(Scene2DData *node) : QObject() , m_node(node) { } // Event handler for the RenderQmlToTexture::renderThread bool RenderQmlEventHandler::event(QEvent *e) { switch (static_cast(e->type())) { case Scene2DEvent::Render: { m_node->render(); return true; } case Scene2DEvent::Initialize: { m_node->initializeRender(); return true; } case Scene2DEvent::Quit: { m_node->cleanup(); return true; } default: break; } return QObject::event(e); } Scene2D::Scene2D() : BackendNode() { m_data.reset(new Scene2DData()); } Scene2D::~Scene2D() { } void Scene2D::setOutput(Qt3DCore::QNodeId outputId) { m_data->m_outputId = outputId; } void Scene2D::initializeSharedObject() { if (!m_data->m_initialized && m_data->m_shareContext) { // bail out if we're running autotests if (!m_data->m_sharedObject->m_renderManager || m_data->m_sharedObject->m_renderManager->thread() == QThread::currentThread()) { return; } renderThreadClientCount->fetchAndAddAcquire(1); renderThread->setObjectName(QStringLiteral("Scene2D::renderThread")); m_data->m_renderThread = renderThread; m_data->m_sharedObject->m_renderThread = m_data->m_renderThread; // Create event handler for the render thread m_data->m_sharedObject->m_renderObject = new RenderQmlEventHandler(m_data.data()); m_data->m_sharedObject->m_renderObject->moveToThread(m_data->m_sharedObject->m_renderThread); if (!m_data->m_sharedObject->m_renderThread->isRunning()) m_data->m_sharedObject->m_renderThread->start(); // Notify main thread we have been initialized QCoreApplication::postEvent(m_data->m_sharedObject->m_renderManager, new Scene2DEvent(Scene2DEvent::Initialized)); // Initialize render thread QCoreApplication::postEvent(m_data->m_sharedObject->m_renderObject, new Scene2DEvent(Scene2DEvent::Initialize)); m_data->m_initialized = true; } } void Scene2D::initializeFromPeer(const Qt3DCore::QNodeCreatedChangeBasePtr &change) { const auto typedChange = qSharedPointerCast>(change); const auto &data = typedChange->data; m_data->m_renderPolicy = data.renderPolicy; setSharedObject(data.sharedObject); setOutput(data.output); m_data->m_mouseEnabled = data.mouseEnabled; } void Scene2D::sceneChangeEvent(const Qt3DCore::QSceneChangePtr &e) { switch (e->type()) { case Qt3DCore::PropertyUpdated: { Qt3DCore::QPropertyUpdatedChangePtr propertyChange = qSharedPointerCast(e); if (propertyChange->propertyName() == QByteArrayLiteral("renderPolicy")) { m_data->m_renderPolicy = propertyChange->value().value(); } else if (propertyChange->propertyName() == QByteArrayLiteral("output")) { Qt3DCore::QNodeId outputId = propertyChange->value().value(); setOutput(outputId); } // TODO add mouse related properties break; } // TODO add pick entities default: break; } BackendNode::sceneChangeEvent(e); } void Scene2D::setSharedObject(Qt3DRender::Scene2DSharedObjectPtr sharedObject) { m_data->m_sharedObject = sharedObject; if (!m_data->m_initialized) initializeSharedObject(); } void Scene2DData::initializeRender() { if (!m_renderInitialized && m_sharedObject.data() != nullptr) { if (!m_shareContext){ qDebug() << 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_context->makeCurrent(m_sharedObject->m_surface); m_sharedObject->m_renderControl->initialize(m_context); m_context->doneCurrent(); QCoreApplication::postEvent(m_sharedObject->m_renderManager, new Scene2DEvent(Scene2DEvent::Prepare)); m_renderInitialized = true; } } bool Scene2DData::updateFbo(QOpenGLTexture *texture, const QSize &size) { QOpenGLFunctions *gl = m_context->functions(); if (m_fbo == 0) { gl->glGenFramebuffers(1, &m_fbo); gl->glGenRenderbuffers(1, &m_rbo); } // TODO: Add another codepath when GL_DEPTH24_STENCIL8 is not supported gl->glBindRenderbuffer(GL_RENDERBUFFER, m_rbo); gl->glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, size.width(), size.height()); gl->glBindRenderbuffer(GL_RENDERBUFFER, 0); gl->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); gl->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture->textureId(), 0); gl->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_rbo); GLenum status = gl->glCheckFramebufferStatus(GL_FRAMEBUFFER); gl->glBindFramebuffer(GL_FRAMEBUFFER, 0); if (status != GL_FRAMEBUFFER_COMPLETE) return false; return true; } void Scene2DData::syncRenderControl() { if (m_sharedObject->isSyncRequested()) { m_sharedObject->clearSyncRequest(); m_sharedObject->m_renderControl->sync(); // gui thread can now continue m_sharedObject->wake(); } } void Scene2DData::render() { if (m_initialized && m_renderInitialized && m_sharedObject.data() != nullptr) { QMutexLocker lock(&m_sharedObject->m_mutex); QOpenGLTexture *texture = m_texture.data(); QMutex *textureLock = &m_mutex; m_context->makeCurrent(m_sharedObject->m_surface); if (!texture) { // Need to call sync even if the texture is not in use syncRenderControl(); m_context->doneCurrent(); return; } textureLock->lock(); if (m_outputDirty) { const QSize textureSize = QSize(texture->width(), texture->height()); updateFbo(texture, textureSize); m_textureSize = textureSize; } if (m_fbo != m_sharedObject->m_quickWindow->renderTargetId()) m_sharedObject->m_quickWindow->setRenderTarget(m_fbo, m_textureSize); // Call disallow rendering while mutex is locked if (m_renderPolicy == QScene2D::SingleShot) m_sharedObject->disallowRender(); // Sync if (m_sharedObject->isSyncRequested()) { m_sharedObject->clearSyncRequest(); m_sharedObject->m_renderControl->sync(); } // Render m_sharedObject->m_renderControl->render(); // 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)); m_sharedObject->m_quickWindow->resetOpenGLState(); m_context->functions()->glFlush(); if (texture->isAutoMipMapGenerationEnabled()) texture->generateMipMaps(); textureLock->unlock(); m_context->doneCurrent(); // gui thread can now continue m_sharedObject->wake(); } } // this function gets called while the main thread is waiting void Scene2DData::cleanup() { if (m_renderInitialized && m_initialized) { m_context->makeCurrent(m_sharedObject->m_surface); m_sharedObject->m_renderControl->invalidate(); m_context->functions()->glDeleteFramebuffers(1, &m_fbo); m_context->functions()->glDeleteRenderbuffers(1, &m_rbo); m_context->doneCurrent(); m_renderInitialized = false; } if (m_initialized) { delete m_sharedObject->m_renderObject; m_sharedObject->m_renderObject = nullptr; delete m_context; m_context = 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->load() == 0) renderThread->quit(); } m_texture.reset(); } ValueContainer updateScene2Ds(ValueContainer scene2dstate, const ValueContainer &scene2ds, const QHash > *loadedTextures, const ValueContainer &outputs, QOpenGLContext *shareContext) { scene2dstate.reset(); const auto &keys = scene2ds.keys(); for (auto key : keys) { Scene2D scene2d = *scene2ds[key]; if (!scene2d.m_data->m_initialized) { scene2d.m_data->m_shareContext = shareContext; scene2d.setSharedObject(scene2d.m_data->m_sharedObject); RenderTargetOutput output = *outputs[scene2d.m_data->m_outputId]; Attachment attachment; attachment.m_name = output.name; attachment.m_mipLevel = output.mipLevel; attachment.m_layer = output.layer; attachment.m_textureUuid = output.textureUuid; attachment.m_point = output.point; scene2d.m_data->m_attachmentData = attachment; if (loadedTextures->contains(attachment.m_textureUuid)) scene2d.m_data->m_texture = (*loadedTextures)[attachment.m_textureUuid]; scene2dstate[key] = Scene2DState(key, attachment.m_textureUuid, nullptr); continue; } if (outputs.changes().contains(Change{Change::Action::Modified, scene2d.m_data->m_outputId})) { RenderTargetOutput output = *outputs[scene2d.m_data->m_outputId]; Attachment attachment; attachment.m_name = output.name; attachment.m_mipLevel = output.mipLevel; attachment.m_layer = output.layer; attachment.m_textureUuid = output.textureUuid; attachment.m_point = output.point; // reset texture if changed if (scene2d.m_data->m_attachmentData.m_textureUuid != attachment.m_textureUuid) scene2d.m_data->m_texture.reset(); scene2d.m_data->m_attachmentData = attachment; scene2d.m_data->m_outputDirty = true; } if (!scene2d.m_data->m_texture) { if (loadedTextures->contains(scene2d.m_data->m_attachmentData.m_textureUuid)) { scene2d.m_data->m_texture = (*loadedTextures)[scene2d.m_data->m_attachmentData.m_textureUuid]; } scene2dstate[key] = Scene2DState(key, scene2d.m_data->m_attachmentData.m_textureUuid, nullptr); continue; } scene2dstate[key] = Scene2DState(key, scene2d.m_data->m_attachmentData.m_textureUuid, &scene2d.m_data->m_mutex); } return scene2dstate; } } // namespace Dragon } // namespace Qt3DRender QT_END_NAMESPACE