The recent
release of Qt 5.4 , among other things, put at the disposal of the developers one, in my opinion, a very interesting tool. Namely, the Qt developers made
QQuickRenderControl part of the public API. The use of this class is that it is now possible to use Qml in conjunction with any other framework, if it provides the ability to get (or set) a pointer to the context used by OpenGL.
On the other hand, in the process of working on one of my projects, I was faced with the need to render the QML scene on
CALayer (Mac OS X) , without the slightest opportunity to access the parent window. A weekly search for possible solutions to the problem showed that the most appropriate solution would be to use QQuickRenderControl from Qt 5.4, thanks to a successful coincidence, which received release status at the same time as the aforementioned task had occurred.
Initially, I assumed that the task is not real, and it will be solved in a couple of evenings, but how wrong I was - the task took about half a month for research, and another half a month for implementation (which is still far from ideal).
Several theses
- QQuickRenderControl is just an additional interface to the implementation of QQuickWindow for receiving notifications about changing the QML scene, as well as sending commands in the opposite direction (that is, in fact, a “crutch”);
- The result of rendering will be obtained as QOpenGLFramebufferObject (hereinafter referred to as FBO) , which can later be used as a texture;
- You will have to work directly with QuickWindow; accordingly, the QML download service provided by QQuickView will not be available, and you will have to implement it yourself;
- Since no window is actually created, it becomes necessary to artificially pass mouse and keyboard events to QQuickWindow. It is also necessary to manually control the size of the window;
- An example of using QQuickRenderControl I managed to find only one, in Qt 5.4 (Examples \ Qt-5.4 \ quick \ rendercontrol) - it was actually on him and went through all the proceedings;
What should be done to solve the original problem?
1) Implement the QQuickWindow setting for rendering in FBO and controlling this process through QQuickRenderControl;
2) Implement loading Qml and attaching the result to QQuickWindow;
3) Implement the transmission of mouse and keyboard events;
4) Draw FBO (for the sake of which everything was started);
In this article I will allow myself to dwell only on point 1), the remaining points in the subsequent parts (if you find it interesting).
')
Customizing QQuickWindow
External QOpenGLContext
The starting point is the OpenGL context in which FBO will eventually render. But since, with a high degree of probability, it is necessary to work with the context initially having no relation to Qt, it is necessary to convert the context from the operating system format to the QOpenGLContext instance. To do this, use the
QOpenGLContext :: setNativeHandle method .
NSOpenGLContext based usage example:
NSOpenGLContext* nativeContext = [super openGLContextForPixelFormat: pixelFormat]; QOpenGLContext* extContext = new QOpenGLContext; extContext->setNativeHandle( QVariant::fromValue( QCocoaNativeContext( nativeContext ) ) ); extContext->create();
It is better to look at the list of available Native Context directly in Qt header files (include \ QtPlatformHeaders), since the documentation in this part is not very complete.Then you can use this context (but you need to carefully monitor that the state changes of this context do not conflict with the owner's manipulations), but you can make a shared context:
QSurfaceFormat format; format.setDepthBufferSize( 16 ); format.setStencilBufferSize( 8 ); context = new QOpenGLContext; context->setFormat( format ); context->setShareContext( extContext ); context->create();
An important nuance to use OpenGL context with QML is the presence of configured Depth Buffer and Stencil Buffer, so if you do not have the ability to influence the parameters of the source context, you need to use a shared context with the installed Depth Buffer Size and Stencil Buffer Size.
Creating QQuickWindow
When creating a QQuickWindow, a QQuickRenderControl is pre-created and passed to the constructor:
QQuickRenderControl* renderControl = new QQuickRenderControl(); QQuickWindow* quickWindow = new QQuickWindow( renderControl ); quickWindow->setGeometry( 0, 0, 640, 480 );
In addition, it is important to set the window size for further successful creation of the FBO.
QQuickRenderControl and QOpenGLFramebufferObject initialization
Before calling QQuickRenderControl :: initialize, it is important to make the context current, because during the call, the sceneGraphInitialized signal will be generated, and this is a good point for creating an FBO (which, in turn, requires an exposed current context).
QOpenGLFramebufferObject* fbo = nullptr; connect( quickWindow, &QQuickWindow::sceneGraphInitialized, [&] () { fbo = new QOpenGLFramebufferObject( quickWindow->size(), QOpenGLFramebufferObject::CombinedDepthStencil ); quickWindow->setRenderTarget( fbo ); } ); offscreenSurface = new QOffscreenSurface(); offscreenSurface->setFormat( context->format() ); offscreenSurface->create(); context->makeCurrent( offscreenSurface ); renderControl->initialize( context ); context->doneCurrent();
Rendering
Rendering needs to be done as a response to the QQuickRenderControl :: renderRequested and QQuickRenderControl :: sceneChanged signals. The difference in these two cases is that in the second case it is necessary to additionally call QQuickRenderControl :: polishItems and QQuickRenderControl :: sync. The second important feature is that it is
strongly advised not to render directly in the handlers of the above signals. Therefore, a timer is used with a small interval. Well, the last subtlety is that, in the case of using the shared OpenGL context, after rendering, glFlush is required to be called - otherwise the primary context does not see changes in FBO.
bool* needSyncAndPolish = new bool; *needSyncAndPolish = true; QTimer* renderTimer = new QTimer; renderTimer->setSingleShot( true ); renderTimer->setInterval( 5 ); connect( renderTimer, &QTimer::timeout, [&] () { if( context->makeCurrent( offscreenSurface ) ) { if( *needPolishAndSync ) { *needPolishAndSync = false; renderControl->polishItems(); renderControl->sync(); } renderControl->render(); quickWindow->resetOpenGLState(); context->functions()->glFlush(); context->doneCurrent(); } ); connect( renderControl, &QQuickRenderControl::renderRequested, [&] () { if( !renderTimer->isActive() ) renderTimer->start(); } ); connect( renderControl, &QQuickRenderControl::sceneChanged, [&] () { *needPolishAndSync = true; if( !renderTimer->isActive() ) renderTimer->start(); } );
Well, in general, that's all, the first part of the task is completed.
A class that implements the above concept is available on GitHub:
FboQuickWindow.h ,
FboQuickWindow.cppComments, questions, healthy criticism in the comments are welcome.
Continued:
Part II: Loading QML ,
Part III: Processing User Input