2022/09/19(月) [n年前の日記]
#1 [prog][windows] OpenGLを使ったWindows用スクリーンセーバをC++で書いてみた
_昨日、
C言語 + OpenGL を使ってボールが箱の中を跳ね回るプログラムを書いたけれど、せっかくだからWindows用スクリーンセーバにしてみた。
使用言語が、C言語じゃなくて C++ になってるけれど、たぶんソースはほとんどC言語の書き方になってるような気がする…。
環境は、Windows10 x64 21H2 + MinGW (g++ 9.2.0) + scrnsave.h + libscrnsave.a。
動作確認に使ったハードウェア環境は以下の2つ。どちらでも動いてくれた。
動作画面は以下。YouTubeにアップロードしてみた。
使用言語が、C言語じゃなくて C++ になってるけれど、たぶんソースはほとんどC言語の書き方になってるような気がする…。
環境は、Windows10 x64 21H2 + MinGW (g++ 9.2.0) + scrnsave.h + libscrnsave.a。
動作確認に使ったハードウェア環境は以下の2つ。どちらでも動いてくれた。
- CPU: AMD Ryzen 5 5600X (6core/12thread, 3.7 - 4.6GHz) + GPU: GeForce GTX 1060 6GB。
- CPU: AMD Athlon 5350 (4core, 2GHz) + 内蔵GPU Radeon R3 (Radeon HD 8400、GCN世代)
動作画面は以下。YouTubeにアップロードしてみた。
◎ ソースその他。 :
ソースその他は、GitHub にアップロードしておいた。
_mieki256/glboundballscr: Bound ball animation screensaver win32 by using OpenGL.
Makefile もつけてあるので、MinGW が利用できる環境で make と打てばコンパイルできるのではないかと…。
実際に処理をしているソースは以下。
_glboundballscr/glboundball.cpp at main - mieki256/glboundballscr
_mieki256/glboundballscr: Bound ball animation screensaver win32 by using OpenGL.
Makefile もつけてあるので、MinGW が利用できる環境で make と打てばコンパイルできるのではないかと…。
実際に処理をしているソースは以下。
_glboundballscr/glboundball.cpp at main - mieki256/glboundballscr
◎ インストール。 :
MinGW は32bit版なので、出来上がるスクリーンセーバ(.scr)も32bit版になる。
- Windowsが64bit版、スクリーンセーバが32bit版なら、C:\Windows\SysWOW64\ 以下にコピー。
- Windowsが64bit版、スクリーンセーバが64bit版なら、C:\Windows\System32\ 以下にコピー。
- Windowsが32bit版なら、C:\Windows\System32\ 以下にコピー。
◎ 問題点。 :
一応スクリーンセーバにしてみたものの、何故かフレームレート(FPS、フレーム/秒)が低くなる問題に遭遇して結構ハマった。
_先日書いたGLUT(freeglut)使用版 は、ちゃんと60FPS出てるのだけど。スクリーンセーバ版を動かしたところ、どうも見た感じ、40FPS前後しか出てなくて…。
試しに目標FPS値を少しずつ上げていったところ、63FPSにして動作させた時、描画は60FPSよりちょっと低い程度になったので、その値のままにしてある。何故そうなるのかは分からないけど…。もし、動作がおかしい場合は、glboundball.cpp 内の #define FPS 63 が目標FPS値の指定なので、変更してコンパイル/動作確認してもらえればと…。
ちなみに、#define FPS_BAR 0 を 1 にしてビルドすれば、箱の一片の近くに赤い線が引かれて、FPSが大まかに分かるようになってる。
_先日書いたGLUT(freeglut)使用版 は、ちゃんと60FPS出てるのだけど。スクリーンセーバ版を動かしたところ、どうも見た感じ、40FPS前後しか出てなくて…。
試しに目標FPS値を少しずつ上げていったところ、63FPSにして動作させた時、描画は60FPSよりちょっと低い程度になったので、その値のままにしてある。何故そうなるのかは分からないけど…。もし、動作がおかしい場合は、glboundball.cpp 内の #define FPS 63 が目標FPS値の指定なので、変更してコンパイル/動作確認してもらえればと…。
ちなみに、#define FPS_BAR 0 を 1 にしてビルドすれば、箱の一片の近くに赤い線が引かれて、FPSが大まかに分かるようになってる。
- 箱の一辺の長さが目標FPSで、赤い線の長さが実際のFPS。
- 例えば、赤い線の長さが、箱の一辺の0.95程度の長さなら、63 * 0.95 ≒ 60FPS が実際のFPS、ということになる。
◎ 原因を想像。 :
これは想像なのだけど、スクリーンセーバに一定の時間間隔で処理をさせるための、SetTimer() という関数の精度がよろしくないのかもしれないなと。
SetTimer() は、「xxミリ秒(msec)の間隔で WM_TIMERメッセージを送ってくれ」とOSに指示を出す関数。OSは、SetTimer() で指定されたミリ秒間隔でWM_TIMERメッセージを送ってくるので…。スクリーンセーバのプログラムは、WM_TIMERメッセージが届いたら、そのタイミングで描画処理をする作りにしておく。これで、一定の時間間隔で画面描画が行われるので、アニメーションが実現できる。
ただ、Windows NT系は、SetTimer() の精度が10ミリ秒程度だよ、という話もどこかで見かけたわけで…。 *1
60FPSで動かそうとすると、SetTimer() には 1000 / 60 ≒ 16ミリ秒(16msec)を指定することになるけれど。SetTimer() の精度が本当に10ミリ秒しかないのであれば、キッチリ16ミリ秒間隔でメッセージが送られてこなくて、20ミリ秒の間隔でメッセージが送られてきそうな気がする。それでは目標とするFPSが出ないよなと。
しかし、50FPS (1000 / 50 = 20ミリ秒)や、25FPS (1000 / 25 = 40ミリ秒) を指定しても、やっぱりフレームレートが低い…。目標の2/3ぐらいのFPSになってしまう。本当に10ミリ秒の精度なんだろうかという疑問が…。
63FPS なら、1000 / 63 ≒ 15ミリ秒になるなと…。ひょっとして、最近のWindowsは、15ミリ秒とか、あるいは5ミリ秒の精度だったりするんだろうか。
ただ、そもそも、「スクリーンセーバはバックグラウンドで動いてるプログラム群の邪魔をしない作りにするべし」という話もどこかで見かけたので…。60FPSでぬるぬる動かしたいと考えること自体がアウトなのかも。例えば10FPSぐらいでカクカクと動くのが正しいスクリーンセーバの姿、かもしれないよな、とも…。
何にせよ、これで OpenGL を使ったスクリーンセーバの作り方が少し分かってきたので、3D描画をするスクリーンセーバもC/C++で書けそうな気がしてきたなと…。
SetTimer() は、「xxミリ秒(msec)の間隔で WM_TIMERメッセージを送ってくれ」とOSに指示を出す関数。OSは、SetTimer() で指定されたミリ秒間隔でWM_TIMERメッセージを送ってくるので…。スクリーンセーバのプログラムは、WM_TIMERメッセージが届いたら、そのタイミングで描画処理をする作りにしておく。これで、一定の時間間隔で画面描画が行われるので、アニメーションが実現できる。
ただ、Windows NT系は、SetTimer() の精度が10ミリ秒程度だよ、という話もどこかで見かけたわけで…。 *1
60FPSで動かそうとすると、SetTimer() には 1000 / 60 ≒ 16ミリ秒(16msec)を指定することになるけれど。SetTimer() の精度が本当に10ミリ秒しかないのであれば、キッチリ16ミリ秒間隔でメッセージが送られてこなくて、20ミリ秒の間隔でメッセージが送られてきそうな気がする。それでは目標とするFPSが出ないよなと。
しかし、50FPS (1000 / 50 = 20ミリ秒)や、25FPS (1000 / 25 = 40ミリ秒) を指定しても、やっぱりフレームレートが低い…。目標の2/3ぐらいのFPSになってしまう。本当に10ミリ秒の精度なんだろうかという疑問が…。
63FPS なら、1000 / 63 ≒ 15ミリ秒になるなと…。ひょっとして、最近のWindowsは、15ミリ秒とか、あるいは5ミリ秒の精度だったりするんだろうか。
ただ、そもそも、「スクリーンセーバはバックグラウンドで動いてるプログラム群の邪魔をしない作りにするべし」という話もどこかで見かけたので…。60FPSでぬるぬる動かしたいと考えること自体がアウトなのかも。例えば10FPSぐらいでカクカクと動くのが正しいスクリーンセーバの姿、かもしれないよな、とも…。
何にせよ、これで OpenGL を使ったスクリーンセーバの作り方が少し分かってきたので、3D描画をするスクリーンセーバもC/C++で書けそうな気がしてきたなと…。
◎ タイマーの精度について。 :
その後もググってたら、Windows7 はタイマー精度が15.6ミリ秒、という話を見かけた。
_ windows - MFCでの標準タイマーの精度をあげる方法ありますか? - スタック・オーバーフロー
_WindowsにおけるSleep(1)の精度について
_Rustのゲーム用ライブラリpistonがWindowsの上だと60fps出なかったのを直した - 名有りさんの日記
_タイマの精度 - お仕事メモ帳
Windows95系、NT系で違うだけ、ではないのだな…。使ってる割り込みコントローラの種類で、10ms と 15.6ms の違いが出てくると…。いやまあ、最近の機種は 15.6ms になってそうだけど。
SetTimer() に 15msec を指定するのと、16msec を指定するのでは、動作に差が出てきそう。16msec を指定すると、実際には次回のメッセージが送られてくるのが 15.6 x 2 = 31.2 msec 後になるのかも。とすると、FPSは 31〜33FPSぐらいになるのだろうか。60FPSを期待したのに40FPS前後しか出ないなーと困っていたけれど、実際は31〜33FPSだった可能性が高そう。
_ windows - MFCでの標準タイマーの精度をあげる方法ありますか? - スタック・オーバーフロー
Windows OSのシステムクロックは15.6ミリ秒の分解能しかもたないため(Windows 7)、記載のような事象が発生します。
_WindowsにおけるSleep(1)の精度について
_Rustのゲーム用ライブラリpistonがWindowsの上だと60fps出なかったのを直した - 名有りさんの日記
_タイマの精度 - お仕事メモ帳
この動作の原因は、Windows XP のハードウェア アブストラクションレイヤ モジュール:HAL.DLL の仕様の違いによるものであり、そのPCが使用しているHALの種類によってタイマ精度が決まります。
タイマ精度 約 10mS となるマシンは halacpi.dll が使用されており、タイマ精度 16mS となるマシンには halaacpi.dll が使用されております。
最近の機種では、割り込みコントローラに APIC (Advanced PIC) を 使用しておりまして、この場合、HAL は halaacpi.dll が使用されます。
Windows95系、NT系で違うだけ、ではないのだな…。使ってる割り込みコントローラの種類で、10ms と 15.6ms の違いが出てくると…。いやまあ、最近の機種は 15.6ms になってそうだけど。
SetTimer() に 15msec を指定するのと、16msec を指定するのでは、動作に差が出てきそう。16msec を指定すると、実際には次回のメッセージが送られてくるのが 15.6 x 2 = 31.2 msec 後になるのかも。とすると、FPSは 31〜33FPSぐらいになるのだろうか。60FPSを期待したのに40FPS前後しか出ないなーと困っていたけれど、実際は31〜33FPSだった可能性が高そう。
*1: ちなみに、Windows95系はもっと精度が荒くて、55ミリ秒程度らしい。
[ ツッコむ ]
#2 [zatta] 日記をアップロード
2022/08/19を最後に日記をアップロードしてなかったのでアップロード。
[ ツッコむ ]
以上、1 日分です。