Qt MainWindow with QOpenGLWIdget in Retina display displays wrong size - c++

I have a Qt application with a MainWindow.
I embed a QOpenGLWidget in it. Everything works fine until I start using an Apple Retina Display and run my app in High DPI mode: my QOpenGLWidget is just 1/4 of the size it was supposed to have (i.e., it only fills the bottom-left part of the area it is supposed to fill). This widget is displaying raw OpenGL data (actually, an OpenSceneGraph context)
What can I do to solve this?

Found out that the best option for now, for OpenGL related widgets and events, is to use the QPaintDevice::devicePixelRatio() (http://doc.qt.io/qt-5/qpaintdevice.html#devicePixelRatio)
This implies multiplying everything that uses pixel coordinates, i.e., mouse events, resizing events, and so on. Example:
void MyGLWidget::resizeGL(int width, int height) {
width *= Application::desktop()->devicePixelRatio();
height *= Application::desktop()->devicePixelRatio();
...
// Continue with previous code
}
When running in low resolution mode in a Retina/HighDPI display, or when running in a regular display, this ratio is 1, so this seems portable to me.

From Qt docs (section OsX),
Note: The scaling is not applied to Open GL windows
I didn't try this approach on Mac, but it helped with the same issue on my Windows machine. I'm not sure if it's the best solution, though, there could be easier. Try and see if it works.
The main idea is to scale up your OpenGL content sizes manually.
First, Define the amount of scale to be performed. You can use the characteristic of physical dot per inch:
QApplication app(argc, argv);
int x = QApplication::desktop()->physicalDpiX();
int y = QApplication::desktop()->physicalDpiY();
// values 284 and 285 are the examples of reference values that we determined when DPI scaling was disabled
double scaleX = 284.0/double(x);
double scaleY = 285.0/double(y);
physicalDpi* makes possible to judge how many pixels we have for an inch. In order to define the scale, detect how much is reference value of density and then scale proportionally against the density of the physical device (next step).
Second, you have to use the scaleX and scaleY inside your QOpenGLWidget and make sure we manually scale:
sizes such as QOpenGLWidget::width() and QOpenGLWidget::height() will turn into this->width()*m_scaleX and this->height()*m_scaleY
mouse events coordinates, e.g., event->x()*m_scaleX and event->y()*m_scaleY

Related

How to get sharp UI on high dpi with Qt 5.6?

I am developing on a 4k monitor and its a pain...
Finally I managed to set up QtDesigner and then ecountered this issue:
When you use QT_AUTO_SCREEN_SCALE_FACTOR=1 and compile an app with radiobutton and other basic widgets, it looks scaled on a 4k screen.
Otherwise the dimensions of controls are correct and as expected, its just not sharp, but rather pixelated.
I am running on Windows 10 Home 64bit on 4k screen with 200% DPI zoom, using Qt 5.6 RC msvc2015 64bit and tried with achieving the same results using
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
If I use
QGuiApplication::setAttribute(Qt::AA_DisableHighDpiScaling);
the controls are sharp, text size is ok BUT all dimensions are much smaller.
How do I make controls sharp on high DPI screen?
As Qt documentation says:
Use QT_AUTO_SCREEN_SCALE_FACTOR to enable platform plugin controlled per-screen factors.
QT_SCREEN_SCALE_FACTORS to set per-screen factors.
QT_SCALE_FACTOR to set the application global scale factor.
You can try doing what Qt Creator is doing:
static const char ENV_VAR_QT_DEVICE_PIXEL_RATIO[] = "QT_DEVICE_PIXEL_RATIO";
if (!qEnvironmentVariableIsSet(ENV_VAR_QT_DEVICE_PIXEL_RATIO)
&& !qEnvironmentVariableIsSet("QT_AUTO_SCREEN_SCALE_FACTOR")
&& !qEnvironmentVariableIsSet("QT_SCALE_FACTOR")
&& !qEnvironmentVariableIsSet("QT_SCREEN_SCALE_FACTORS")) {
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
}
Basically important thing is the last line QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);.
Here is what was working for me. You can set DPIawareness manually by giving a command line option at the instanciation of the QApplication.
The official documentation is here https://doc.qt.io/qt-5/highdpi.html (section DPI awareness).
According to the documentation, you can set the application to DPI Unaware (thus it will automatically scale but display will be blurred), or to System DPI Aware or to Per-Monitor Aware.
Here is a minimal example code for the instanciation of the QApplication to force High DPI, choose other value than 1 (0 or 2) to enable DPIUnaware or Per Monitor DPI Aware:
int main()
{
int argc = 3;
char*argv[] = {(char*)"Appname", (char*)"--platform", (char*)"windows:dpiawareness=1";
(void) new QApplication(argc, argv);
}
With QT_AUTO_SCREEN_SCALE_FACTOR, the point size of the fonts are not changed, they're just scaled up from their original pixels, so they will never be smooth, only more bumpy.
Ref: http://doc.qt.io/qt-5.6/highdpi.html#high-dpi-support-in-qt
"This will not change the size of point sized fonts"
You need to use QT_SCALE_FACTOR instead to rescale your app, not just rescale its pixels.

Resizing in SFML 2.0

This may be a simple question but I'm having a hard time finding a straight answer to it: is there a way to resize a loaded Texture in SFML 2.0? For instance, if I have a 3264x2448 png and I want to scale it down to fit a 900x1200 rendered window without cropping, how would I do so?
Is there a way to scale all rendered windows to fit whatever monitor
of whatever system the application is running on?
First, here's a way to scale the image to the current RenderWindow size.
// assuming the given dimension
// change this dynamically with the myRenderWindow->getView().getSize()
sf::Vector2f targetSize(900.0f, 1200.0f);
yourSprite.setScale(
targetSize.x / yourSprite.getLocalBounds().width,
targetSize.y / yourSprite.getLocalBounds().height);
Be aware that this may stretch your image if the aspect ratio is not maintained. You might want to add code to adjust the decision for your case.
Then, if you want to stretch the RenderWindow to fill all the screen, may I suggest you use fullscreen mode?
Here's a snippet of how it's done:
// add the flag to the other ones
mStyleFlag = sf::Style::Default | sf::Style::Fullscreen;
// get the video mode (which tells you the size and BPP information of the current display
std::vector<sf::VideoMode> VModes = sf::VideoMode::getFullscreenModes();
// then create (or automatically recreate) the RenderWindow
mMainWindow.create(VModes.at(0), "My window title", mStyleFlag);
is there a way to resize a loaded Texture in SFML 2.0?
Not an sf::Texture directly. But you can use an sf::Sprite: you load load your texture, you pass it to an sf::Sprite and you play with sf::Sprite::setScale or sf::Sprite::scale.

Changing DPI scaling size of display make Qt application's font size get rendered bigger

I have created some GUI application using Qt. My GUI application contains controls like push button and radio button. When I run the application, buttons and fonts inside button looks normal. When I change the DPI scaling size of display from 100% to 150% or 200%, font size of controls rendered bigger but not control size (pushbutton, radio button) irrespective of resolution. Due to this the text inside controls were cut off. please see the attached image.
Qt application look when DPI scaling size set to 100%
Qt application look when DPI scaling size set to 200%
I am running my application in some tablets also. In tablets, DPI scale value should be more than 150% else everything will be shown very small.
I searched in the web for creating UI application in Qt irrespective of resolution and DPI scale value but no luck. So I am posting my questing here. Please let me know if there is some way to get rid of this.
High DPI support is enabled from Qt 5.6 onward.
Setting QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling) in your application source code allows automatic high-DPI scaling.
NOTICE: To use the attribute method, you must set the attribute before you create your QApplication object:
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv);
return app.exec();
}
Using layouts correctly can help.
http://qt-project.org/doc/qt-4.8/layout.html
Telling the OS that you handle DPI changes, will prevent weird font changes that you weren't expecting.
http://msdn.microsoft.com/en-us/library/ms701681(v=vs.85).aspx
For spacing critical places, you can check the size of your rendered font, and then set the minimum size of your object based on the resulting size of your text.
http://qt-project.org/doc/qt-4.8/qfontmetrics.html#details
https://blog.qt.digia.com/blog/2009/06/26/improving-support-for-higher-dpi-on-vista/
You could try checking with other built in measurements from Qt:
http://qt-project.org/doc/qt-4.8/qpaintdevice.html#widthMM
http://qt-project.org/doc/qt-4.8/qpaintdevice.html#logicalDpiX
If you are using QML, try for pristine layouts of only anchor based placement.
http://qt-project.org/doc/qt-4.8/qml-anchor-layout.html
QApplication has some settings that are somewhat related.
http://qt-project.org/doc/qt-4.8/qapplication.html#setDesktopSettingsAware
You could manually specify the font, too.
http://qt-project.org/doc/qt-4.8/qapplication.html#setFont
Hope that helps.
I had a fixed size window which was not large enough to fit all the text it contained when Windows accessibility settings where applied to scale up all text sizes. Windows does this via dpi increases. I fixed this by retreiving the os scaling factor and then adjusted the size of the my window and some of it's layouts (which I couldn't get to scale automatically for some reason).
Here's how I got the dpi scale (in a file called "WindowsDpiScale.h"):
#ifndef WINDOWSDPISCALE_H
#define WINDOWSDPISCALE_H
#include <QtGlobal>
#ifdef Q_OS_WIN
#include <windows.h>
const float DEFAULT_DPI = 96.0;
float windowsDpiScale()
{
HDC screen = GetDC( 0 );
FLOAT dpiX = static_cast<FLOAT>( GetDeviceCaps( screen, LOGPIXELSX ) );
ReleaseDC( 0, screen );
return dpiX / DEFAULT_DPI;
}
#endif //Q_OS_WIN
#endif // WINDOWSDPISCALE_H
And then, how I applied it in my case:
...
#include "WindowsDpiScale.h"
MainWindow::MainWindow( QWidget *parent )
: QMainWindow( parent )
{
...
// Enlarge the window and various child widgets to accomendate
// OS display scaling (i.e. accessibily options)
setScaleToOsSettings();
...
}
void MainWindow::setScaleToOsSettings()
{
#ifdef Q_OS_WIN
setScale( windowsDpiScale() );
#endif
}
void MainWindow::setScale( float scale )
{
// Resize the window
this->setFixedSize( (int)(scale * this->maximumWidth()),
(int)(scale * this->maximumHeight()) );
// Resize the layouts within the stacked widget
foreach( QVBoxLayout * layout,
windowUi_->pagerStackedWidget->findChildren<QVBoxLayout *>() )
layout->parentWidget()->setFixedSize(
(int)(scale * layout->parentWidget()->contentsRect().width()),
(int)(scale * layout->parentWidget()->contentsRect().height()) );
}
Here is a workaround:
Create a file qt.conf and add these lines to it.
[Platforms]
WindowsArguments = dpiawareness=0
Put this file in the application binary folder. That's all. It will work well but only issue is that the look will not be that crisp.
For delivering it to the customer, add this file where you have your dependency files & while creating setup, like normally you run your dependency files, run this same as well.
There are several options when dealing with high-resolution displays:
Do nothing and develop in high resolution. Eventually, a higher resolution becomes a new standard, everybody will get 13-15-17 inches. It did work in the 2000th, didn't it?
Leave it to the OS. For example, Windows has a special compatibility setting which scales everything to correct size, while keeping the application think that it renders of a low-resolution display. This is called DPI Unaware on Windows.
Try to use Qt capabilities, QT_AUTO_SCREEN_SCALE_FACTOR=0, QT_SCALE_FACTOR=1.
Use a little bit of help from Qt by setting QT_AUTO_SCREEN_SCALE_FACTOR to 1 or a correspondent AA_EnableHighDpiScaling attribute (introduced in Qt 5.6). This will scale widget sizes relative to the font size, so you only need to deal with raster images.
Turn off AA_EnableHighDpiScaling and rethink all your pixel sizes by making them relative to the font size or multiplying them over the device pixel ratio.
An improvement over a previous step: take into an account the device pixel ratio on every display, so that UI is scaled appropriately when you move it to another display.
Sources:
https://doc.qt.io/qt-5/scalability.html
https://doc.qt.io/qt-5/highdpi.html
https://learn.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows
There are many ways.
one of them is to have reference (height, width and DPI) and when you run your application on a different setup all you need is to get height, width and the logical dots per inch
you can use qreal dpi = QGuiApplication::primaryScreen()->logicalDotsPerInch();
and then you calculate the new font size by multiplying it with the calculated ratio.
qreal refDpi = 216.;
qreal refHeight = 1776.;
qreal refWidth = 1080.;
QRect rect = QGuiApplication::primaryScreen()->geometry();
qreal height = qMax(rect.width(), rect.height());
qreal width = qMin(rect.width(), rect.height());
qreal dpi = QGuiApplication::primaryScreen()->logicalDotsPerInch();
m_ratio = qMin(height/refHeight, width/refWidth);
m_ratioFont = qMin(height*refDpi/(dpi*refHeight), width*refDpi/(dpi*refWidth));
the second way, which is extremely simple, more productive and personally I use it, is simply by using stylesheets!!, and the font size won't change by your DPI scaling.
you can use font property like that for example
font: bold italic large "Times New Roman";

