Seaside Laboratory

Posts

Direct2D と DPI スケーリング (2)

以前、Direct2D で描画した内容がウィンドウからはみ出してしまう問題が発生したときに、Windows における DPI の扱いや対策について「Direct2D と DPI スケーリング」という記事にまとめたが、DPI スケーリングによって引き起こされる問題はまだ残っていたので、アプリケーションの高 DPI 対応についてもう一度調べることにした。

レンダーターゲットのはみ出し

はみ出しはウィンドウとレンダーターゲットのサイズが異なっていたことに因るものだったが、そもそもサイズに差が出るような状況は発生するのか、という疑問がある。ウィンドウは DPI 仮想化によるビットマップストレッチ (引き伸ばし) が行われ、レンダーターゲットは表示スケールに応じたサイズに調整される。最終的にどちらも同じ倍率で引き伸されるため、理論上差は生じない。実際、Windows 10 で問題となったアプリケーションを走らせると表示は正常に行われた。

何が問題だったのかよく分からなくなってしまったので、以前動作確認を行った Windows 8.1 が入っている PC を引っ張り出し、アプリケーションを実行してみたところ、問題の事象を確認することができた。Windows 8.1 (またはそれ以前の OS) の表示スケール機能は対象が限定的で、ウィンドウは表示スケールの影響を受けることなく指定したサイズで作成されるが、レンダーターゲットは Direct2D が持つスケーリング機構によってサイズが変わってしまう、というのが原因だった。

ウィンドウのはみ出し

レンダーターゲットのはみ出しは DPI を 96 に固定することで解決できたが、Windows 10 上でフルスクリーン表示にすると今度はウィンドウがはみ出してしまう。フルスクリーンはディスプレイの解像度と同じサイズのウィンドウを作ることで実現しているが、ウィンドウは DPI 仮想化によるビットマップストレッチが行われるので、拡大した部分は画面外にはみ出してしまう。

DPI 仮想化は、高 DPI に対応していないアプリケーションに表示スケールを強制的に適用する仕組みなので、アプリケーションが高 DPI に対応していればビットマップストレッチは行われない。なら、高 DPI に対応すればよいという話になるが、アプリケーション側で GUI コントロールやフォントを表示スケールに応じたサイズに切り替え、拡大されても高品質なレンダリングが行えるようにするには、それ相応の手間がかかってしまう。

仮に、高 DPI 対応を謳いながら今まで通りのレンダリングを行ったらどうなってしまうのか。単にビットマップストレッチが行われない従来通りのアプリケーションが出来上がる。つまり、「高 DPI 対応」と宣言しておくだけで煩わしい問題を回避することができる。

高 DPI 対応アプリケーションへの変更

アプリケーションが高 DPI に対応していることを宣言する方法は 2 つある。

プログラムから設定

Windows Vista で追加された SetProcessDPIAware 関数を呼び出すことで、高 DPI に対応していることを宣言できる。ただ、Note に、

It is recommended that you set the process-default DPI awareness via application manifest, not an API call. See Setting the default DPI awareness for a process for more information. Setting the process-default DPI awareness via API call can lead to unexpected application behavior.

API 呼び出しではなくアプリケーションマニフェストを介してプロセスの既定 DPI 認識を設定することをお勧めします。詳細については「プロセスの既定 DPI 認識の設定」を参照してください。API 呼び出しを介してプロセスの既定 DPI 認識を設定すると、アプリケーションが予期しない動作をする可能性があります。

と書かれているように API による DPI 認識設定は推奨されていない。

また、SetProcessDPIAware 関数とマニフェストの関係については、PDC 2008 の資料に詳しく書かれている。

アプリケーションのアセンブリマニフェストに <dpiAware> 要素を追加することにより、アプリケーションが DPI 対応であることを宣言できます。user32.dll モジュール (Windows のユーザーインターフェイス機能を提供するモジュール) によって、アプリケーションの DPI 対応性設定がチェックされます。アプリケーションが DPI 対応であることが判明すると、アプリケーションの代わりに user32.dll モジュールが SetProcessDPIAware 関数を呼び出します。

アプリケーションマニフェスト

Visual Studio でビルドしたアプリケーションにはマニフェストが自動的に埋め込まれる。以下は実行ファイルから抜き出したもの。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
    <security>
      <requestedPrivileges>
        <requestedExecutionLevel level="asInvoker" uiAccess="false" />
      </requestedPrivileges>
    </security>
  </trustInfo>
</assembly>

マニフェストの設定は manifest ファイルをリソースとして埋め込んだり、ツールを使って後から追加したりと様々な方法が用意されているが、マニフェストの書式は複雑なのでプロジェクト設定を変更してやるのが一番手っ取り早い。[構成プロパティ]>[マニフェストツール]>[入出力] にある「DPI 認識」を「高い DPI 認識」に設定すると、

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
    <security>
      <requestedPrivileges>
        <requestedExecutionLevel level="asInvoker" uiAccess="false"></requestedExecutionLevel>
      </requestedPrivileges>
    </security>
  </trustInfo>
  <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
    </windowsSettings>
  </application>
</assembly>

dpiAware 要素が追加される。

設定値「高い DPI 認識」は、DPI 認識モードにおける「システム DPI 対応」のことを指していて、詳細については「High DPI Desktop Application Development on Windows」に書かれている。

Desktop applications that are system DPI aware typically receive the DPI of the primary connected monitor as of the time of user sign-in. During initialization, they lay out their UI appropriately (sizing controls, choosing font sizes, loading assets, etc.) using that System DPI value. As such, System DPI-aware applications are not DPI scaled (bitmap stretched) by Windows on displays rendering at that single DPI. When the application is moved to a display with a different scale factor, or if the display scale factor otherwise changes, Windows will bitmap scale the application's windows, making them appear blurry. Effectively, System DPI-aware desktop applications only render crisply at a single display scale factor, becoming blurry whenever the DPI changes.

システム DPI 対応のデスクトップアプリケーションは、通常、ユーザーがサインインした時に接続されているプライマリモニターの DPI を受け取ります。初期化中、そのシステム DPI 値を使用して UI を適切にレイアウトします (コントロールのサイズ変更、フォントサイズの選択、アセットの読み込みなど)。そのためシステム DPI 対応アプリケーションは、その単一 DPI で表示されるディスプレイ上では Windows によって DPI スケーリング (ビットマップストレッチ) されません。アプリケーションがスケールファクターの異なるディスプレイに移動した場合、またはスケールファクターが変更された場合、アプリケーションのウィンドウは Windows によってビットマップスケールされ、ぼやけた表示になります。事実上、システム DPI 対応のデスクトップアプリケーションは単一のスケールファクターでのみ鮮明に表示され、DPI が変化する度にぼやけてしまいます。