08/16/2016

12 minutes to read

In this article

Today we have guest author for this blog. Peter Felts is a Senior Program Manager in the developer platform group and is going to discuss Display Scaling improvements and changes with Windows 10 Anniversary Update (version 1607)

Overview

Steve Wright's previous blog post about display scaling for high dots-per-inch (DPI) displays in Windows 10 does a great job of giving an overview of the concepts of how Windows handles DPI scaling. In this article I'm going to focus more on the technical side of what we've been working on for the Windows 10 Anniversary Update to help improve the display-scaling story for desktop applications. Note that most of what this article discusses does not apply to Universal Windows Applications (UWA) as they already handle display scaling well.

During Windows 10 significant work was done to improve the display-scaling story for Windows itself, which Steve's article covers. While this resulted in an improved experience for some of the in-box UI of Windows itself and for UWA, many third-party (and Microsoft's own) desktop applications were not able to benefit from this work and could still display blurry or sized incorrectly in some common scenarios. For the Windows 10 Anniversary Update we wanted to tackle this problem so we focused on making it easier (and less expensive) for software developers to update their desktop applications to scale properly.

Problem Statement:

As was discussed in Steve's article, many desktop applications do not render well on some of the latest high DPI displays. There are three symptoms of display-scaling problems we typically see with desktop applications:

1. Blurry text and UI components.

2. Applications sized incorrectly (too big or too small).

3. Applications are sized correctly and are not blurry, but have other layout issues (such as clipped text or other UI components).

These problems are most frequently seen whenever the display scale factor of a Windows PC changes while the user is logged in and/or if an application is moved from the “main display” to a display that has a different display scale factor.