Choppy scrolling of QPixmap using Qt Animation Framework

I created QPropertyAnimation and connected it to my SonogramWidget that scroll a long picture vertically on animation events. The 'long picture' is composed of 100 pre-calculated QPixmap objects 1024x128 placed one after another vertically. They displayed in SonogramWidget::paintEvent() with QPainter. Drawing procedure paint not all QPixmap at once, but only visible of them, considering widget height and current vertical offset. CPU is almost free, because QPixmap is a fastest way to display a picture. There is no big calculations during scrolling, because all the 100 QPixmaps are pre-calculated and stored in memory.
I see strange effect: pulsating movement: 2 times a second the entire image slightly speed-up and moves up by 1..2 pixels faster than usual motion. The same effect when i replace Qt Animation Framework with single 60 fps QTimer and scroll the image in its SLOT.
Video: http://www.youtube.com/watch?v=KRk_LNd7EBg#t=8 (watch from 00:08; My firefox adds more chopping to video playing itself, google chrome plays the video much better).
I see the same effect for my Linux and Windows build.
SOLUTION
i figured out the issue: the "chopping" was not a bug, it was a feature! It is a feature of integer-number calculations, so sometimes we had to have different numbers for animations, like: 16,16,16,16,16,16,17,16,16,16,16,16,17,....
In the paintEvent add the following assert:
Q_ASSERT(m_animation->currentValue() == m_animatedPropertyValue);
If it triggers, then you know you must use currentValue() instead of the property value. This might be the case. Let me know.

