DPI Scaling in HaxeFlixel (and Lime/OpenFL)

Over on the Haxe Discord server, a topic that seems to come up a lot with HaxeFlixel games is the issue of screen DPI scaling. This page aspires to deal with most of the common issues people find, but is not an exhaustive resource. The information in this post may not be entirely accurate, but instead mostly correct to the best of my knowledge.

What is DPI scaling?


First, DPI. DPI stands for “dots per inch”, meaning display pixels per physical inch of a screen (this is also technically the meaning of “display resolution”, but almost nobody uses it that way). It can also sometimes be called PPI, or “pixels per inch”. It’s useful to know because it can vary greatly between different types of displays your player or user may have on their device. As the name suggests, you can work out the DPI by calculating number of horizontal pixels / width of screen in inches = DPI; for example, a 1280x720 screen with a physical width of 6.1” gives a DPI of 1280 / 6.1 = 209.8 (to 1 decimal place). We use 6.1” as the width in that calculation because display manufacturers advertise the diagonal length of the screen because it’s a bigger number and sounds better, and a 16:9 ratio display with 7” screen has roughly 6.1” width.

Previous systems

Second, a quick flashback. Back in the ancient days just before smartphones took over the world, most personal computing was done on desktop or laptop PCs. The overwhelming majority of desktop OS market share belonged to Microsoft Windows, likely either XP or Vista, and would probably be used with a 4:3 or 16:9 monitor of at least 1024x768 (or 1280x720 for 16:9).

On versions of Windows prior to Windows 8, it was a pretty good bet that the vast majority of people’s displays would be big enough to use comfortably at the expected usage distance and resolution. Crucially, Windows itself never really bothered to tackle the assumption that it might be different until Windows 8. If you couldn’t read text, you either increased the font size or lowered the resolution of your display until you could. For this reason, Windows assumed a default DPI value of 96 for the vast majority of situations (some exceptions include graphic design and printing, but that’s a whole other set of problems).

Enter the smartphone

Smartphones and PDAs had been around for a long time before then, but were mostly a business toy or niche use cases. Once Apple’s iPhone took off, complete with its own marketing hype, suddenly regular users were using these small, handheld devices to access the web and expecting things to work. For reference, the first iPhone had a display of 480x320 pixels, with a diagonal screen length of 3.5”. That’s a DPI of roughly 163, which, while quite low compared to some devices available today, is still quite a good resolution for such a small device. A few generations later, the pixel dimensions doubled in both axes, to 640x960.

If you still assume a screen DPI of 96, then your programs’ text would be very small indeed on such a small device. Text that might be readable on a 20” desktop monitor running at 1920x1080 might be unreadable if it ran at 3840x2160 without scaling, or on a 1920x1080 screen that was only 5” diagonal without scaling.

Badly-drawn image depicting a desktop monitor, a laptop and a smartphone all displaying the same program. The laptop shows the program at a smaller physical width, whereas the smartphone shows it bigger for the user.

DPI Scaling

DPI scaling was introduced to scale up resources and text so that the user can still read and see things without a magnifying glass. In Windows 8 onwards, for programs that didn’t declare themselves as DPI-aware, Windows would scale the program up to more closely match the user’s display DPI. This would almost always end up blurry due to filtering, unless it were a clean number like 200% or 300% scaling. You can still see this in older programs on Windows, and even on Windows 10 going to certain settings dialogues, where text and images appear fuzzy. However, the alternative would be that programs remain unscaled and likely unreadable on more modern high-resolution displays.

The idea is that a DPI-aware application includes higher-resolution resources and draws text at a larger font size, while taking these scaled resources into account when drawing the UI so that you don’t end up with tiny buttons with huge text or other problems. That way, on higher-resolution displays with high DPI, your application remains usable, readable and looks good.

Scaling in HaxeFlixel

By default, Lime doesn’t declare project exports as DPI-aware. Presumably this is to avoid the problems explained earlier, if you as a developer don’t take precautions to properly scale things up. This isn’t so clear-cut an assumption when it comes to games, particularly HaxeFlixel games, since they have their own scaling by default. They can look blurry because, since they are not declared DPI-aware to the OS, HaxeFlixel sets the window size to your desired resolution, and then it gets scaled by the OS or desktop environment.

You can avoid this double scaling by declaring your project as DPI-aware in Project.xml, by changing the target settings to add allow-high-dpi:

<window if="desktop" orientation="landscape" fullscreen="false" resizable="true" allow-high-dpi="true" />
Note: There's currently no support for declaring a Windows export as DPI-aware for the CPP target. This should be coming in a later version of Lime. Linux and macOS exports should correctly report as DPI-aware. HashLink's Windows executable already sets the DPI-aware bit, so no problems there. As a work-around for `CPP` exports, if you or your players want to get proper DPI-awareness, you'll have to change the DPI settings of the executable in the Properties dialogue, setting it to `Application`.

Compile your project again and hopefully your window will appear smaller but less blurry. If you go fullscreen in your game, there should be no scaling other than that which HaxeFlixel does according to the scale mode.