§ ¶Direct3D may not show custom video modes
I spent some time today trying to get some Direct3D 9 code to switch the display into 50Hz refresh so it could display PAL video. Displaying PAL video on a 60Hz display doesn't look very good because of the difference in frame rates -- you get a beat at the difference between the rates, 10Hz in this case -- and so it's better to change the display mode to match. Problem is, a 50Hz mode wasn't showing up in the mode list for my monitor, so I opened the NVIDIA control panel and added a custom 50Hz mode. The test ran fine and the monitor happily switched into a PAL-compatible refresh rate.
Then I tried the Direct3D 9 code, and it refused to do the mode switch.
Strangely, the old ChangeDisplaySettings() call had no problem selecting the 50Hz mode and the control panel showed 50Hz available, but a check with the DirectX Caps Viewer revealed that the mode wasn't showing up in the Direct3D APIs. Checking the docs for the IDirect3D9::CreateDevice() method revealed this ominous comment:
An unsupported refresh rate will default to the closest supported refresh rate below it. For example, if the application specifies 63 hertz, 60 hertz will be used. There are no supported refresh rates below 57 hertz.
It would be disappointing if the Direct3D 9 API was ignorant of a few continents worth of video standards, but fortunately this doesn't seem to be true at least on Windows 7. Some 56Hz modes were showing up and I could switch to those just fine, so that statement appears to be wrong.
A bit of digging with WinDbg -- okay, a frustrating amount of digging -- showed that on Windows 7, d3d9.dll uses the EnumDisplaySettings() API to enumerate available video modes. This function unfortunately only returns modes that are determined to be compatible with the current monitor. Even worse, if you happen to have your desktop set to an "unsupported" resolution, Direct3D grudgingly temporarily returns the mode in its list, which can lead to confusion. I pulled the EDID stream for the monitor and it showed that the monitor was only saying that it supported refresh rates in the 56-75Hz range, even though it actually supports both 50Hz and 85Hz as well. EnumDisplaySettingsEx() with the EDS_RAWMODE flag did return the 50Hz mode, but that wouldn't help.
Plan B was to try to use the D3DPRESENTFLAG_UNPRUNEDMODE flag, which supposedly allows access to raw modes. It requires using Direct3D 9Ex instead of Direct3D 9, but fortunately my code worked both ways. Unfortunately, there still isn't a way to enumerate the hidden modes, and setting that flag didn't seem to do anything. More digging revealed that when 9Ex is in use, d3d9.dll uses D3DKMTGetDisplayModeList() to enumerate the modes instead. That function does return all modes, but farther down the line a function called d3d9!BuildModeTableLH() has a hardcoded check for the D3DKMDT_DISPLAYMODE_FLAGS::ValidatedAgainstMonitorCaps bit and drops any modes that are missing it:
66ff9fcf 8b9084941967 mov edx,dword ptr d3d9!g_ModeInfo+0x4 (67199484)[eax]
66ff9fd5 8b4508 mov eax,dword ptr [ebp+8]
66ff9fd8 f644022401 test byte ptr [edx+eax+24h],1
66ff9fdd 0f848a000000 je d3d9!BuildModeTableLH+0xa1 (66ffa06d)
66ff9fe3 8b0b mov ecx,dword ptr [ebx]
66ff9fe5 8b550c mov edx,dword ptr [ebp+0Ch]
66ff9fe8 8b02 mov eax,dword ptr [edx]
66ff9fea 57 push edi
66ff9feb 51 push ecx
66ff9fec 50 push eax
66ff9fed 56 push esi
66ff9fee e88c000000 call d3d9!AddModeEntry (66ffa07f)
This prevents 9Ex applications from switching to unvalidated modes even if the UNPRUNEDMODE flag is set. I'm not actually sure what that flag does anymore; according to Google no one seems to be publicly using this flag and maybe this is why. Hacking out the check works, but that's not a shipping solution.
And no, unchecking the "hide modes that the monitor cannot display" checkbox doesn't affect any of this. I unchecked that a long time ago.
In the end, I wasn't able to find a programmatic way to cleanly enable these modes for Direct3D 9 applications. What did work is to override the EDID stream for the monitor to enlarge the vertical refresh rate range to 50-75Hz. This requires a collection of tools and generating a replacement monitor INF, so it's also not a shipping solution, but it appears to be the only choice if the desired custom mode falls outside of the EDID specs.
While tracing through the mode enumeration logic in d3d9.dll I also discovered a weakness in its IDirect3D9::EnumAdapterModes() implementation. In Windows 7, and presumably in Vista, this function is implemented on top of the internal version of IDirect3D9Ex::EnumAdapterModesEx(), which returns an extended mode structure and has additional filtering options. The non-Ex version calls through to the -Ex version, discards modes that don't pass a default filter, and then translates the mode structure. When you realize that the API for both functions is to take a mode index, though, you might see the problem. When the non-Ex version is called requesting the Nth mode, it can't simply pass through the value N since it uses a filter, so what it does is to count from 0 on up until it finds the Nth mode that passes the filter. Unlike EnumDisplaySettings() there is no caching of the filtered mode list and therefore enumerating all modes with IDirect3D::EnumAdapterModes() is an O(N^2) operation. It's best to cache the mode list if you need it frequently.
People who are not as good in hacking into system calls could just switch to 75 Hz mode. 50 Hz display rate is bad for eyes anyway.
Kasuha - 23 08 11 - 22:50
a) Unless your video is 25FPS, this will give you even more beating (at 25 Hz) for 50FPS video
2. 50 Hz on an LCD screen hasn't been bad for anyone's eyes since the advent of LCD screens
Leak - 24 08 11 - 04:49
I hit a similar problem a while back with the laptop. My TV has a native resolution of 1366x768, and I wanted to get the laptop to provide that on the VGA connector. Unfortuantly the stock IBM drivers for the graphics chip (an Intel 852GM or 855GM) claim the hardware only supports the classic 4:3 ones.
The solution I found was to use Intel's dev kit to build your own driver package, and since this chip is intended for embedded use the packager lets you specify whatever monitor timings you want. Even better, it actually lets you just enter 1366x768 (though I think it may have needed 1368 due to alignment) and the packager will work out the rest of the timings for you.
@Kasuha: that requires your display to support 75Hz. Some LCDs max out at 60Hz.
Torkell (link) - 24 08 11 - 05:23
What Leak said -- plus, 50 Hz isn't all that worse for the eyes. After all, millions of Europeans were exposed to 50 Hz field rate on CRT TVs for multiple decades and you'd be hard pressed to find evidence that their eyes suffered from significantly more strain than those from Americans or Japanese :)
KeyJ (link) - 24 08 11 - 05:31
I can confirm, 50Hz bob-mode on 60Hz has a very visible jitter. Every LCD television I've ever seen offered 50Hz, but none of the regular PC monitors.
Gabest - 24 08 11 - 06:59
Try an Amiga in 50i mode on a CRT with NTSC phosphors, and we'll see how your eyes fare....
LCD screens go below 50Hz. My current laptop goes to 40Hz in power saving mode without visible effects other than jitter on 50/60Hz material. It's a bit evil though because some of the APIs lie about refresh rate when these low-refresh power saving modes are used and then vsync algorithms fail horribly.
Phaeron - 24 08 11 - 09:06
I'm one of those Europeans who was looking at 50 Hz TV for over 40 years (and at 50 Hz "TV monitors" attached to computers for over 10 years too) so I know what I'm talking about. A CRT is clearly blinking if you are not looking straight into it which may get very annoying, and it's tiring even if you don't see the blinking. On LCDs it may be different, depending on how fast they are. But if the LCD is slow enough to display at 50 Hz without visible blinking then I believe it's blending frames enough at 75 Hz to not show any beating too. And if it's fast then 75 Hz is clearly better.
100 Hz would be best for that purpose but I'm afraid not many LCDs can do it.
Well... that's for perfectionists. Personally I'm watching PAL videos on a 60 Hz LCD quite often and I don't complain.
Kasuha - 25 08 11 - 07:21
LCDs don't blink - their picture is static (though they do use dithering to display certain colours, but that's independent of the input refresh rate; however due to this dithering you can get blinking effects with certain patterns).
ender - 28 08 11 - 04:07
There's something seriously broken in Windows 7 that causes problems when trying to use it with 15KHz arcade monitors and the MAME emulator.
1) For some reason, Windows 7 doesn't allow applications to switch from interlaced to non-interlaced resolutions or vice verse. This worked on XP. I'm not sure about Vista.
2) Windows 7 reports refresh rates at 50% of their actual value when running an interlaced resolutions. This causes vector based games, that are typically run at 800x600 interlaced, to run at half-speed.
At the moment, for running the MAME emulator in an arcade cabinet on a 15KHz arcade monitor, the best OS is still Windows XP x64, believe it or not.
krick (link) - 28 08 11 - 15:46
I haven't tried this in Windows 7 / Vista, but that Direct3D filtering resolutions issue seems to have been there since Windows XP at least. I found that problem also when trying to make Mame accept non standard resolutions if a monitor with a valid EDID is attached.
There is a workaround however, provided you want to support the old DirectDraw interface in your app. The IDirectDraw7::SetDisplayMode method is able to switch to any video mode returned by EnumDisplaySettingsEx with the EDS_RAWMODE flag set. Unfortunately, I couldn't find a way to force Direct3D to work with that mode afterwards, so you need to stick with DirectDraw.
Finally, there is a way you can send any arbitrary refresh rate to your monitor, completely bypassing Windows. You need to interface with PowerStrip's API for that. Actually, from Windows point of view, refresh rate values are just labels returned by the video driver which can have anything behind, so Windows itself is not "aware" of the actual refresh rate but for the value the driver says it is. So you can just enable standard 1920x1080@60 and then use PowerStrip's API to reprogram the video mode on the fly, if you know how to do this. We're just starting to experience with this functionality here:
Calamity - 28 08 11 - 22:49
I used to hack the monitor settings (in the registry) on each of my PC.
I tweaked it to say the monitor supports 85 Hz minimum (CRTs).
Otherwise Windows (XP and 2000 and 20003) defaulted to flickering 60 HZ each time a program (games mostly) used a new resolution.
David Balažic - 03 09 11 - 01:36
Please keep comments on-topic for this entry.
If you have unrelated comments about VirtualDub, the forum is a better place to post them.