summaryrefslogtreecommitdiffstats
path: root/src/qmlstreamer/q3dsqmlstreamrenderer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qmlstreamer/q3dsqmlstreamrenderer.cpp')
-rw-r--r--src/qmlstreamer/q3dsqmlstreamrenderer.cpp483
1 files changed, 483 insertions, 0 deletions
diff --git a/src/qmlstreamer/q3dsqmlstreamrenderer.cpp b/src/qmlstreamer/q3dsqmlstreamrenderer.cpp
new file mode 100644
index 0000000..f1879a6
--- /dev/null
+++ b/src/qmlstreamer/q3dsqmlstreamrenderer.cpp
@@ -0,0 +1,483 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "q3dsqmlstreamrenderer.h"
+
+#include <QCoreApplication>
+#include <qdebug.h>
+#include <QOpenGLContext>
+#include <QSurface>
+#include <QPainter>
+#include <QOpenGLFunctions>
+#include <QWindow>
+#include <QQuickWindow>
+#include <QQuickRenderControl>
+#include <QtQml/QQmlEngine>
+#include <QtQml/QQmlContext>
+#include <QQuickWindow>
+#include <QQuickItem>
+#include <QThread>
+#include <QOffscreenSurface>
+#include <QOpenGLFramebufferObject>
+#include <QOpenGLShaderProgram>
+#include <QOpenGLBuffer>
+#include <QOpenGLVertexArrayObject>
+
+Q_GLOBAL_STATIC(QThread, renderThread)
+Q_GLOBAL_STATIC(QAtomicInt, renderThreadClientCount)
+
+#define RenderEvent(name, value) \
+ class name : public QEvent \
+ { \
+ public: \
+ name() : QEvent(_type) {} \
+ static const QEvent::Type _type; \
+ }; \
+ const QEvent::Type name::_type = QEvent::Type(value);
+
+RenderEvent(InitializeRender, QEvent::User + 1)
+RenderEvent(RequestUpdate, QEvent::User + 2)
+RenderEvent(InitializeRenderThread, QEvent::User + 3)
+RenderEvent(PrepareRender, QEvent::User + 4)
+RenderEvent(Cleanup, QEvent::User + 5)
+
+#define EventType(e) (e::_type)
+
+class RenderControl : public QQuickRenderControl
+{
+public:
+ RenderControl(QWindow *w)
+ : m_window(w)
+ {
+ }
+
+ QWindow *renderWindow(QPoint *offset) Q_DECL_OVERRIDE
+ {
+ if (offset)
+ *offset = QPoint(0, 0);
+ return m_window;
+ }
+
+private:
+ QWindow *m_window;
+};
+
+Q3DSQmlStreamEventHandler::Q3DSQmlStreamEventHandler(Q3DSQmlStreamRenderer *renderer)
+ : m_renderer(renderer)
+{
+
+}
+
+bool Q3DSQmlStreamEventHandler::event(QEvent *e)
+{
+ switch (e->type()) {
+
+ case EventType(RequestUpdate): {
+ m_renderer->renderTexture();
+ return true;
+ }
+
+ case EventType(InitializeRenderThread): {
+ m_renderer->initializeRender();
+ return true;
+ }
+
+ case EventType(Cleanup): {
+ m_renderer->cleanup();
+ return true;
+ }
+
+ default:
+ break;
+ }
+ return QObject::event(e);
+}
+
+Q3DSQmlStreamRenderer::Q3DSQmlStreamRenderer()
+ : m_renderControl(nullptr)
+ , m_quickWindow(nullptr)
+ , m_rootItem(nullptr)
+ , m_frameBuffer(nullptr)
+ , m_program(nullptr)
+ , m_vao(nullptr)
+ , m_vertices(nullptr)
+ , m_context(nullptr)
+ , m_offscreenSurface(nullptr)
+ , m_renderObject(nullptr)
+ , m_renderThread(nullptr)
+ , m_requestUpdate(false)
+ , m_initialized(false)
+ , m_prepared(false)
+ , m_update(false)
+ , m_delayedUpdateRequest(false)
+{
+ renderThreadClientCount->fetchAndAddAcquire(1);
+
+ QSurfaceFormat format = QSurfaceFormat::defaultFormat();
+ if (QOpenGLContext::currentContext())
+ format = QOpenGLContext::currentContext()->format();
+ else {
+ format.setDepthBufferSize(24);
+ format.setStencilBufferSize(8);
+ }
+
+ m_offscreenSurface = new QOffscreenSurface;
+ m_offscreenSurface->setFormat(format);
+ m_offscreenSurface->create();
+
+ m_renderControl = new RenderControl(nullptr);
+
+ connect(m_renderControl, &QQuickRenderControl::renderRequested,
+ this, &Q3DSQmlStreamRenderer::requestUpdate);
+ connect(m_renderControl, &QQuickRenderControl::sceneChanged,
+ this, &Q3DSQmlStreamRenderer::requestUpdate);
+
+ m_quickWindow = new QQuickWindow(m_renderControl);
+ m_quickWindow->setClearBeforeRendering(true);
+ m_quickWindow->setColor(Qt::transparent);
+
+ m_renderObject = new Q3DSQmlStreamEventHandler(this);
+ renderThread->setObjectName(QStringLiteral("Qt3DSQmlStreamRenderer::renderThread"));
+ m_renderThread = renderThread;
+
+ m_renderObject->moveToThread(m_renderThread);
+ if (!m_renderThread->isRunning())
+ m_renderThread->start();
+}
+
+Q3DSQmlStreamRenderer::~Q3DSQmlStreamRenderer()
+{
+ QMutexLocker lock(&m_mutex);
+ QCoreApplication::postEvent(m_renderObject, new Cleanup());
+ m_waitCondition.wait(&m_mutex);
+
+ delete m_offscreenSurface;
+ delete m_renderControl;
+ delete m_quickWindow;
+ delete m_renderObject;
+
+ renderThreadClientCount->fetchAndSubAcquire(1);
+ if (renderThreadClientCount->load() == 0)
+ renderThread->quit();
+}
+
+void Q3DSQmlStreamRenderer::cleanup()
+{
+ if (m_context)
+ m_context->makeCurrent(m_offscreenSurface);
+ if (m_renderControl)
+ m_renderControl->invalidate();
+
+ delete m_frameBuffer;
+ delete m_program;
+ delete m_vertices;
+
+ if (m_context)
+ m_context->doneCurrent();
+ delete m_context;
+
+ if (m_renderObject)
+ m_renderObject->moveToThread(QCoreApplication::instance()->thread());
+ m_waitCondition.wakeOne();
+}
+
+bool Q3DSQmlStreamRenderer::initialize(QOpenGLContext *context, QSurface *surface)
+{
+ Q_UNUSED(surface);
+ QMutexLocker lock(&m_renderMutex);
+ if (!m_context) {
+ m_context = new QOpenGLContext();
+ m_context->setShareContext(context);
+ m_context->setFormat(context->format());
+ m_context->create();
+ m_context->moveToThread(m_renderThread);
+ }
+
+ if (!m_rootItem) {
+ return true;
+ }
+
+ if (m_initialized) {
+ Q_ASSERT(QOpenGLContext::areSharing(context, m_context));
+ return true;
+ }
+
+ m_rootItem->setParentItem(m_quickWindow->contentItem());
+
+ updateSizes();
+
+ m_initialized = true;
+ // Initialize render thread
+ QCoreApplication::postEvent(m_renderObject,
+ new InitializeRenderThread());
+ return true;
+}
+void Q3DSQmlStreamRenderer::initializeFboCopy()
+{
+ m_program = new QOpenGLShaderProgram;
+ if (m_context->format().renderableType() == QSurfaceFormat::OpenGLES) {
+ static const char *vsSource =
+ "attribute highp vec4 pos;\n"
+ "attribute highp vec2 tc;\n"
+ "varying lowp vec2 texcoord;\n"
+ "void main() {\n"
+ " texcoord = tc;\n"
+ " gl_Position = pos;\n"
+ "}\n";
+ static const char *fsSource =
+ "varying highp vec2 texcoord;\n"
+ "uniform sampler2D sampler;\n"
+ "void main() {\n"
+ " gl_FragColor = texture2D(sampler, texcoord);\n"
+ "}\n";
+ m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vsSource);
+ m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fsSource);
+ } else {
+ static const char *vsSource =
+ "#version 150 core\n"
+ "in vec4 pos;\n"
+ "in vec2 tc;\n"
+ "out vec2 texcoord;\n"
+ "void main() {\n"
+ " texcoord = tc;\n"
+ " gl_Position = pos;\n"
+ "}\n";
+ static const char *fsSource =
+ "#version 150 core\n"
+ "in vec2 texcoord;\n"
+ "out vec4 fragColor;\n"
+ "uniform sampler2D sampler;\n"
+ "void main() {\n"
+ " fragColor = texture(sampler, texcoord);\n"
+ "}\n";
+ m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vsSource);
+ m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fsSource);
+ }
+
+ m_program->bindAttributeLocation("pos", 0);
+ m_program->bindAttributeLocation("tc", 1);
+
+ if (!m_program->link()) {
+ QByteArray logData(m_program->log().toLocal8Bit());
+ const char *log = logData.data();
+ qFatal("Failed to create shader program: %s", log);
+ }
+
+ m_vertices = new QOpenGLBuffer;
+ m_vertices->create();
+ m_vertices->bind();
+
+ static const float vertices[] =
+ {
+ -1, -1, 0, 1, 0, 0,
+ 1, -1, 0, 1, 1, 0,
+ 1, 1, 0, 1, 1, 1,
+ -1, -1, 0, 1, 0, 0,
+ 1, 1, 0, 1, 1, 1,
+ -1, 1, 0, 1, 0, 1,
+ };
+
+ m_vertices->allocate(vertices, sizeof(vertices));
+ m_vertices->release();
+}
+
+void Q3DSQmlStreamRenderer::initializeRender()
+{
+ QMutexLocker lock(&m_renderMutex);
+ m_context->makeCurrent(m_offscreenSurface);
+ m_renderControl->initialize(m_context);
+ m_context->doneCurrent();
+ QCoreApplication::postEvent(this, new PrepareRender());
+}
+
+QSize Q3DSQmlStreamRenderer::getDesiredSize()
+{
+ return m_size;
+}
+
+E_TEXTURE_FORMAT Q3DSQmlStreamRenderer::getDesiredFormat()
+{
+ return E_TEXTURE_RGBA8;
+}
+
+bool Q3DSQmlStreamRenderer::isUpdateRequested()
+{
+ return m_update;
+}
+
+// Called by stream renderer client when it stops using the renderer
+// TODO: We are sharing the context so stop using it.
+void Q3DSQmlStreamRenderer::uninitialize()
+{
+}
+
+void Q3DSQmlStreamRenderer::setItem(QQuickItem *item)
+{
+ if (item && m_rootItem && m_initialized) {
+ QMutexLocker lock(&m_renderMutex);
+ m_rootItem->setParentItem(nullptr);
+ m_rootItem = item;
+ m_rootItem->setParentItem(m_quickWindow->contentItem());
+ updateSizes();
+ } else {
+ if (item && m_rootItem != item) {
+ m_rootItem = item;
+
+ if (m_context)
+ initialize(m_context, nullptr);
+ }
+ }
+}
+
+bool Q3DSQmlStreamRenderer::event(QEvent *event)
+{
+ switch (event->type()) {
+
+ case EventType(PrepareRender): {
+
+ m_renderControl->prepareThread(m_renderThread);
+ m_prepared = true;
+
+ if (m_delayedUpdateRequest) {
+ QCoreApplication::postEvent(this, new RequestUpdate());
+ m_delayedUpdateRequest = false;
+ }
+
+ return true;
+ }
+
+ case EventType(RequestUpdate): {
+
+ m_renderControl->polishItems();
+ QMutexLocker lock(&m_mutex);
+ QCoreApplication::postEvent(m_renderObject, new RequestUpdate());
+ m_waitCondition.wait(&m_mutex);
+ return true;
+ }
+
+ default:
+ break;
+ }
+
+ return QObject::event(event);
+}
+
+void Q3DSQmlStreamRenderer::updateSizes()
+{
+ if (m_rootItem->width() > 0 && m_rootItem->height() > 0) {
+ m_size = QSize(m_rootItem->width(), m_rootItem->height());
+ } else {
+ m_rootItem->setWidth(256);
+ m_rootItem->setHeight(256);
+ }
+
+ m_quickWindow->setGeometry(0, 0, m_size.width(), m_size.height());
+}
+
+void Q3DSQmlStreamRenderer::requestUpdate()
+{
+ if (!m_requestUpdate) {
+ m_requestUpdate = true;
+ if (m_initialized)
+ QCoreApplication::postEvent(this, new RequestUpdate());
+ else
+ m_delayedUpdateRequest = true;
+ }
+}
+
+void Q3DSQmlStreamRenderer::renderTexture()
+{
+ QMutexLocker lock(&m_renderMutex);
+ m_context->makeCurrent(m_offscreenSurface);
+
+ if (!m_frameBuffer || m_frameBuffer->size() != m_size) {
+ if (m_frameBuffer)
+ delete m_frameBuffer;
+
+ m_frameBuffer = new QOpenGLFramebufferObject(m_size);
+ m_frameBuffer->setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
+ m_quickWindow->setRenderTarget(m_frameBuffer);
+ }
+
+ {
+ QMutexLocker lock(&m_mutex);
+ if (m_requestUpdate) {
+ m_requestUpdate = false;
+ m_renderControl->sync();
+ m_renderControl->render();
+ m_waitCondition.wakeOne();
+ }
+ }
+
+ m_context->functions()->glFlush();
+ m_quickWindow->resetOpenGLState();
+ m_context->doneCurrent();
+ m_update = true;
+}
+
+void Q3DSQmlStreamRenderer::render()
+{
+ QMutexLocker lock(&m_renderMutex);
+ if (m_update && m_initialized) {
+ QOpenGLContext *context = QOpenGLContext::currentContext();
+ QOpenGLFunctions *func = context->functions();
+ GLuint texture = m_frameBuffer->texture();
+ func->glDisable(GL_DEPTH_TEST);
+ func->glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+
+ if (!m_program)
+ initializeFboCopy();
+
+ m_program->bind();
+
+ if (!m_vao) {
+ m_vao = new QOpenGLVertexArrayObject;
+ m_vao->create();
+ m_vao->bind();
+ m_vertices->bind();
+
+ m_program->enableAttributeArray(0);
+ m_program->enableAttributeArray(1);
+ func->glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 6 * sizeof(float), 0);
+ func->glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 6 * sizeof(float),
+ (const void *)(4 * sizeof(GLfloat)));
+ m_vertices->release();
+ } else {
+ m_vao->bind();
+ }
+
+ func->glActiveTexture(GL_TEXTURE0);
+ func->glBindTexture(GL_TEXTURE_2D, texture);
+ func->glDrawArrays(GL_TRIANGLES, 0, 6);
+ func->glEnable(GL_DEPTH_TEST);
+
+ m_program->release();
+ m_vao->release();
+ }
+}