One very common scenario where applications start to experience these problems is when a device with a high display scale factor (say 200% display scaling) is docked or undocked with an external display that has a different display scale factor (and the external display is used as the “main display” or the PC uses “Second screen only” display mode). In this scenario applications render as expected on the internal display (#1 below) before the PC is docked but once connected to the external display they are stretched by Windows such that they are sized correctly on the external display (#2 below). This stretching results in the application looking blurry. At this point the only thing that the end user can do is to close all of their applications and completely log out and back into Windows. Once the user has logged out and logged back into Windows, most applications should render correctly on the external display (#3 below). Needless to say this is not an acceptable workaround as it interrupts a user’s workflow. To add insult to injury, if the user does completely log out and back into Windows, once they un-dock their device the same problem will occur in the reverse (#4 below). This scenario forms the cycle shown below:

Background:

There are many reasons why some desktop applications do not render correctly, but at a high-level one of the biggest challenges desktop applications face in this space is that many apps were written without considering that the display factor on Windows could change while the app was running, and so they don’t respond to those changes. This is true if you don't ever connect an external display that has a different display scale factor, remote into Windows from a device with a different display scale factor, or change the display scaling settings. Once any of these things happen though, the scale factor of the system is suddenly different from what the application was told it was when it launched, and applications that are not expecting the display-scale factor to change are not made aware of this change, and therefore they do not know that they should respond. When this happens, Windows jumps in and stretches the on-screen image of the application such that it will be sized appropriately for the new scale factor. This, at least, results in applications being physically sized correctly on a display but they can be blurry as result of being stretched.

PCs today are being equipped with displays with increasingly high pixel densities, also referred to as dots-per-inch (DPI). The Surface Pro 4, for example, ships with a display that accommodates a 200% display scale factor. This means that if you were to connect a Surface Pro 4 to a “standard” external display (a display with 96 DPI or 100% display scaling) there would be a 2-to-1 difference between the scale of an application on the Surface display and that of when it was rendered on the external display. For applications that don’t handle dynamic display scaling, this means that the image of the application shown on screen would either be reduced by half when displayed on an external display or doubled (depending on which monitor was configured as the “main display” when the user logged into Windows). As the difference between the display scaling of monitors increases the blurriness of applications becomes more and more noticeable. This is a problem that is only going to get worse as display manufactures produce displays with even higher DPI.

While it is technically possible for legacy desktop applications to be updated to understand the concept that the display scale factor can change at any time, it is clear that not all applications will be updated and that some many never be updated for this. Furthermore, until the Windows 10 Anniversary Update, Windows did not even offer enough of the functionality that an application developer required in order to do this work. So much key functionality was missing that it was not practical for developers to update their desktop applications in many cases, even if the will was there to do so. So, this is what we focused on for the Windows 10 Anniversary Update.

During the development cycle for the first Windows 10 release we started to tackle this problem by updating the Windows File Explorer application to dynamically handle a display-scale-factor change. Through this process we learned a great deal about the type of challenges Windows desktop developers will hit when trying to update their applications, and we wanted to address as many of those as we could.

Why doesn’t Microsoft Just Fix Display Scaling on Windows?

This is a valid question that many of us have asked ourselves when joining teams that are working on this problem space. The main challenge that we face, however, is that many, many, applications that run on Windows are using a design pattern where they ask Windows for information about the system when they launch (questions such as how big the display is, what is the display scale factor, what is the size of the font that should be used for default text, as well as others) and then cache this information and never expect it to change. Because of this, even if Windows did start giving these applications information about a DPI change, most, if not all, of these applications wouldn’t even be asking and therefore would not respond correctly. Furthermore, if Windows did start providing dynamic display-scale-factor-related information this would be a nightmare for application compatibility and would probably cause more problems for application stability than it would help in terms of high DPI display issues.

RS1 Improvements

Non-client area scaling

One of the first (and biggest) blockers than any desktop application developer runs into when they try to update their application to handle dynamic display-scale changes is that what we refer to as the "non-client area" of a window does not respond to scale-factor changes. The term non-client area (NCA) refers to parts of a typical desktop application window that the application itself does not draw… such as the title/caption bar, system menus, traditional menu bars (such as in Notepad), scrollbars, and other UI that application developers require the system to handle on their behalf. In other words: all the standard "Windows stuff" that make up a typical desktop application window, but that aren’t drawn by the applications themselves.

Before the Windows 10 Anniversary Update, if an application developer tried to update their desktop application to respond to a display-scale-factor changes, they would soon discover that the NCA would not resize when the scale factor changed. This meant that their application would have undersized or oversized titlebars when the scale-factor changed (Figure 1). This is not something that application developers could live with and the only option available to them to address this was to have the developer handle all of the drawing of these UI components themselves, which is a prohibitively-expensive proposition for most developers (note that some applications, such as Google Chrome and the FireFox browser do draw most of the UI that is typically NCA themselves, because they have highly-stylized application UI).

Figure 1. Non-Client Area not scaling for DPI (left) and scaling correctly (right)

For the Windows 10 Anniversary Update we now support automatic scaling of NCA via use of a new "EnableNonClientDpiScaling" API.

Mixed-Mode DPI scaling

One lesson that we learned while making File Explorer dynamically handle display-scale-factor changes was that the current model for an application to tell Windows how it wanted to handle display scaling was too inflexible for complicated applications. The model has been that an application would either tell Windows that it knew how to scale when it started (System DPI awareness), that it could handle dynamic display-scale factor changes (Per-Monitor DPI awareness), or that it would say nothing and Windows would stretch/scale it appropriately.

This is an adequate model for simple applications, or applications that are being created from scratch, but when a developer tries to update even a moderately-complicated application with many windows, they'll be in a position where they have to update all of their UI... an all-or-nothing proposition. They either update all of their UI or live with some UI not rendering at the correct size. For any application with many windows this could become a ton of work quickly. Also, developers for applications that present third-party content (such as plugins) might not even have access to the source code for this content, so handling the display scaling for these windows wouldn't even be an option.

To make it easier for desktop applications to be updated to handle display scaling well, we realized that this had to be changed. So we've broken the process-wide constraint on an application's display-scaling mode such that developer can now specify a different scaling mode for each (top-level) window. In other words: developers can focus their development time on making the important parts of their UI handle display scaling well, while letting Windows handle the other windows in the application. The API that enables this functionality is SetThreadDpiAwarenessContext. This should significantly reduce the cost for developers to update their desktop applications.

Figure 2 and Figure 3 show an example of an application that utilizes this functionality to make its primary UI render crisply while having Windows handle DPI scaling of less-frequently used UI. Notepad’s primary window renders natively at the DPI of the display it’s primarily located on while the Print dialog is scaled by Windows (and may be blurry). Figure 3 shows a close-up of the two windows, showing that the system-scaled Print dialog is somewhat blurry while the Notepad UI is crisp:

Figure 2. Notepad's primary window is natively scaling while Windows is scaling the Print dialog

Figure 3. Close up of the Notepad window and the system-scaled Print dialog

Office

Some of the biggest feedback we've received about display scaling has been related to Lync/Skype for Business and PowerPoint being sized incorrectly in scenarios such as docking a Surface Pro, Surface Book, or any high-DPI device to a standard DPI display (or any display with a different display scale factor). I'm happy to say that with the new functionality (mentioned above), that is part of the Windows 10 Anniversary Update, the Office team is now working on updates to these applications that will enable them to render at the correct size when the display scale factor changes (the updates to these Office applications will only apply when running on PCs with the Windows 10 Anniversary Update (or newer)).

WPF

Windows Presentation Framework (WPF) is a heavily used application framework used for making many desktop applications. Unfortunately, WPF applications hit the same problems with non-client area (NCA) scaling as other desktop applications did (mentioned above) when they were updated to handle display-scale-factor changes on the fly. For the Windows 10 Anniversary Update WPF is being updated to support automatic NCA scaling.

What we didn't get to:

For the Windows 10 Anniversary Update we focused on some of the biggest rocks that needed to be moved in order to make it easier for developers to update desktop applications to handle dynamic display-scale-factor changes, but there are still more things we need to tackle:

Desktop Icon Scaling

In previous Windows releases, desktop icons would not scale properly when the scale factor changed (they would be too big or too small in some scenarios). We've improved this for many common scenarios such as docking and undocking with displays that have a different scale factor, but desktop icons still do not scale on a per-display basis if you are in "extend" display mode. This means that if a user has their desktop spread across displays ("extend" display mode) with different scale factors, the icons will be sized incorrectly on some displays.

Common Control scaling and WinForms

Unfortunately, we weren't able to deliver per-monitor display-scale-factor scaling support for Win32 common controls in the Windows 10 Anniversary Update. Application developers that want to create a native Win32 application that uses common controls and want to natively scale the controls will still face challenges due to lack of support for this in Windows.

WinForms is a very widely used framework for creating desktop applications, which is partially built upon Win32 common controls. Unfortunately, WinForms controls have not been updated to support dynamic display-scale-factor changes.

Addressing the need to log out and log back into Windows after a display-scale factor change

Due to the common architecture of applications asking Windows what the display-scale factor is once at startup, and not asking again while they're running, often the only way to have an application pick up the new display-scale factor is to log out of Windows and log back in. Until we find a way to work around the constraints that this pattern imposes on applications, users will continue to have to log out and log back into Windows, unfortunately.

Conclusion

For the Windows 10 Anniversary Update our goal was to make it easier and less expensive for application developers to update their desktop applications to handle display-scale-factor changes while they're running, so that they don't show up blurry or sized incorrectly in common use cases. Hopefully developers will find this work useful and we'll start to see more desktop applications updated to render correctly. We've still got a lot of work ahead of us in the high-DPI space until we get to a point where most desktop applications scale properly, but we recognize how critical this is for Windows users. We feel the same pain ourselves.