Improving window resize behaviour, possibly by manually setting bigger framebuffer size

I was considering using glfw in my application, while developing on mac
After successfully writing a very simple program to render a triangle on a colored backround,
I noticed that when resizing the window, it takes quite some time to rerender the scene, as I suspect due to framebuffer resize.
This is not the case when I am repeating the experiment with NSOpenGLView. Is there a way to hint glfw to use bigger framebuffer size on start, to avoid expensive resizes?
I am using GLFW 3.
Could you also help me with enabling High DPI for retina display. Couldn't find something in docs on that, but it supported in version 3.
Obtaining a larger framebuffer
Try to obtain a large initial frame-buffer by calling glfwCreateWindow() with large values for width & height and immediately switching to displaying a smaller window using glfwSetWindowSize() with the actual initial window size desired.
Alternately, register your own framebuffer size callback function using glfwSetFramebufferSizeCallback() and set the framebuffer to a large size according to your requirement as follows :
void custom_fbsize_callback(GLFWwindow* window, int width, int height)
{
/* use system width,height */
/* glViewport(0, 0, width, height); */
/* use custom width,height */
glViewport(0, 0, <CUSTOM_WIDTH>, <CUSTOM_HEIGHT>);
}
UPDATE :
The render pipeline stall seen during the window re-size(and window drag) operation is due to the blocking behavior implemented in the window manager.
To mitigate this in one's app, one needs to install handler functions for the window messages and run the render pipeline in a separate thread independent from the main app(GUI) thread.
High DPI support
The GLFW documentation says :
GLFW now supports high-DPI monitors on both Windows and OS X, giving
windows full resolution framebuffers where other UI elements are
scaled up. To achieve this, glfwGetFramebufferSize() and
glfwSetFramebufferSizeCallback() have been added. These work with
pixels, while the rest of the GLFW API work with screen coordinates.
AFAIK, that seems to be pretty much everything about high-DPI in the documentation.
Going through the code we can see that on Windows, glfw hooks into the SetProcessDPIAware() and calls it during platformInit. Currently i am not able to find any similar code for high-DPI support on mac.