Commit b346a79b authored by Akhil K Gangadharan's avatar Akhil K Gangadharan
Browse files

Enable multithreaded rendering

src: Render using a separate thread

* Move the OGL Context and render on a separate thread
* Move core rendering code to CoreRenderer class
* Use CoreRenderer in QmlRenderer to render

cli: Account for structural changes made in src

* Make changes to accomodate changes made to src for threading
* Temporarily allow only filename to be the changeable attribute from the user

test: Account for structural changes made in src

* Make changes to accomodate changes made in src for threading
parent 72a6489b
......@@ -82,8 +82,10 @@ int main(int argc, char *argv[])
QSize frameSize(frameSizeList.at(0).toInt(), frameSizeList.at(1).toInt());
bool ifSingleFrame = parser.value(singleframe)=="true"? true:false ;
QmlRender w;
w.renderer->initialiseRenderParams(QUrl(parser.value(file)), ifSingleFrame, parser.value(frametime).toLongLong(), outputName, parser.value(odir), parser.value(format), frameSize, parser.value(devicePRatio).toLongLong(), parser.value(duration).toInt(), parser.value(fps).toInt());
w.renderer->renderQml();
// TODO : Extend functionality
QmlRender w(QString(parser.value(file)));
QImage img = w.renderer->render(720, 596, QImage::Format_ARGB32);
img.save(parser.value(odir));
return app.exec();
}
......@@ -21,14 +21,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "qmlrender.h"
QmlRender::QmlRender(QObject *parent)
QmlRender::QmlRender(QString filename, QObject *parent)
: QObject(parent)
, m_filename(filename)
{
renderer = std::make_unique<QmlRenderer>(new QmlRenderer);
connect(renderer.get(), &QmlRenderer::terminate, this, [] (){
qDebug() << "Done.";
exit(0);
});
renderer = std::make_unique<QmlRenderer>(filename);
}
QmlRender::~QmlRender()
......
......@@ -30,10 +30,13 @@ class QmlRender : public QObject
Q_OBJECT
public:
explicit QmlRender(QObject *parent = nullptr);
explicit QmlRender(QString filename, QObject *parent = nullptr);
~QmlRender();
std::unique_ptr<QmlRenderer> renderer;
private:
QString m_filename;
};
#endif // QMLRENDER_H
......@@ -2,7 +2,7 @@ TEMPLATE = lib
TARGET = QmlRenderer
QT = core qml opengl quick
DEFINES += QMLRENDERER_LIBRARY
SOURCES += qmlrenderer.cpp qmlanimationdriver.cpp
HEADERS += qmlrenderer.h qmlrenderer_global.h qmlanimationdriver.h
SOURCES += qmlrenderer.cpp qmlanimationdriver.cpp corerenderer.cpp
HEADERS += qmlrenderer.h qmlrenderer_global.h qmlanimationdriver.h corerenderer.h
win32: DESTDIR = ../bin
else: DESTDIR = ../lib
\ No newline at end of file
else: DESTDIR = ../lib
/*
Copyright (C) 2019 Akhil K Gangadharan <helloimakhil@gmail.com>
This file is part of Kdenlive. See www.kdenlive.org.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License or (at your option) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "corerenderer.h"
#include "qmlanimationdriver.h"
#include <memory>
#include <QCoreApplication>
#include <QOpenGLContext>
#include <QOffscreenSurface>
#include <QQuickRenderControl>
#include <QOpenGLFramebufferObject>
#include <QThread>
#include <QOpenGLFunctions>
CoreRenderer::CoreRenderer(QObject *parent)
: QObject(parent),
m_quit(false)
{
}
CoreRenderer::~CoreRenderer() = default;
void CoreRenderer::requestInit()
{
QCoreApplication::postEvent(this, new QEvent(INIT));
}
void CoreRenderer::requestRender()
{
QCoreApplication::postEvent(this, new QEvent(RENDER));
}
void CoreRenderer::requestResize()
{
QCoreApplication::postEvent(this, new QEvent(RESIZE));
}
void CoreRenderer::requestStop()
{
QCoreApplication::postEvent(this, new QEvent(STOP));
}
bool CoreRenderer::event(QEvent *e)
{
QMutexLocker lock(&m_mutex);
switch (int(e->type())) {
case INIT:
init();
return true;
case RENDER:
render(&lock);
return true;
case RESIZE:
// TODO
return true;
case STOP:
cleanup();
return true;
default:
return QObject::event(e);
}
}
void CoreRenderer::init()
{
m_context->makeCurrent(m_offscreenSurface.get());
m_renderControl->initialize(m_context.get());
Q_ASSERT(m_fps>0);
m_animationDriver = std::make_unique<QmlAnimationDriver>(1000/m_fps);
m_animationDriver->install();
}
void CoreRenderer::cleanup()
{
m_context->makeCurrent(m_offscreenSurface.get());
m_renderControl->invalidate();
m_context->doneCurrent();
m_context->moveToThread(QCoreApplication::instance()->thread());
m_cond.wakeOne();
}
void CoreRenderer::ensureFbo()
{
Q_ASSERT(!m_size.isEmpty());
Q_ASSERT(m_dpr != 0.0);
if (m_fbo && m_fbo->size() != m_size * m_dpr) {
m_fbo.reset();
}
if (!m_fbo) {
m_fbo = std::make_unique<QOpenGLFramebufferObject>(m_size * m_dpr, QOpenGLFramebufferObject::CombinedDepthStencil);
m_quickWindow->setRenderTarget(m_fbo.get());
Q_ASSERT(m_quickWindow->isSceneGraphInitialized());
}
}
void CoreRenderer::render(QMutexLocker *lock)
{
// Q_ASSERT(QThread::currentThread() != m_window->thread());
if (!m_context->makeCurrent(m_offscreenSurface.get())) {
qWarning("Failed to make context current on render thread");
return;
}
ensureFbo();
// Synchronization and rendering happens here on the render thread
m_renderControl->sync();
// Meanwhile on this thread continue with the actual rendering (into the FBO first).
m_renderControl->render();
m_context->functions()->glFlush();
QMutexLocker quitLock(&m_quitMutex);
image = m_fbo->toImage();
image.convertTo(m_format);
// The main thread can now continue
m_cond.wakeOne();
lock->unlock();
m_animationDriver->advance();
}
void CoreRenderer::aboutToQuit()
{
QMutexLocker lock(&m_quitMutex);
m_quit = true;
}
/*
Copyright (C) 2019 Akhil K Gangadharan <helloimakhil@gmail.com>
This file is part of Kdenlive. See www.kdenlive.org.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License or (at your option) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CORERENDERER_H
#define CORERENDERER_H
#include <memory>
#include <QObject>
#include <QMutex>
#include <QQuickWindow>
#include <QWaitCondition>
class QOpenGLContext;
class QOpenGLFramebufferObject;
class QOffscreenSurface;
class QQuickRenderControl;
class QQmlComponent;
class QQuickItem;
class QmlAnimationDriver;
static const QEvent::Type INIT = QEvent::Type(QEvent::User + 1);
static const QEvent::Type RENDER = QEvent::Type(QEvent::User + 2);
static const QEvent::Type RESIZE = QEvent::Type(QEvent::User + 3);
static const QEvent::Type STOP = QEvent::Type(QEvent::User + 4);
static const QEvent::Type UPDATE = QEvent::Type(QEvent::User + 5);
class CoreRenderer : public QObject
{
Q_OBJECT
public:
explicit CoreRenderer(QObject *parent = nullptr);
~CoreRenderer() override;
void requestInit();
void requestRender();
void requestResize();
void requestStop();
QWaitCondition *cond() { return &m_cond; }
QMutex *mutex() { return &m_mutex; }
void setContext(std::shared_ptr<QOpenGLContext> context){ m_context = context; }
void setSurface(std::shared_ptr<QOffscreenSurface> surface) { m_offscreenSurface = surface; }
void setQuickWindow(std::shared_ptr<QQuickWindow> window) { m_quickWindow = window; }
void setRenderControl(std::shared_ptr<QQuickRenderControl> control) { m_renderControl = control; }
void setSize(QSize s){ m_size = s; }
void setDPR(qreal value){ m_dpr = value; }
void setFPS(int value){ m_fps = value;}
void setFormat( QImage::Format f){ m_format = f; }
void aboutToQuit();
QImage image;
private:
bool event(QEvent *e) override;
void init();
void cleanup();
void ensureFbo();
void render(QMutexLocker *lock);
QWaitCondition m_cond;
QMutex m_mutex;
std::shared_ptr<QOpenGLContext> m_context;
std::shared_ptr<QOffscreenSurface> m_offscreenSurface;
std::shared_ptr<QQuickRenderControl> m_renderControl;
std::shared_ptr<QQuickWindow> m_quickWindow;
std::unique_ptr<QOpenGLFramebufferObject> m_fbo;
std::unique_ptr<QmlAnimationDriver> m_animationDriver;
QImage::Format m_format;
QSize m_size;
qreal m_dpr;
QMutex m_quitMutex;
int m_fps;
bool m_quit;
};
#endif // CORERENDERER_H
......@@ -40,231 +40,58 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "qmlrenderer.h"
#include "qmlanimationdriver.h"
#include "corerenderer.h"
QmlRenderer::QmlRenderer(QString qmlFileUrlString, qint64 frameTime, qreal devicePixelRatio, int durationMs, int fps, QObject *parent)
: QObject(parent)
, m_status(NotRunning)
, m_framesCount(0)
, m_currentFrame(0)
, m_futureFinishedCounter(0)
, m_qmlFileUrl(qmlFileUrlString)
, m_frameTime(frameTime)
, m_dpr(devicePixelRatio)
, m_duration(durationMs)
, m_fps(fps)
, m_frameTime(frameTime)
, m_ifProducer(true)
{
m_selectFrame = static_cast<int>(m_frameTime / ((1000/m_fps)));
m_framesCount = (m_duration / 1000 )* m_fps;
}
QmlRenderer::QmlRenderer(QObject *parent)
: QObject(parent)
, m_status(NotRunning)
, m_framesCount(0)
, m_currentFrame(0)
, m_futureFinishedCounter(0)
, m_ifProducer(false)
{
QSurfaceFormat format;
format.setDepthBufferSize(16);
format.setStencilBufferSize(8);
m_context = std::make_unique<QOpenGLContext>();
m_context->setFormat(format);
Q_ASSERT(format.depthBufferSize() == (m_context->format()).depthBufferSize());
Q_ASSERT(format.stencilBufferSize() == (m_context->format()).stencilBufferSize());
m_context->create();
Q_ASSERT(m_context->isValid());
m_offscreenSurface = std::make_unique<QOffscreenSurface>();
m_offscreenSurface->setFormat(m_context->format());
m_offscreenSurface->create();
Q_ASSERT(m_offscreenSurface->isValid());
m_renderControl = std::make_unique<QQuickRenderControl>(this);
Q_ASSERT(m_renderControl != nullptr);
QQmlEngine::setObjectOwnership(m_renderControl.get(), QQmlEngine::CppOwnership);
m_quickWindow = std::make_unique<QQuickWindow>(m_renderControl.get());
Q_ASSERT(m_quickWindow != nullptr);
m_qmlEngine = std::make_unique<QQmlEngine>();
if (!m_qmlEngine->incubationController()) {
m_qmlEngine->setIncubationController(m_quickWindow->incubationController());
}
m_context->makeCurrent(m_offscreenSurface.get());
//load Component
Q_ASSERT(m_context->currentContext()!= nullptr);
m_renderControl->initialize(m_context.get());
m_selectFrame = static_cast<int>(m_frameTime / ((1000/m_fps)));
m_framesCount = (m_duration / 1000 )* m_fps;
connect(m_quickWindow.get(), SIGNAL(sceneGraphError(QQuickWindow::SceneGraphError, const QString)), this, SLOT(displaySceneGraphError(QQuickWindow::SceneGraphError, const QString)));
connect(m_qmlEngine.get(), SIGNAL(warnings(QList<QQmlError>)), this, SLOT(displayQmlError(QList<QQmlError>)));
renderFlag = false;
initialiseContext();
}
QmlRenderer::~QmlRenderer()
{
m_context->makeCurrent(m_offscreenSurface.get());
m_context->doneCurrent();
}
void QmlRenderer::displaySceneGraphError(QQuickWindow::SceneGraphError error, const QString &message)
{
qDebug() << "ERROR : QML Scene Graph " << error << message;
}
void QmlRenderer::displayQmlError(QList<QQmlError> warnings)
{
foreach(const QQmlError& warning, warnings) {
qDebug() << "QML ERROR: " << warning;
}
}
int QmlRenderer::getStatus()
{
return m_status;
}
int QmlRenderer::getCalculatedFramesCount()
{
return m_framesCount;
}
int QmlRenderer::getActualFramesCount()
{
return m_currentFrame;
}
int QmlRenderer::getSelectFrame()
{
return m_selectFrame;
}
bool QmlRenderer::getSceneGraphStatus()
{
if(m_status == Initialised) {
return m_quickWindow->isSceneGraphInitialized();
}
return false;
}
bool QmlRenderer::getAnimationDriverStatus()
{
if(m_status == Initialised && m_animationDriver) {
return m_animationDriver->isRunning();
}
return false;
}
bool QmlRenderer::getfboStatus()
{
if(m_status == Initialised && m_fbo) {
return m_fbo->isBound();
}
return false;
}
int QmlRenderer::getFutureCount()
{
return m_futures.count();
}
void QmlRenderer::getAllParams()
{
qDebug() << "frames" << m_fps;
qDebug() << "file" << m_qmlFileText <<" : " << m_qmlFileUrl;
qDebug() << "odir" << m_outputDirectory;
qDebug() << "outputfile name" << m_outputName;
qDebug() << "format" << m_outputFormat;
qDebug() << "durration" << m_duration;
qDebug() << "single frame" << m_isSingleFrame;
qDebug() << "actual frames " << m_currentFrame;
qDebug() << "calculated frames " << m_framesCount;
qDebug() << "frame time " << m_frameTime;
qDebug() << "size : " << m_size;
}
bool QmlRenderer::isRunning()
{
return m_status == Running;
}
void QmlRenderer::initImageParams(int width, int height, const QImage::Format imageFormat)
{
//TODO: write tests
if (m_status == NotRunning) {
initialiseContext();
m_size = QSize(width, height);
m_ImageFormat = imageFormat;
loadInput();
m_status = Initialised;
}
else if (width == m_size.width() && height == m_size.height() && imageFormat == m_ImageFormat ) {
renderFlag = true; // meaning we just advance the animation driver by calling renderNext() since parameters are the same
m_corerenderer->mutex()->lock();
m_corerenderer->requestStop();
m_corerenderer->cond()->wait(m_corerenderer->mutex());
m_corerenderer->mutex()->unlock();
}
else if ( width != m_size.width() || height != m_size.height() || imageFormat != m_ImageFormat ) {
m_size = QSize(width, height);
m_ImageFormat = imageFormat;
cleanup();
prepareWindow();
prepareRenderer();
renderFlag = true;
}
m_rendererThread->quit();
m_rendererThread->wait();
}
void QmlRenderer::initialiseRenderParams(const QUrl &qmlFileUrl, bool isSingleFrame, qint64 frameTime, const QString &filename, const QString &outputDirectory, const QString &outputFormat, const QSize &size, qreal devicePixelRatio, int durationMs, int fps)
{
m_qmlFileUrl = qmlFileUrl;
m_size = size;
m_dpr = devicePixelRatio;
m_duration = durationMs;
m_fps = fps;
m_outputName = filename;
m_outputDirectory = outputDirectory;
m_outputFormat = outputFormat;
m_isSingleFrame = isSingleFrame;
m_frameTime = frameTime;
Q_ASSERT(m_fps!=0);
if(isSingleFrame) {
m_selectFrame = static_cast<int>(m_frameTime / ((1000/m_fps)));
}
m_framesCount = (m_duration / 1000 )* m_fps;
if (!loadComponent(m_qmlFileUrl)) {
return;
}
m_status = Initialised;
}
void QmlRenderer::initialiseContext()
{
QSurfaceFormat format;
format.setDepthBufferSize(16);
format.setStencilBufferSize(8);
m_context = std::make_unique<QOpenGLContext>();
m_context = std::make_shared<QOpenGLContext>();
m_context->setFormat(format);
Q_ASSERT(format.depthBufferSize() == (m_context->format()).depthBufferSize());
Q_ASSERT(format.stencilBufferSize() == (m_context->format()).stencilBufferSize());
m_context->create();
Q_ASSERT(m_context->isValid());
m_offscreenSurface = std::make_unique<QOffscreenSurface>();
m_offscreenSurface = std::make_shared<QOffscreenSurface>();
m_offscreenSurface->setFormat(m_context->format());
m_offscreenSurface->create();
Q_ASSERT(m_offscreenSurface->isValid());
m_renderControl = std::make_unique<QQuickRenderControl>(this);
m_renderControl = std::make_shared<QQuickRenderControl>(this);
Q_ASSERT(m_renderControl != nullptr);
QQmlEngine::setObjectOwnership(m_renderControl.get(), QQmlEngine::CppOwnership);
m_quickWindow = std::make_unique<QQuickWindow>(m_renderControl.get());
m_quickWindow = std::make_shared<QQuickWindow>(m_renderControl.get());
Q_ASSERT(m_quickWindow != nullptr);
m_qmlEngine = std::make_unique<QQmlEngine>();
......@@ -272,24 +99,30 @@ void QmlRenderer::initialiseContext()
m_qmlEngine->setIncubationController(m_quickWindow->incubationController());
}
m_context->makeCurrent(m_offscreenSurface.get());
Q_ASSERT(m_context->currentContext()!= nullptr);
m_renderControl->initialize(m_context.get());
connect(m_quickWindow.get(), SIGNAL(sceneGraphError(QQuickWindow::SceneGraphError, const QString)), this, SLOT(displaySceneGraphError(QQuickWindow::SceneGraphError, const QString)));
connect(m_qmlEngine.get(), SIGNAL(warnings(QList<QQmlError>)), this, SLOT(displayQmlError(QList<QQmlError>)));
m_corerenderer = std::make_unique<CoreRenderer>();
m_corerenderer->setContext(m_context);
}
m_corerenderer->setSurface(m_offscreenSurface);
m_corerenderer->setQuickWindow(m_quickWindow);
m_corerenderer->setRenderControl(m_renderControl);
void QmlRenderer::loadInput()
{
m_qmlComponent = std::make_unique<QQmlComponent>(m_qmlEngine.get(), QUrl(m_qmlFileUrl), QQmlComponent::PreferSynchronous);
Q_ASSERT(!m_qmlComponent->isNull() || m_qmlComponent->isReady());
Q_ASSERT(loadRootObject());
prepareWindow();
prepareRenderer();
m_corerenderer->setFPS(m_fps);
m_corerenderer->setDPR(m_dpr);
m_rendererThread = new QThread;
m_renderControl->prepareThread(m_rendererThread);
m_context->moveToThread(m_rendererThread);
m_corerenderer->moveToThread(m_rendererThread);
m_rendererThread->start();
connect(m_quickWindow.get(), SIGNAL(sceneGraphError(QQuickWindow::SceneGraphError, const QString)), this, SLOT(displaySceneGraphError(QQuickWindow::SceneGraphError, const QString)));
connect(m_qmlEngine.get(), SIGNAL(warnings(QList<QQmlError>)), this, SLOT(displayQmlError(QList<QQmlError>)));
}
bool QmlRenderer::checkIfComponentOK()
bool QmlRenderer::checkQmlComponent()
{
if (m_qmlComponent->isError()) {
const QList<QQmlError> errorList = m_qmlComponent->errors();
......@@ -302,14 +135,14 @@ bool QmlRenderer::checkIfComponentOK()
bool QmlRenderer::loadRootObject()
{
if(!checkIfComponentOK()) {