Current version

v1.10.4 (stable)


Main page
Archived news
Plugin SDK
Knowledge base
Contact info
Other projects



01 Dec - 31 Dec 2013
01 Oct - 31 Oct 2013
01 Aug - 31 Aug 2013
01 May - 31 May 2013
01 Mar - 31 Mar 2013
01 Feb - 29 Feb 2013
01 Dec - 31 Dec 2012
01 Nov - 30 Nov 2012
01 Oct - 31 Oct 2012
01 Sep - 30 Sep 2012
01 Aug - 31 Aug 2012
01 June - 30 June 2012
01 May - 31 May 2012
01 Apr - 30 Apr 2012
01 Dec - 31 Dec 2011
01 Nov - 30 Nov 2011
01 Oct - 31 Oct 2011
01 Sep - 30 Sep 2011
01 Aug - 31 Aug 2011
01 Jul - 31 Jul 2011
01 June - 30 June 2011
01 May - 31 May 2011
01 Apr - 30 Apr 2011
01 Mar - 31 Mar 2011
01 Feb - 29 Feb 2011
01 Jan - 31 Jan 2011
01 Dec - 31 Dec 2010
01 Nov - 30 Nov 2010
01 Oct - 31 Oct 2010
01 Sep - 30 Sep 2010
01 Aug - 31 Aug 2010
01 Jul - 31 Jul 2010
01 June - 30 June 2010
01 May - 31 May 2010
01 Apr - 30 Apr 2010
01 Mar - 31 Mar 2010
01 Feb - 29 Feb 2010
01 Jan - 31 Jan 2010
01 Dec - 31 Dec 2009
01 Nov - 30 Nov 2009
01 Oct - 31 Oct 2009
01 Sep - 30 Sep 2009
01 Aug - 31 Aug 2009
01 Jul - 31 Jul 2009
01 June - 30 June 2009
01 May - 31 May 2009
01 Apr - 30 Apr 2009
01 Mar - 31 Mar 2009
01 Feb - 29 Feb 2009
01 Jan - 31 Jan 2009
01 Dec - 31 Dec 2008
01 Nov - 30 Nov 2008
01 Oct - 31 Oct 2008
01 Sep - 30 Sep 2008
01 Aug - 31 Aug 2008
01 Jul - 31 Jul 2008
01 June - 30 June 2008
01 May - 31 May 2008
01 Apr - 30 Apr 2008
01 Mar - 31 Mar 2008
01 Feb - 29 Feb 2008
01 Jan - 31 Jan 2008
01 Dec - 31 Dec 2007
01 Nov - 30 Nov 2007
01 Oct - 31 Oct 2007
01 Sep - 30 Sep 2007
01 Aug - 31 Aug 2007
01 Jul - 31 Jul 2007
01 June - 30 June 2007
01 May - 31 May 2007
01 Apr - 30 Apr 2007
01 Mar - 31 Mar 2007
01 Feb - 29 Feb 2007
01 Jan - 31 Jan 2007
01 Dec - 31 Dec 2006
01 Nov - 30 Nov 2006
01 Oct - 31 Oct 2006
01 Sep - 30 Sep 2006
01 Aug - 31 Aug 2006
01 Jul - 31 Jul 2006
01 June - 30 June 2006
01 May - 31 May 2006
01 Apr - 30 Apr 2006
01 Mar - 31 Mar 2006
01 Feb - 29 Feb 2006
01 Jan - 31 Jan 2006
01 Dec - 31 Dec 2005
01 Nov - 30 Nov 2005
01 Oct - 31 Oct 2005
01 Sep - 30 Sep 2005
01 Aug - 31 Aug 2005
01 Jul - 31 Jul 2005
01 June - 30 June 2005
01 May - 31 May 2005
01 Apr - 30 Apr 2005
01 Mar - 31 Mar 2005
01 Feb - 29 Feb 2005
01 Jan - 31 Jan 2005
01 Dec - 31 Dec 2004
01 Nov - 30 Nov 2004
01 Oct - 31 Oct 2004
01 Sep - 30 Sep 2004
01 Aug - 31 Aug 2004


Powered by Pivot  
XML: RSS feed 
XML: Atom feed 

§ Implementing per-monitor DPI awareness

Windows 8.1 will soon offer the ability to use different DPI scaling settings for different monitors. This is a cool feature, but one that will need some fixup in existing programs, particularly since the virtualization method that scales up existing programs looks rather ugly. Below is a dump of everything I've encountered while implementing per-monitor DPI awareness in an existing program.

