Qt5.6: high DPI support and OpenGL (OpenSceneGraph)
Asked Answered
C

1

6

I have a minimal application which uses QOpenGLWidget that integrates an OpenGL wrapper library (OpenSceneGraph). I am trying to figure out how to correctly use the Qt5.6 support for high DPI screens when dealing with OpenGL content like I use.

My main() function has the following code:

int main(int argc, char** argv)
{
    // DPI support is on
    QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QApplication app(argc, argv);
    QMainWindow window;

    // QOpenGLWidget with OpenSceneGraph content
    QtOSGWidget* widget = new QtOSGWidget();

    window.setCentralWidget(widget);
    window.show();
    return app.exec();
}

The QtOSGWidget is derived from QOpenGLWidget with OpenSceneGraph content: I use osgViewer::GraphicsWindowEmbedded to render my simple scene.

To merge OSG with Qt, I re-define the *GL() methods: paintGL(), resizeGL() and initializeGL(). I follow the Qt docs on what each of the *GL() methods should contain, i.e.:

  • paintGL() makes sure the viewer is updated
  • resizeGL() makes sure the graphics window is resized properly (together with camera and viewport);
  • initializeGL() makes sure OpenGL state is initialized.
  • I also re-defined Qt mouse events so that to pass the events to OSG

When I run my example on normal resolution screen, or with QApplication::setAttribute(Qt::AA_DisableHighDpiScaling);, the scene looks like it should:

cylinder example - high DPI support is off

Also, when I manipulate the camera view, the mouse coordinates are captured correctly.

However, when I set the high DPI option on, this is what I get:

high DPI is on

The mouse coordinates for events are scaled as well and not passed to the OpenSceneGraph's event handler correctly.

As you can see, the graphics window size is not scaled by Qt. It is probably because of the way how I set up the sizing:

virtual void resizeGL( int width, int height ) 
{
    // resize event is passed to OSG
    this->getEventQueue()->windowResize(this->x(), this->y(), width, height);

    // graphics window resize
    m_graphicsWindow->resized(this->x(), this->y(), width, height);

    // camera viewport
    osg::Camera* camera = m_viewer->getCamera();
    camera->setViewport(0, 0, this->width(), this->height());
}

That sizing is not scaled by Qt. Same thing happens to the mouse events coordinates.

My question: is there a way to know to what size the scaling will be performed so that to do resizeGL() correctly? Or what is the correct way to deal with the problem?

Update/Solution using scaling by hand: thanks to the answer of @AlexanderVX, I figured out the scaling solution. At first, I need to know some reference values of DPI in X and Y dimensions. Then I calculate the scaling coordinates based on that and pass them to my widget QtOSGWidget. So, the code of the main() has to contain:

QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv);

int x = QApplication::desktop()->physicalDpiX();
int y = QApplication::desktop()->physicalDpiY();
// values 284 and 285 are the reference values
double scaleX = 284.0/double(x);
double scaleY = 285.0/double(y);

QMainWindow window;
QtOSGWidget* widget = new QtOSGWidget(scaleX, scaleY, &window);
// etc.

Then, whenever I refer to the sizing functions that needed to be passed to OpenSceneGraph (OpenGL) content, I have to do scaling, e.g.:

// resizeGL example
this->getEventQueue()->windowResize(this->x()*m_scaleX, this->y() * m_scaleY, width*m_scaleX, height*m_scaleY);

// mouse event example
this->getEventQueue()->mouseButtonPress(event->x()*m_scaleX, event->y()*m_scaleY, button);

Final update: since the target platform of my application is Windows 7-10, it makes much more sense to stick with the proposed answer of @AlexanderV (second part), i.e., to use SetProcessDPIAware() function.

Consideration answered 18/5, 2016 at 15:24 Comment(2)
Will your resize method called when you are resizing the window?Hook
Yes, I checked that. It is called automatically every time.Consideration
Z
4

Is there a way to know to what size the scaling will be performed so that to do resizeGL() correctly?

First, detect the monitor:

        // relative to widget
        int screenNum = QApplication::desktop()->screenNumber(pWidget);

or maybe

        // relative to global screen position
        int screenNum = QApplication::desktop()->screenNumber(pWidget->topLeft());

and that gives us pointer to QScreen:

        QScreen* pScreen = QApplication::desktop()->screen(screenNum);

from which you can read many screen characteristics, including "physical dot per inch" which makes us able to judge how many pixels there per inch:

        qreal pxPerInch = pScreen->physicalDotsPerInch();

Having pixels per inch you will be able to programmatically scale your drawing code. Detect how much is 'normal' density and then scale proportionally against the density detected on physical device. Of course that approach is more suitable for accurate graphics. Be aware of both physicalDotPerInch() and devicePixelRatio(), though.

        qreal scaleFactor = pScreen->physicalDotsPerInch() / normalPxPerInch;

Or what is the correct way to deal with the problem?

However, with widgets and normal GUI drawing it is often easier to let Qt / system to scale the entire UI. Qt Documentation: High DPI Displays.

If the OS Windows at least Vista or higher and tuning Qt for high DPI sounds complicated then there is a shortcut that I take and it helps me, though Qt complains in the log: "SetProcessDpiAwareness failed: "COM error 0xffffffff80070005 (Unknown error 0x0ffffffff80070005)" ". I call this function from main() before the event loop: SetProcessDPIAware() and then all the UI looks alike no matter what monitor density is. I use it with Qt 5.5, though. There is also SetProcessDpiAwareness() function, explore. I use SetProcessDPIAware because it is available since Windows Vista but SetProcessDpiAwareness is only available since Windows 8.1. So, the decision may depend on potential clients systems.

A 'shortcut' approach:

int main(int argc, char** argv)
{
    // DPI support is on
    // QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

     // on Windows?
    ::SetProcessDPIAware();
    // MSDN suggests not to use SetProcessDPIAware() as it is obsolete and may not be available.
    // But it works with widgets.

    QApplication app(argc, argv);
    QMainWindow window;

    // QOpenGLWidget with OpenSceneGraph content
    QtOSGWidget* widget = new QtOSGWidget();

    window.setCentralWidget(widget);
    window.show();
    return app.exec();
}
Zipper answered 18/5, 2016 at 16:13 Comment(7)
Thank you for the answer. I am trying the first approach. A question: any ideas how to detect the 'normal' density normalPxPerInch?Consideration
Run the code on the system which you consider 'normal'. Try to see what physicalDotsPerInch returns.Zipper
Thank you again. I think I will go with the first approach (scaling). For my simple example I had to calculate scaling for both X and Y dimensions. I used QApplication::desktop()->physicalDpiX() and QApplication::desktop()->physicalDpiY(). Your answer helped me to figure it out.Consideration
@vicrucann, mind that now you have multi-monitor unawareness issue. Try, for example, using 2 monitors of different dot per inch.Zipper
@AlexandeVX : I have multi monitors and already did some tests with them. And yes, you are right: the scaling does not work properly when I drag from high DPI to low DPI. But I thought it is a bit off-topic for the question. Actually, Qt tries to scale its widgets and it somewhat works (not best-looking result, though). For the scaling I will try to figure out how to detect the change of screens DPI and update the scale factors. If it will not be straightforward, I might explore the second suggested option; event it is only intended for Windows.Consideration
That is what SetProcessDPIAware for. But instead of other scaling.Zipper
I see what you mean now. I tried it on my extended project with complex scene, it looks better. For me large project I will chose the SetProcessDPIAware.Consideration

© 2022 - 2024 — McMap. All rights reserved.