Seaside Laboratory

Post

EnumDisplaySettings は同じ設定を複数返すことがある

サポートしている画面モードを取得するには EnumDisplaySettings を使用する。

BOOL EnumDisplaySettings(
  LPCTSTR lpszDeviceName,  // ディスプレイデバイス
  DWORD iModeNum,          // グラフィックスモード
  LPDEVMODE lpDevMode      // グラフィックスモードの設定
);

マニュアルに、

まず、iModeNum パラメータを 0 に設定して最初の呼び出しをします。
その後、戻り値が 0 になるまで、iModeNum パラメータに 1 ずつ加算して繰り返し呼び出します。

と書かれているように iModeNum を加算しながら呼び出すことで全モードを取得することができる。

設定値は引数 lpDevMode に書きこまれるが、

EnumDisplaySettings 関数は、次の 5 つの DEVMODE メンバに値を設定します。

dmBitsPerPel
dmPelsWidth
dmPelsHeight
dmDisplayFlags
dmDisplayFrequency

有効なのは特定のメンバ変数に限られる。

有効なメンバ変数の組み合わせで一意になるリストを生成するプログラムを書いていたのだが、同じ設定値が複数回返ってくるので重複が発生してしまった。原因がよくわからないのでデバッガー上でウォッチした構造体の全メンバーを比較してみると、dmDefaultSource と dmDisplayFixedOutput に違いがあった。

dmDefaultSource

給紙方法を指定します。
プリンタで利用可能な給紙方法の一覧を取得するには、
DC_BINS フラグを指定して DeviceCapabilities 関数を使用します。

プリンタ用の設定っぽいので無視。

dmDisplayFixedOutput

固定解像度ディスプレイデバイスの場合のみ、
高解像度ディスプレイでディスプレイが低解像度モードを表示する方法。
たとえば、ディスプレイデバイスの解像度が 1024 x 768 ピクセルに固定されていて、
そのモードが 640 x 480 ピクセルに設定されているとします。
デバイスは、1024 x 768の画面スペースの内部のどこかに 640 x 480の画像を表示するか、
または 640 x 480 の画像を拡大してより大きな画面スペースを埋めることができます。

これが原因っぽい。値の内訳は以下の通り。

意味
DMDFO_DEFAULT ディスプレイのデフォルト設定
DMDFO_CENTER 低解像度の画像は大きな画面スペースの中央に配置されます。
DMDFO_STRETCH 低解像度の画像は、より広い画面スペースを埋めるように引き伸ばされます。

物理ピクセル数より小さい画面を表示する時にどう見せるのかを指定する値ということか。たしかに列挙された画面モードを見ると、小さい解像度は 3 パターン返ってくるのに対し、液晶の最大解像度だけは DMDFO_DEFAULT の 1 パターンしか返ってこないので納得はいく。

このメンバ名で検索して出てきた「Scaling the Desktop Image」というドキュメントを読むと、

EnumDisplaySettings Win32 関数は、呼び出し側が新しい Windows 7 のスケーリングタイプを要求したときに
lpDevMode パラメータが指す DEVMODE 構造体の dmDisplayFixedOutput メンバに DMDFO_DEFAULT を返します。

DEVMODE の dmDisplayFixedOutput に値が入ると書かれている。有効になるメンバは 5 つだけじゃなかったんかい!

このドキュメントには続きがあって、DirectX と ChangeDisplaySettings を組み合わせる際の注意点が書かれている。

DirectX ゲームとスケーリング

Microsoft DirectX 9L 以前のランタイムでは、lpDevMode パラメータが指す
DEVMODE 構造体の dmFields メンバに DM_DISPLAYFIXEDOUTPUT を設定せずに、
アプリケーションで常に ChangeDisplaySettingsEx 関数を呼び出す必要があります。
DirectX 10 以降のランタイムでは、アプリケーションはそれらのアプリケーションが
ChangeDisplaySettingsEx に渡すスケーリングを選択できます。

古い DirectX を使う場合、dmDisplayFixedOutput の指定はするな、ということらしい。

とりあえず今回は画面モードが知りたかっただけなので、DMDFO_DEFAULT 以外を無視することで対処した。