MyBlog++

#arduino #beginner #game #qt #qt3d #rendering

How to create a custom rendering in a Qt5 Widget

19 February 2016 - #qt #rendering

For a recent project, I chose Qt to handle my application (honestly to get rid of Win32 and MFC but to keep the C/C++ flavor). Then I searched a way to integrate my custom rendering engine into a Qt5 QWidget.

As Qt is pushing its new OpenGL rendering pipeline, it’s hard to find a correct and official way to do it beside using their QOpenGLWidget.

Source code

This is a base code example on how I did it in C++. For more details about the implementation, see below.

This code has been tested with Qt5, Visual Studio 2013/2015, Windows 7/8/10, 32/64 bits. It was also used to create a custom widget for Oculus, with success, supporting OpenGL 3.3 and Oculus SDK 0.7.

#include <QWidget>
#include <QResizeEvent>
#include <QApplication>
	
class WidgetCustomRender : public QWidget
{
    Q_OBJECT;

public:
    explicit WidgetCustomRender(QWidget* parent)
        : QWidget{parent}
    {
        setAttribute(Qt::WA_NativeWindow);
        setAttribute(Qt::WA_PaintOnScreen);
        setAttribute(Qt::WA_NoSystemBackground);
    }

    virtual ~WidgetCustomRender() = default;

    virtual QPaintEngine* paintEngine() const override
    {
        return nullptr;
    }

    void render()
    {
        if(_updatePending == false)
        {
            _updatePending = true;
            QApplication::postEvent(
                this,
                new QEvent{QEvent::UpdateRequest});
        }
    }

public:
    bool continuousRender = false;

protected:
    virtual void paintEvent(QPaintEvent* paintEvent) override
    {
        if(_isInit == false)
            _init();
        render();
    }

    virtual void showEvent(QShowEvent* showEvent) override
    {
        QWidget::showEvent(showEvent);
        if(_isInit == false)
            _init();
    }

    virtual void resizeEvent(QResizeEvent* resizeEvent) override
    {
        QWidget::resizeEvent(resizeEvent);
        auto sz = resizeEvent->size();
        if((sz.width() < 0) || (sz.height() < 0))
            return;
        
		//
        // put your resize code here...
        //

        // because Qt is not sending update request when resizing smaller
        render();
    }

    virtual bool event(QEvent* event) override
    {
        switch(event->type())
        {
        case QEvent::UpdateRequest:
            _updatePending = false;
            _doRender();
            return true;
        default:
            return QWidget::event(event);
        }
    }

private:
    void _doRender()
    {
        if(isVisible() == false)
            return;
        if(_isInit == false)
            return;
        
        // you may want to add some code to control rendering frequency
        // and ensure you are not rendering too fast in case of continuous
        // rendering...
        // if you control the rendering frequency, don't forget to make a
        // call to render() if you're not going to do the rendering...
        
		//
        // do your custom rendering here...
        //
		
        // next frame if rendering continuously
        if(continuousRender == true)
            render();
    }

    void _init()
    {
        // you can grab the native window handler (HWND for Windows) of
        // this widget:
        auto nativeWindowHandler = winId();
        
        // do your init code here...
        
        _isInit = true;
    }

private:
    bool _updatePending = false;
    bool _isInit = false;
};

Implementation details

Constructor

Setting the attribute WA_NativeWindow ensures the widget will have a native window created (i.e.: HWND for Windows). It’s just a precaution, because as far as I tested, it seems that Qt is creating a native window for almost all widgets.

Setting both WA_PaintOnScreen, WA_NoSystemBackground and returning a nullptr paint engine, tells Qt that you will handle the whole painting of the widget yourself.

Widget behaviour

The way I handle resizeEvent may trigger two renderings. This comes from (I guess) an optimization where Qt will only trigger an UpdateRequest event if the new size is bigger than the old size. As a result, on smaller resize, one rendering is triggered, on bigger resize, two rendering may be triggered (depending on the speed of the rendering).

_init() is the place where you can be sure that the native window has been created and is valid. It’s where you should do any initialization related to the native window system (i.e.: creating an OpenGL context, initializing a Direct3D device, etc.).

Rendering

All rendering code should go inside _doRender().

The function _doRender() is not and shouln’t be called directly. It is called, inside the event processing function, on UpdateRequest event. This helps the rendering to be synchronized with other Qt events (like mouse or keyboard) and prevent simultaneous render calls.

When the widget needs to be refreshed or repainted just make a call to render(), which generates an UpdateRequest event.

In case of continuous rendering, render() is automatically called at the end of the rendering. As a result, highest render frequency is limited by the speed of the processing of the Qt event pipeline which is fairly fast enough to reach very high rendering frequency.

In my project, I was not doing direct API calls (OpenGL, DirectX) in _doRender(). Instead, the API calls (i.e.: the real rendering calls) were registered in _doRender() as command lists and executed in a separate thread.


author-logo zeduckmaster
just another senior C++ developper
mastodon-logo framagit-logo rss-symbol

Creative Commons Licence
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.