12 June, 2019
In the last week, I gained progress with the QML rendering library (see the code here)
It is doing what it is supposed to do - it renders an input QML file to output frames of a specified format and renders it as quick as possible (with QQuickRenderControl). If you want to test it out - there is CLI access to it through an executable (which is one of the things I've been working in the last week) for the library in the test directory here (make sure you read the READMEs along the way!)
So let's try to understand what really happens at the core of the library i.e. the rendering part.
To render QML, the obvious approach is to take 'screenshots' of each frame using a grab() method which would grab all the pixels at each instant of time and then render it - not only is this darned slow and expensive, it is also not possible to render at a custom frame rate that way.
Here's where QQuickRenderControl comes into the picture. QQuickRenderControl is used for rendering Qt Quick content (read QML) onto something in a very controlled manner. If you read the official documentation, that 'something' is an 'offscreen render target' - emphasis on 'offscreen' - which means we can tell our dummy window or surface to render our QML onto something else (a QOpenGLFramebufferObject to be specific) and that too, in a very quick process as well!
That was in words, now let's see some code - I do not want to confuse you (further) but this is essential because at the core of this library is this rendering.
Before we start rendering, we need to setup QQuickRenderControl :
- Set up the the format of the surface on which we will be doing the rendering on.
QSurfaceFormat format; format.setDepthBufferSize(16); format.setStencilBufferSize(8);
Set up the OpenGL context
m_context = std::make_unique<QOpenGLContext>(); m_context->setFormat(format); m_context->create();
- Set up the surface (dummy)
m_offscreenSurface = std::make_unique<QOffscreenSurface>(); m_offscreenSurface->setFormat(m_context->format()); m_offscreenSurface->create();
Set up RenderControl and target window (dummy)
m_renderControl = std::make_unique<QQuickRenderControl>(this); m_quickWindow = std::make_unique<QQuickWindow>(m_renderControl.get()); m_context->makeCurrent(m_offscreenSurface.get()); m_renderControl->initialize(m_context.get());
Create frame buffer object, and tell QuickWindow to render to this object
m_fbo = std::make_unique<QOpenGLFramebufferObject>(m_size * m_dpr, QOpenGLFramebufferObject::CombinedDepthStencil); m_quickWindow->setRenderTarget(m_fbo.get());
And with that, we can begin rendering but there's a catch - we cannot render at a custom frame rate yet, as I said before (and that is exactly what the blog that I had earlier mentioned was trying to primarily address). To achieve this, we need to tweak the animation driver - we create our own animation driver and advance frames at our own pace:
void advance() override
m_elapsed += m_step;
With that, and the next 4 lines (in an event driven loop, of course)
m_renderControl->polishItems(); m_renderControl->sync(); m_renderControl->render(); m_context->functions()->glFlush();
You're rendering frames! (if you want to see this code in practice, have a look here root/QmlRenderer/src/)
That's how it works - in my work so far here, I have made the above mentioned code, almost production ready.
For the next week, I've already started writing unit tests to make sure the library works fine at all points in the future. And after that, I'll start tinkering with MLT and writing a MLT producer.
And here's a question you might be having by now, why develop this as an independent library? Why not just directly integrate it with MLT?
a) Modularity : Easier to test an independent piece of code and make sure it works.
b) Easy integration : It's easier to integrate a module with a framework - as there are discussions regarding MLT's future in Kdenlive.
That's it for this week, let's hope for the best!