Fair warning: this is based on the publicly available Windows 8.1 Preview (build 9431), so behaviors may change in the final (RTM) release.

Testing per-monitor DPI

Obviously, it's inconvenient to set up a new physical system with multiple monitors in order to test. A virtual machine is more convenient for this, and fortunately, Oracle VirtualBox works well. You need a recent version in order to boot Windows 8.1 Preview and an even newer version for the Additions to load so that multi-monitor support works. As of this writing, the latest version is 4.2.16 and it successfully boots 8.1 Preview on the VirtualBox WDDM driver.

Detecting 8.1 Preview

I learned the hard way a while back that it's not a good idea to try to load system DLLs without checking the OS version first. I know, Microsoft's recommended strategy is to load the system DLL in question and then doing GetProcAddress() to check for functionality instead of checking OS version, but that has two ugly problems. The first is that unless care is taken, the LoadLibrary() call can pull a different but same-named DLL from the current directory on downlevel OSes that don't have the system DLL. The second is that there have been problems with people somehow getting DLLs from newer versions of Windows into the PATH and even system32 directories of older systems, particularly DWMAPI.DLL on XP, resulting in bizarre-looking DLL load error dialogs. For this reason, I now do an OS version check first before attempting a LoadLibrary() from the system directory.

GetVersionEx() works as usual to check the OS version, with the minor problem that it lies by default in Windows 8.1 Preview and reports the same version as Windows 8: 6.2. To fix this, a Windows 8.1 compatible entry must be added to the application manifest. This isn't always possible, particularly if you're in a plug-in DLL or otherwise don't have access to the manifest. It's unclear if there is an official way to bypass the version lie, but the new version helpers use VerifyVersionInfo() under the hood rather than GetVersionEx().

Adapting to per-monitor DPI

In order to bypass virtualization and allow your program to directly handle the different DPI settings on each monitor, you need to tell Windows that you're DPI-aware. This is documented in Writing DPI-Aware Desktop Applications in Windows 8.1 Preview. The short version is that you need to put a declaration in the application manifest, similarly to the way DPI awareness declaration works in Windows Vista. In case it's not clear from the doc, the string “True/PM” is literally what you put inside the <dpiAware> element.

It's also possible to call an API to declare DPI-awareness, but I wouldn't recommend it as it needs to be called before virtualization starts, and a surprising amount of UI code can execute before WinMain() starts due to DLL dependencies.

Once the declaration is in place, it's shockingly easy to get basic support working: simply handle the WM_DPICHANGED message to resize and redraw the window. No need to track a resolution independent size, watch for monitor crossing, implement hysteresis, or compute an appropriate new size – the window manager handles all that for you. There's not even a need to do a version check or to call GetProcAddress().

Of course, it can get a little bit more complicated than that....

The global DPI setting

Enabling per-monitor DPI in Control Panel allows different DPI settings for each monitor, but despite having that enabled, there is still a global DPI setting. It's the one that's returned by GetDeviceCaps(LOGPIXELSY) and it determines the size of theme elements like window caption bars and menu text. Windows on monitors that have different DPIs than the global DPI are appropriately scaled up or down to compensate, by either image scaling when virtualization is active or by the program itself when it isn't.

There are a few nasty issues with the global DPI setting: (a) it doesn't have to match the DPI settings of any of the monitors, (b) it can be lower or greater than a monitor's DPI, and (c) it can be different each time you log in. These arise because the global DPI is selected based on the monitor resolutions when you log in or enable the per-monitor DPI mode and doesn't change when the resolution of the monitors are changed. This can lead to the situation where lowering the resolution of one monitor results in window downscaling on that monitor, but after logging out and back in, that monitor draws at 1:1 and the other monitor now has window upscaling!

The upside of this goofy behavior is that it comes in handy during testing, because you can log in with a specific setup of monitor resolutions to set the global API, then change them after the fact to test any combination of upscaling and downscaling.

3D virtualization

When DPI virtualization was introduced in Windows Vista, it affected GDI and DirectDraw rendering, but not the 3D APIs (D3D9, D3D11, OpenGL). This meant that programs that mainly just created a bare-bones window and inited D3D – particularly games – largely weren't affected.

With per-monitor mode in Windows 8.1, this is still the case for programs that do not declare themselves DPI-aware at all. However, if a program declares itself as DPI-aware in Vista style – and not per-monitor aware – then things get a bit funky. 3D output is virtualized and scaled, but unfortunately, IDirect3D9::GetAdapterDisplayMode() is not and still returns the physical display resolution. Annoyingly, this breaks my D3D9 display code when windows are downscaled. This appears to be a bug in 8.1 Preview, but unfortunately I don't have high hopes for it getting fixed for RTM.

