Windows 7 におけるULWの挙動 (3)

これまでの実験で、問題の原因はアプリ描画とDWM画面更新の同期問題ではないかという結論に至った(半分くらいは勘)。

Layered Window Accelerated by Direct2D

そうして “windows 7 vsync” やら “dwm anti-tearing” やら “dwm framerate” で調べたら、今回のそもそもの目的である UWL: UpdateLayeredWindow と組み合わせた記事がMSから出ていた。

これだ!と思って早速プログラム作成。記事はソースコードがバラバラに掲載されているので苦労した。実行!やった!

……「やった!」と思ったのは間違いだった。記事の手法を使うとDirect2Dを使って超高速に描画するため、問題がなくなったように見えるだけ。結局のところ、1000fpsだと綺麗に見えるけど、60fpsではコマ落ちする。

やっぱり問題は処理速度ではなくて、描画処理のタイミングにあるようだ。なんとかしてDWMの画面更新と同期しないといけない。

Revival of Vsync

タイミングを知る方法としていくつか考えた。

  • D2DがHWNDに対してレンダリングするとき、実はVsyncするオプションがある(というか既定でする)。隠しHWNDに対して描画を行い、処理完了タイミングを計測して、画面更新タイミングを推定する。
  • DirectDrawにはVsync関係の機能があったはずなので、無理矢理利用する。

どっちも実用性に乏しい。手詰まり?

Solution

と、ここまでがこのブログ開設までのまとめ。ここで記事を書きながら「あれ?Vsyncを扱うのはD2DとかD3DだけじゃなくてDWMも扱ってね?」と気がついて、DWMの関数を洗ったら、ついに、ついに発見。

記事中にVsyncとかsynchronizeとか、それっぽい単語が一切ないのが死ねって感じ。

DwmFlush waits for any queued DirectX changes that were queued by the calling application to be drawn to the screen before returning. It does not flush the entire session rendering batch.

DwmFlushは、呼び出し元のアプリケーションによってキューされたDirectX変更が画面に描画されるまで待機します。この関数は描画バッチ全体はフラッシュしません。

いや、このAPIでうまく動くんだけど、改めて説明読んでも全然わからねーよコレ。”DirectX changes”ってなんだよ、こっちはGDIに投げてるんであってDirectXは知らんちゅーの。前述した記事でのDWM APIの紹介からも「残りの関数は省略」として省略されてたし、重要な割に情報少なすぎだろ。

ウインドウへの描画や、UWLへの書き込みの周りでDwmFlushを呼び出してやると、綺麗にアニメーションするようになります。もちろん、時刻やフレーム状態の管理など、アニメーションの基本は押さえた上でね。

Timer Interruption Accuracy

調べ物の途中でWindowsのタイマー割り込みについても一応洗ったので補足。

Windows 7から導入された Timer Coalescing が効いているかなとも思ったんだけど、そうでもないらしい。そもそもどのタイマーに効くのか良く分かんない。こいつも謎が多い機能だ。