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

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
This diff is collapsed.
......@@ -23,16 +23,16 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define QMLRENDERER_H
#include "qmlrenderer_global.h"
#include "corerenderer.h"
#include <memory>
#include <QObject>
#include <QSize>
#include <QString>
#include <QDir>
#include <QQmlEngine>
#include <QQmlError>
#include <QFuture>
#include <QQuickWindow>
#include <QThread>
class QOpenGLContext;
class QOpenGLFramebufferObject;
......@@ -48,158 +48,51 @@ class QMLRENDERERSHARED_EXPORT QmlRenderer: public QObject
Q_OBJECT
public:
explicit QmlRenderer(QObject *parent = nullptr);
explicit QmlRenderer(QString qmlFileUrlString, qint64 frameTime=0, qreal devicePixelRatio = 1.0, int durationMs = 1000*5, int fps = 24, QObject *parent = nullptr);
~QmlRenderer() override;
enum Status {
enum renderStatus {
NotRunning,
Initialised,
Running,
};
/* @brief Initialises the render parameters - overloaded: loads a QML file template passed as a QUrl
*/
void initialiseRenderParams(const QUrl &qmlFileUrl, bool isSingleFrame=false, qint64 frameTime=0, const QString &outputDirectory = "", const QString &filename = "output_frame", const QString &outputFormat = "jpg", const QSize &size = QSize(1280, 720), qreal devicePixelRatio = 1.0, int durationMs = 1000*5, int fps = 24);
/* @brief Loads QML components - overloaded: called by overloaded initialiseRenderParams which uses QUrl for loading QML
*/
bool loadComponent(const QUrl &qmlFileUrl);
/* @brief Loads QML components - overloaded: called by overloaded initialiseRenderParams which uses QString QML file
*/
bool loadComponent(const QString &qmlFileText); // over loaded - used for MLT QML producer
/* @brief Uninstalls the animation driver, destroys FBO
* @description cleanup() must be called by the program using QmlRenderer library seperately
*/
void cleanup();
QImage render(int width, int height, QImage::Format format);
/* @brief Begins rendering and sets m_status to 'Running;
* @description Depending on value of m_isSingleFrame, renderSingleFrame() or renderEntireQml() is called from here
*/
void renderQml();
/* @brief Getter method for class members
*/
void getAllParams();
/* @brief Returns true if m_status is 'Initialised' and m_quickWindow's scene graph is initialised
*/
bool getSceneGraphStatus();
/* @brief Returns true if m_status is 'Initialised' and m_animationDriver is initialised
*/
bool getAnimationDriverStatus();
/* @brief Returns true if m_status is 'Initialised' and m_Fbo is bound
*/
bool getfboStatus();
/* @brief Returns state of m_status
*/
int getStatus();
/* @brief Returns m_currentFrame which is the number of frames actually rendered or atleast iterated
*/
int getActualFramesCount(); // returns the number of frames actually rendered
/* @brief Returns m_framesCount which is the number of frames that are supposed to be rendered (fps x duration)
*/
int getCalculatedFramesCount();
/* @brief Returns m_selectFrame which is the frame being rendered when m_isSingleFrame is set true
*/
int getSelectFrame();
/* @brief Returns the size of the QVector of futures stored in m_future
*/
int getFutureCount();
/* @brief Called by renderQml() when m_isSingleFrame is true, to render only a specific frame
*/
void renderSingleFrame();
/* @brief Called by renderQml() when m_isSingleFrame is false, to render all the frames
*/
void renderAllFrames();
/* @brief Called after the future finishes for a frame after rendering to save the frame in the given output directory and format
*/
static void saveImage(const QImage &image, const QString &outputFile)
{
image.save(outputFile);
}
/* @brief Returns true if m_status is 'Running'
*/
bool isRunning();
void render(QImage &img);
void loadInput();
private:
void initImageParams(int width = 1280, int height = 720, QImage::Format image_format = QImage::Format_ARGB32_Premultiplied);
/* @brief Creates the Frame Buffer Object and sets render target to the FBO
*/
void init(int width = 1280, int height = 720, QImage::Format image_format = QImage::Format_ARGB32_Premultiplied);
void loadInput();
// bool event(QEvent *event) override;
void createFbo();
/* @brief Resets the created Frame Buffer Object
*/
void destroyFbo();
/* @brief Returns true if QML components are loaded properly
*/
bool checkIfComponentOK();
/* @brief Called by loadComponent() to prepare m_quickWindow with the given size
*/
bool loadQML();
/* Overriden event() handles the Update requests sent by renderAllFrames() / renderSingleFrame() required for rendering
*/
bool event(QEvent *event) override;
bool loadRootObject();
void prepareWindow();
/* @brief Sets m_status to 'Initialised', creates FBO, installs animation driver
*/
void prepareRenderer();
bool checkQmlComponent();
QImage renderToQImage();
void renderNext();
void initialiseContext();
std::unique_ptr<QOpenGLContext> m_context;
std::unique_ptr<QOffscreenSurface> m_offscreenSurface;
std::unique_ptr<QQuickRenderControl> m_renderControl;
std::unique_ptr<QQuickWindow> m_quickWindow;
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<QQmlEngine> m_qmlEngine;
std::unique_ptr<QQmlComponent> m_qmlComponent;
std::unique_ptr<QQuickItem> m_rootItem;
std::unique_ptr<QOpenGLFramebufferObject> m_fbo;
std::unique_ptr<QmlAnimationDriver> m_animationDriver;
std::unique_ptr<QObject> m_rootObject;
std::unique_ptr<QFutureWatcher<void>> watcher;
QScopedPointer<QEvent> updateRequest;
QVector<std::shared_ptr<QFutureWatcher<void>>> m_futures;
std::unique_ptr<CoreRenderer> m_corerenderer;
QThread *m_rendererThread;
qreal m_dpr;
QSize m_size;
Status m_status;
renderStatus m_status;
int m_selectFrame;
int m_duration; // by default = 5 seconds
int m_fps; // by default = 24 fps
int m_framesCount;
int m_currentFrame;
int m_futureFinishedCounter;
QString m_outputName;
QString m_outputFormat;
QString m_outputDirectory;
......@@ -207,23 +100,14 @@ private:
QString m_qmlFileText;
QUrl m_qmlFileUrl;
QImage m_frame;
bool m_ifProducer; //set true when render() for producer is called
qint64 m_frameTime;
bool m_isSingleFrame;
QImage::Format m_ImageFormat;
bool renderFlag;
signals:
void finished();
void terminate(); // can be used by an implementing program to connect with a slot to close the execution
// * Currently used in QmlRender (CLI implementation of the lib) and connected with slot to quit the program
// * Currently used in test module
// TODO - implement terminate()
private slots:
/* @brief Slot activated when a future finishes, m_futureFinisheCounter is incremented and checks if rendering needs to be stopped, sets m_status to 'NotRunning' and emits terminate()
*/
void futureFinished();
/* @brief Slot activated when handling the QML file returns with warning
*/
void displayQmlError(QList<QQmlError> warnings);
......
......@@ -36,206 +36,14 @@ Render::~Render()
}
void Render::test_case1() // base initialisation test
void Render::test_case1()
{
QmlRenderer *m_renderer = new QmlRenderer(this);
qDebug() << " CURRENTPATH() == " << libDir;
m_renderer->initialiseRenderParams(QUrl(refDir + "/test.qml"), false, 0, "test_output", libDir, "jpg", QSize(1280,720), 1, 1000, 25);
QVERIFY2(m_renderer->getStatus() != m_renderer->Status::NotRunning, "STATUS ERROR : Not supposed to be running");
QVERIFY2(m_renderer->getCalculatedFramesCount()!=0, "VALUE ERROR: Frames not supposed to be zero");
QVERIFY2(m_renderer->getSceneGraphStatus()!=false, "SCENE GRAPH ERROR: Scene graph not initialised");
QVERIFY2(m_renderer->getAnimationDriverStatus()==false, "ANIMATION DRIVER ERROR: Driver not supposed to be running");
QVERIFY2(m_renderer->getfboStatus()==true, "FRAME BUFFER OBJECT ERROR: FBO not bound");
}
void Render::test_case2() // QmlRenderer::renderEntireQml() test
{
QmlRenderer *m_renderer = new QmlRenderer(this);
m_renderer->initialiseRenderParams(QUrl(refDir + "/test.qml"), false, 0, "test_output", libDir, "jpg", QSize(1280,720), 1, 1000, 25);
QVERIFY2(m_renderer->getStatus() != m_renderer->Status::NotRunning, "STATUS ERROR : Not supposed to be running");
QVERIFY2(m_renderer->getCalculatedFramesCount() != 0, "VALUE ERROR: Frames not supposed to be zero");
QVERIFY2(m_renderer->getSceneGraphStatus() != false, "SCENE GRAPH ERROR: Scene graph not initialised");
QVERIFY2(m_renderer->getAnimationDriverStatus() == false, "ANIMATION DRIVER ERROR: Driver not supposed to be running");
QVERIFY2(m_renderer->getfboStatus() == true, "FRAME BUFFER OBJECT ERROR: FBO not bound");
m_renderer->renderQml();
// We wait till the signal QmlRenderer::terminate is emitted from the renderer
QTimer timer;
timer.setSingleShot(true);
QEventLoop loop;
connect( m_renderer, &QmlRenderer::terminate, &loop, &QEventLoop::quit );
connect( &timer, &QTimer::timeout, &loop, &QEventLoop::quit );
timer.start(2000);
loop.exec();
qDebug() << "calc frame == " << m_renderer->getActualFramesCount() << " " << m_renderer->getCalculatedFramesCount();
QVERIFY2(m_renderer->getCalculatedFramesCount() == m_renderer->getActualFramesCount(), "RENDERING ERROR: Missing frames");
QVERIFY2(m_renderer->getFutureCount() == m_renderer->getCalculatedFramesCount(), "FUTURE HANDLING ERROR: Enough futures were not found");
qDebug() << " NOT ENOUGH TIME!!!";
}
void Render::test_case3() // QmlRenderer::renderSingleFrame() test
{
QmlRenderer *m_renderer = new QmlRenderer(this);
m_renderer->initialiseRenderParams(QUrl(refDir + "/test.qml"), true, 80, "test_output", libDir, "jpg", QSize(1280,720), 1, 1000, 25);
QVERIFY2(m_renderer->getStatus() != m_renderer->Status::NotRunning, "STATUS ERROR : Not supposed to be running");
QVERIFY2(m_renderer->getCalculatedFramesCount()!=0, "VALUE ERROR: Frames not supposed to be zero");
QVERIFY2(m_renderer->getSceneGraphStatus()!=false, "SCENE GRAPH ERROR: Scene graph not initialised");
QVERIFY2(m_renderer->getfboStatus()==true, "FRAME BUFFER OBJECT ERROR: FBO not bound");
m_renderer->renderQml();
// We wait till the signal QmlRenderer::terminate is emitted from the renderer
QTimer timer;
timer.setSingleShot(true);
QEventLoop loop;
connect( m_renderer, &QmlRenderer::terminate, &loop, &QEventLoop::quit );
connect( &timer, &QTimer::timeout, &loop, &QEventLoop::quit );
timer.start(2000);
loop.exec();
QVERIFY2(m_renderer->getActualFramesCount() == m_renderer->getSelectFrame(), "FRAME NOT RENDERED: The required frame was not rendered properly");
//QVERIFY2(m_renderer->getFutureCount() == 1, "FUTURE HANDLING ERROR: Futures not handled properly");
}
void Render::test_case4() // QmlRenderer::cleanup() test
{
QmlRenderer *m_renderer = new QmlRenderer(this);
m_renderer->initialiseRenderParams(QUrl(refDir + "/test.qml"), false, 0, "test_output", libDir, "jpg", QSize(1280,720), 1, 1000, 25);
QVERIFY2(m_renderer->getStatus() != m_renderer->Status::NotRunning, "STATUS ERROR : Not supposed to be running");
QVERIFY2(m_renderer->getCalculatedFramesCount() != 0, "VALUE ERROR: Frames not supposed to be zero");
QVERIFY2(m_renderer->getSceneGraphStatus() != false, "SCENE GRAPH ERROR: Scene graph not initialised");
QVERIFY2(m_renderer->getfboStatus() == true, "FRAME BUFFER OBJECT ERROR: FBO not bound");
m_renderer->renderQml();
// We wait till the signal QmlRenderer::terminate is emitted from the renderer
QTimer timer;
timer.setSingleShot(true);
QEventLoop loop;
connect( m_renderer, &QmlRenderer::terminate, &loop, &QEventLoop::quit );
connect( &timer, &QTimer::timeout, &loop, &QEventLoop::quit );
timer.start(2000);
loop.exec();
QVERIFY2(m_renderer->getCalculatedFramesCount() == m_renderer->getActualFramesCount(), "RENDERING ERROR: Missing frames");
QVERIFY2(m_renderer->getFutureCount() == m_renderer->getActualFramesCount(), "FUTURE ERROR: Future handling went wrong");
QVERIFY2(m_renderer->getAnimationDriverStatus() == false, "ANIMATION DRIVER ERROR: Animation driver still running");
QVERIFY2(m_renderer->getfboStatus() == false, "FBO ERROR: Animation driver still running");
}
void Render::test_case5() // Integration test - QmlRenderer::renderEntireQml()
{
QmlRenderer *m_renderer = new QmlRenderer(this);
m_renderer->initialiseRenderParams(QUrl(refDir + "/test.qml"), false, 0, "test_output", libDir, "jpg", QSize(1280,720), 1, 1000, 25);
m_renderer->renderQml();
// We wait till the signal QmlRenderer::terminate is emitted from the renderer
QTimer timer;
timer.setSingleShot(true);
QEventLoop loop;
connect( m_renderer, &QmlRenderer::terminate, &loop, &QEventLoop::quit );
connect( &timer, &QTimer::timeout, &loop, &QEventLoop::quit );
timer.start(3000);
loop.exec();
qDebug() << m_renderer->getCalculatedFramesCount() << " " << m_renderer->getActualFramesCount();
QVERIFY2(m_renderer->getCalculatedFramesCount() == m_renderer->getActualFramesCount(), "RENDERING ERROR: Missing frames");
QImage orig_frame;
QImage lib_frame;
orig_frame = QImage(refDir + "/output_2.jpg");
lib_frame = QImage(libDir + "/test_output_2.jpg");
QVERIFY2(orig_frame == lib_frame, "Rendering error");
orig_frame = QImage(refDir + "/output_13.jpg");
lib_frame = QImage(libDir + "/test_output_13.jpg");
QVERIFY2(orig_frame == lib_frame, "Rendering error");
orig_frame = QImage(refDir + "/output_25.jpg");
lib_frame = QImage(libDir + "/test_output_25.jpg");
QVERIFY2(orig_frame == lib_frame, "Rendering error");
}
void Render::test_case6() // integration test : QmlRenderer::renderSingleFrame()
{
QmlRenderer *m_renderer = new QmlRenderer(this);
m_renderer->initialiseRenderParams(QUrl(refDir + "/test.qml"), true, 800, "test_output", libDir, "jpg", QSize(1280,720), 1, 1000, 25);
// at frameTime = 800, 20th frame is rendered
QVERIFY2(m_renderer->getStatus() != m_renderer->Status::NotRunning, "STATUS ERROR : Not supposed to be running");
QVERIFY2(m_renderer->getCalculatedFramesCount()!=0, "VALUE ERROR: Frames not supposed to be zero");
QVERIFY2(m_renderer->getSceneGraphStatus()!=false, "SCENE GRAPH ERROR: Scene graph not initialised");
QVERIFY2(m_renderer->getfboStatus()==true, "FRAME BUFFER OBJECT ERROR: FBO not bound");
m_renderer->renderQml();
QImage orig_frame = QImage(refDir + "/output_frame_20.jpg");
QImage lib_frame = QImage(libDir + "/test_output_20.jpg");
QVERIFY2(orig_frame == lib_frame, "Rendering error");