As a side note, it looks like the DirectDraw implementation has also been changed in 8.1 Preview so that blits use bilinear filtering, like the majority of hardware drivers did on XP. If so, this is a nice bonus for older programs that are DirectDraw-based.

Non-client metrics

You've been a good programmer and used NONCLIENTMETRICS to adjust the size of text in your program, right? That way, if the user adjusts the size of caption text, palette window text, etc. in Windows, your program also follows suit. Well, no good deed goes unpunished. The issue is that the NONCLIENTMETRICS structure provides font selection information as a raw LOGFONT structure, so if you have a per-monitor DPI aware program and try using those font specifications directly, the text in your program won't scale properly with DPI changes. Doh.

It's easy to scale the font heights in the LOGFONT structures as long as the proper scale factor is available. There is a handy GetScaleFactorForMonitor() function, but it appears to always return 100% for per-monitor DPI aware programs. What does work is GetDpiForMonitor(), which can then be compared against the global DPI value returned by GetDeviceCaps(LOGPIXELSY) on an HDC from GetDC(NULL).

One oddity that I haven't been able to resolve is the scaling factor on the top-level window frame. When virtualization is enabled, the whole window is scaled, so the caption bar and menu bar also scale. Once a program is made per-monitor DPI aware, though, this stops and there doesn't appear to be compensation logic for the window manager to rescale the caption and menu bars. It looks odd to have a huge caption bar when a window is dragged to a scaled down monitor, but I haven't found a fix for this yet.

The font chooser dialog

Okay, this is getting a bit long, but as Columbo would say, there's just one more thing.

Presumably, as a good programmer, you've also been using the OS's ChooseFont() dialog for user font selection. This still works with per-monitor DPI, but with a couple of quirks. First, the choose font dialog doesn't use the current monitor DPI when displaying the font, so the font preview shows a different size than what the user will get. This is caused by it using the global DPI setting from GetDeviceCaps() instead of the per-monitor DPI. No workaround, as far as I can tell.

The second issue is related and is that the height that ChooseFont() puts into the LOGFONT structure is also scaled by global DPI. Using the point size value reported in the CHOOSEFONT structure works around this coming out of the dialog, but doesn't work going in when using the LOGFONT structure to init the dialog. The solution is to use the point size for persistence and to create fonts, but to calculate a different LOGFONT structure using the global DPI just for ChooseFont().

You might think that creating a custom font dialog would be a good workaround. It's an attractive idea, particularly since this would also allow fixes for other issues such as the inability to accept fractional point sizes. Doing so is not a good idea in Windows 7 and later, though, due to ChooseFont() having access to hidden font filtering functionality.


Comments posted:

Couldn't the DLL loading problems (the fact that you might be loading the wrong DLL) be solved by using LoadLibraryEx with the LOAD_LIBRARY_SEARCH_SYSTEM32 flag?


cdman (link) - 06 08 13 - 21:47

Is it finally possible to selectively enter power-saving mode in secondary monitors via an API now?

bLight - 07 08 13 - 23:55

LOAD_LIBRARY_SEARCH_SYSTEM32 needs a hotfix installed to be available. Not really much point when it's trivial to call GetSystemDirectory() and tack the filename on the end.

No idea on power-saving on individual monitors.

Phaeron - 08 08 13 - 14:58

For True/PM to work on Vista/7 it must be the case that the xml parser in the loader only checks for a string prefix?

WndSks (link) - 12 08 13 - 05:38

Interesting read, particularly as my team built this feature ;-) Are these changes available in the latest exp build of VirtualDub?


James Clarke - 11 09 13 - 03:01

No, not yet. I'll probably end up throwing the DPI aware switch and implement auto window resizing without necessarily doing DPI scaling everywhere -- I'm way overdue for another release and I don't think I'll want to hold it to add the necessary scaling everywhere, but the virtualization scaling and resulting D3D9 rendering bug have got to go.

Phaeron - 12 09 13 - 18:40


I hope that you have changed default Windows font size while testing this DPI aware thing?

If you did not you might want to do that (set from 100% to say 108% or 120%) and re-test to see if everything is still ok.

Igor Levicki (link) - 06 10 13 - 00:13

Is it possible to segment (in 2GB w64 files) a, say 100GB continuous w64 file, with virtualdub, without having to load the 100GB file in the ram?

Carl - 12 11 13 - 17:59

Comment form