2024/02/01(木) [n年前の日記]
#1 [basic] FreeBASICでスクリーンセーバを作成する
FreeBASICを使ってWindows用スクリーンセーバを作成したい。環境は、Windows10 x64 22H2 + FreeBASIC 1.10.1。
FreeBASICの公式掲示板(?)で、FreeBASIC Screensaver Kit というものを公開してくれてる方が居た。昔公開されていたものを、比較的新しい FreeBASIC でも動くように修正してアップロードしてくれたらしい。ありがたや。
_freeBASIC Screensaver Kit Updated - freebasic.net
_FreeBASIC Screensaver Kit - freebasic.net
これを使わせてもらえば、比較的簡単にスクリーンセーバを作れるのでは…?
FreeBASICの公式掲示板(?)で、FreeBASIC Screensaver Kit というものを公開してくれてる方が居た。昔公開されていたものを、比較的新しい FreeBASIC でも動くように修正してアップロードしてくれたらしい。ありがたや。
_freeBASIC Screensaver Kit Updated - freebasic.net
_FreeBASIC Screensaver Kit - freebasic.net
これを使わせてもらえば、比較的簡単にスクリーンセーバを作れるのでは…?
◎ サンプルを試用してみる :
前述のページ内の「Download it here」というリンクをクリックすると Dropboxのページが開くので、ページ右上のあたりにある、下向きの矢印(ダウンロード)をクリックすれば、関連ファイル一式が入っている FB_ScreenSaverKit Updated.zip をダウンロードできる。
消えると怖いので、一応ここにも置かせてもらいます。元になった最初のファイル群はリンク切れになってて入手できない状態だし…。こういうのはあちこちに置いとかないと後から入手できなくなるので…。
_fb_screensaverkit_updated.zip
解凍すると、以下のファイルが入っている。
以下を打てば、FreeBASICでコンパイルして、Mambazo Doodle.exe を生成できる。
.exe が生成されたら、コピーして拡張子を .scr にする。Windows用のスクリーンセーバは拡張子が .scr だけど、実態は .exe を .scr にリネームしたものなので…。
エクスプローラ等で、Mambazo Doodle.scr を右クリックして「Test」を選べば、フルスクリーンで表示される。以下が実行した際のスクリーンショット。長いので途中で映像を飛ばしてるけど、雰囲気は伝わるかと…。
これは余談だけど、このスクリーンセーバ、描画処理部分が一切 sleep せずに全力でループをブンブン回して描画するので、CPU使用率が凄いことになる…。適切なタイミングで sleep を入れるように改造したほうが良さそう。
消えると怖いので、一応ここにも置かせてもらいます。元になった最初のファイル群はリンク切れになってて入手できない状態だし…。こういうのはあちこちに置いとかないと後から入手できなくなるので…。
_fb_screensaverkit_updated.zip
解凍すると、以下のファイルが入っている。
- FB_ScreenSaverKit.bas ... スクリーンセーバ作成時に必要になる処理をまとめたファイル
- Mambazo Doodle.bas ... スクリーンセーバの描画処理部分。これを改造して自分なりのスクリーンセーバを作る。
- Mambazo Doodle.exe ... スクリーンセーバの実行形式。拡張子を .scr にすればスクリーンセーバになる。
- Mambazo Doodle.rc ... リソースファイル。スクリーンセーバの設定ダイアログのレイアウトが記述してある
- Mambazo Doodle.xml ... 謎。リソースファイルから内包するように指定されていた
以下を打てば、FreeBASICでコンパイルして、Mambazo Doodle.exe を生成できる。
fbc "Mambazo Doodle.bas" "Mambazo Doodle.rc" -s gui
- 「-s gui」は、「GUIアプリとして生成せよ」という指定。これをつけないと、生成したアプリを実行するたびにDOS窓が開いてしまう。
.exe が生成されたら、コピーして拡張子を .scr にする。Windows用のスクリーンセーバは拡張子が .scr だけど、実態は .exe を .scr にリネームしたものなので…。
copy "Mambazo Doodle.exe" "Mambazo Doodle.scr"
エクスプローラ等で、Mambazo Doodle.scr を右クリックして「Test」を選べば、フルスクリーンで表示される。以下が実行した際のスクリーンショット。長いので途中で映像を飛ばしてるけど、雰囲気は伝わるかと…。
これは余談だけど、このスクリーンセーバ、描画処理部分が一切 sleep せずに全力でループをブンブン回して描画するので、CPU使用率が凄いことになる…。適切なタイミングで sleep を入れるように改造したほうが良さそう。
◎ サンプルを少し修正 :
前述のサンプルを見る限り、たしかに FreeBASIC でスクリーンセーバを作れそうだなと分かった。
ソースを眺めてみたけれど、描画処理を担当する Mambazo Doodle.bas は、以下のような簡素な作りになっていた。
FB_ScreenSaverKit.bas の中で書かれている StartScreenSaver() が肝っぽいと眺めてみたけれど、以下のような処理をしている。
つまり、描画処理担当と、スクリーンセーバとして画面上に表示されているウインドウ、この2つが別々に存在していて、並列して動いている。
ソースを眺めてみたけれど、描画処理を担当する Mambazo Doodle.bas は、以下のような簡素な作りになっていた。
- FB_ScreenSaverKit.bas を include して、
- StartScreenSaver() を呼んで、
- SaverInfo.IsClosing が TRUE にならない限り、ひたすら描画に専念する。
FB_ScreenSaverKit.bas の中で書かれている StartScreenSaver() が肝っぽいと眺めてみたけれど、以下のような処理をしている。
- コマンドラインオプションを判別して、設定モード(/c)、プレビュー表示モード(/p xxxx)、フルスクリーン表示モード(/s) で処理を分けてる。ただ、この実装はちょっとバグがある。それについては後述。
- 設定モードの時は、リソースファイル Mambazo Doodle.rc に記述されてるダイアログを表示して、ダイアログが閉じられたら終了する。それ以外は何もしない。
- プレビュー表示モード、フルスクリーン表示モードの時は、ウインドウを新規作成する。
- そのウインドウで、キー入力やマウス操作があったか等を調べて、何か操作されてたらウインドウ破棄メッセージを送る。
- タイマーを設定して、一定時間毎に、bitmapバッファの内容をウインドウにコピーしている。このbitmapバッファに、前述の描画処理担当部分(Mambazo Doodle.bas)が、せっせと描画しているらしい。
- ウインドウの破棄メッセージが来たら、SaverInfo.IsClosing を TRUE にしている。
つまり、描画処理担当と、スクリーンセーバとして画面上に表示されているウインドウ、この2つが別々に存在していて、並列して動いている。
- メインメモリ上のbitmapバッファに対して、描画処理担当が描画。ウインドウ担当は、そのバッファ内容を、一定の時間間隔で自分のウインドウ内にコピーしている。
- スクリーンセーバの終了するタイミングは、ウインドウ担当が、SaverInfo.IsClosing を通じて知らせてくれる。描画処理担当は、SaverInfo.IsClosing が TRUE になったら終了処理をする。
◎ コマンドラインオプションの解析にバグがある :
ところで、StartScreenSaver() のコマンドライン解析には、些細なバグがあった。
Windows用のスクリーンセーバは、与えられたコマンドラインオプションによって、以下のように動作を変えないといけない。
例えば、エクスプローラ上で .scr を右クリックして「構成」を選んだ際は設定ダイアログが表示されるのだけど、この場合、コマンドラインオプションとしては何も与えられてない状態で .scr が呼ばれている。
しかし、StartScreenSaver() の実装では、設定ダイアログではなく、フルスクリーン表示になってしまう。
そんなわけで、以下のように修正して使うことにした。
Windows用のスクリーンセーバは、与えられたコマンドラインオプションによって、以下のように動作を変えないといけない。
- /c を与えられた場合、設定ダイアログを表示。
- /p xxxx を与えられた場合、xxxx(ウインドウハンドルを10進数で示した数値) を親とした子ウインドウを新規作成して、その新規作成したウインドウにスクリーンセーバの内容を描画する。
- /s を与えられた場合、フルスクリーン表示でスクリーンセーバを描画する。
- 何も与えられてない場合、設定ダイアログを表示。
例えば、エクスプローラ上で .scr を右クリックして「構成」を選んだ際は設定ダイアログが表示されるのだけど、この場合、コマンドラインオプションとしては何も与えられてない状態で .scr が呼ばれている。
しかし、StartScreenSaver() の実装では、設定ダイアログではなく、フルスクリーン表示になってしまう。
そんなわけで、以下のように修正して使うことにした。
Sub StartScreenSaver() Select Case LCase(Command(1)) Case "/c" ' Config StartConfigDialog() End Case "/p" ' Preview Dim As String tmpHWnd = Command(2) SaverInfo.IsPreview = TRUE If tmpHWND <> "" Then SaverInfo.hWndParent = Cast(HWND__ Ptr, CLng(Val(tmpHWnd))) End If SetupScreenMode() ThreadCreate(@WindowThread) Case "/s" ' Full screen SetupScreenMode() ThreadCreate(@WindowThread) Case Else ' No options. Config StartConfigDialog() End End Select End Sub
- LCase() は、文字列を小文字化してくれる関数。
- Command() は、コマンドラインオプションが入っている。何も指定されてなかったら "" が返ってくる。
- "/p xxxx" が与えられた時、command(1) には "/p" が、command(2) には "xxxx" が入る。
◎ 簡単なサンプルを作ってみた :
Mambazo Doodle.bas は綺麗な図形が描かれるけど、その分、どんな処理をしているのかちょっと分かりづらい気もした。
そこで、試しに、簡単な処理をするサンプルを書いてみることにした。環境は、Windows10 x64 22H2 + FreeBASIC 1.10.1 32bit。
処理としては、いつものアレ。画面の中をボールが跳ねるヤツ。
アイコン画像その他も含めたファイル一式は以下。
_ssfbscrsample.zip
zip内に入っている build.bat を実行すれば、FreeBASIC でコンパイルできる。やっていることは以下。
ちなみに、以下を打てば、.exe を作らずに、いきなり .scr を作れる。「-x hoge」で、出力ファイル名を指定できるらしい。
出来上がった .scr は、以下のフォルダにコピーすれば、スクリーンセーバとして使えるようになる。
自分が書いた部分/作った画像は、CC0 / Public Domain ってことで。ただ、FB_ScreenSaverKit.bas のライセンスは、ちょっと分からない…。でもまあ、たぶん、自由に使っていいタイプのソレじゃないのかな…。何とか kit を名乗っていて自由に使えなかったら公開してる意味が無いし…。
一応ソースも個別に置いておく。
_build.bat
_fb_screensaverkit.bas
_ssfbscrsample.xml
_ssfbscrsample.bas
見ればわかる通り、SaverInfo.IsClosing が TRUE になるまでメインループを回し続けてひたすら描画しかしていない。
_ssfbscrsample.rc
リソースファイルの中で、スクリーンセーバ名や、アイコンファイルのファイル名を指定できる。
余談。当初、描画する際に、ScreenLock と ScreenUnlock を呼んでいたのだけど、その2つは画面に見えてるバッファに対して描画する際は有効だけど、メインメモリ上にあるバッファに描画する際は何もしない、という記述を見かけたので今回はコメントアウトしておいた。
そこで、試しに、簡単な処理をするサンプルを書いてみることにした。環境は、Windows10 x64 22H2 + FreeBASIC 1.10.1 32bit。
処理としては、いつものアレ。画面の中をボールが跳ねるヤツ。
アイコン画像その他も含めたファイル一式は以下。
_ssfbscrsample.zip
zip内に入っている build.bat を実行すれば、FreeBASIC でコンパイルできる。やっていることは以下。
fbc ssfbscrsample.bas ssfbscrsample.rc -s gui copy ssfbscrsample.exe ssfbscrsample.scr
ちなみに、以下を打てば、.exe を作らずに、いきなり .scr を作れる。「-x hoge」で、出力ファイル名を指定できるらしい。
fbc -x ssfbscrsample.scr ssfbscrsample.bas ssfbscrsample.rc -s gui
出来上がった .scr は、以下のフォルダにコピーすれば、スクリーンセーバとして使えるようになる。
- スクリーンセーバが32bit版アプリで、Windows が 64bit版の場合、C:\Windows\SysWOW64\ にコピー。
- スクリーンセーバが32bit版アプリで、Windows が 32bit版の場合、C:\Windows\System32\ にコピー。
- スクリーンセーバが64bit版アプリで、Windows が 64bit版の場合、C:\Windows\System32\ にコピー。
自分が書いた部分/作った画像は、CC0 / Public Domain ってことで。ただ、FB_ScreenSaverKit.bas のライセンスは、ちょっと分からない…。でもまあ、たぶん、自由に使っていいタイプのソレじゃないのかな…。何とか kit を名乗っていて自由に使えなかったら公開してる意味が無いし…。
一応ソースも個別に置いておく。
_build.bat
_fb_screensaverkit.bas
_ssfbscrsample.xml
_ssfbscrsample.bas
#include "fb_screensaverkit.bas" ' use mmsystem #include "windows.bi" #include "win/mmsystem.bi" timeBeginPeriod(1) Randomize Timer Const MAX_FPS = 60.0 Const PI = 3.141592653 Dim As Double start_time, prev_time, now_time, next_time, delta, one_frame Dim As Integer frame_count Dim As String fps_text = "FPS" Dim As Integer sw, sh 'Start the Screen Saver StartScreenSaver #If 1 ' get screen size. width and height ScreenInfo sw, sh #Else 'You can also find the width & height of the screen by using SaverInfo.ScrWidth & SaverInfo.ScrHeight sw = SaverInfo.ScrWidth sh = SaverInfo.ScrHeight #Endif ' init work Dim As Double x, y, dx, dy, r x = sw / 2.0 y = sh / 2.0 dx = (CDbl(sw) / MAX_FPS) * 0.6 dy = (CDbl(sh) / MAX_FPS) * 0.4 r = sh / 16.0 start_time = Timer prev_time = start_time frame_count = 0 ' main loop Do Until SaverInfo.IsClosing = True ' get delta now_time = Timer delta = now_time - prev_time prev_time = now_time next_time = now_time + (1.0 / MAX_FPS) If delta < 0 Then delta = 1.0 / MAX_FPS ' count FPS If now_time >= start_time Then If (now_time - start_time) >= 1.0 Then fps_text = Str(frame_count) & " FPS" start_time += 1.0 frame_count = 0 End If Else start_time = now_time End If frame_count += 1 ' move ball position x += (dx * MAX_FPS * delta) y += (dy * MAX_FPS * delta) If (x <= (r / 2) And dx < 0) Or (x >= (sw - (r / 2)) And dx > 0) Then dx *= -1.0 If (y <= (r / 2) And dy < 0) Or (y >= (sh - (r / 2)) And dy > 0) Then dy *= -1.0 If SaverInfo.IsClosing <> True Then ' draw start ' ScreenLock ' clear screen ' Line (0, 0)-(sw, sh), Rgb(0, 0, 0), BF color RGB(0, 0, 0), RGB(0, 0, 0) cls ' draw ball circle (x, y), r, RGB(255, 0, 0), , , , F ' darw FPS Draw String ((sw - Len(fps_text) * 8) / 2, 10), fps_text, RGB(255, 255, 255) ' draw end ' ScreenUnlock End If ' sleep If Timer < next_time Then Dim As Double wait_time = (next_time - Timer) * 1000.0 If wait_time > 0 Then sleep wait_time End If End If Loop timeEndPeriod(1)
見ればわかる通り、SaverInfo.IsClosing が TRUE になるまでメインループを回し続けてひたすら描画しかしていない。
_ssfbscrsample.rc
/* Screensaver title */ STRINGTABLE BEGIN 1 "FreeBASIC Screensaver" END /* icon */ 100 ICON "fbscricon.ico" /* dialog */ FB_SCRNSAVER_ABOUT DIALOGEX 6, 18, 160, 62 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | DS_NOFAILCREATE FONT 12,"Arial" CAPTION "FB Screensaver Sample" BEGIN DEFPUSHBUTTON "OK" IDOK, 55, 40, 50, 14 CTEXT "FB Screensaver Sample" -1, 0, 5, 160, 8 CTEXT "Version 1.0" -1, 0, 15, 160, 8 END 1 24 "ssFBscrSample.xml"
リソースファイルの中で、スクリーンセーバ名や、アイコンファイルのファイル名を指定できる。
余談。当初、描画する際に、ScreenLock と ScreenUnlock を呼んでいたのだけど、その2つは画面に見えてるバッファに対して描画する際は有効だけど、メインメモリ上にあるバッファに描画する際は何もしない、という記述を見かけたので今回はコメントアウトしておいた。
◎ 問題点 :
たったこれだけでスクリーンセーバが作れてしまうなんて…。素晴らしい。そう思ったものの、これはこれでちょっと問題があるような気もしてきた。
問題その1。描画と表示の同期が、おそらく取れてない。
例えば今回書いたサンプルでは、描画担当の、メインメモリ上のbitmapバッファへの描画は60FPS前後で処理できているけれど。画面に表示されているソレは、かなりガクガクした動きに見える。60FPSで動いてるようには全然見えない。と言うのも、ウインドウ担当は60FPSでバッファ内容をコピーしてきているわけではなく、謎のタイミングでコピーしているので…。
問題その2。状況によっては表示がおかしくなりそう。
あるいは、描画担当側が自前でループを回して処理するのではなく、ウインドウ担当が1フレーム毎に関数を呼びに来る流れのほうがシンプルにならないか…。
問題その3。メインメモリ上のバッファ内容をウインドウ上にコピーしてるあたりからして、描画はCPUがソフトウェア処理で行っているのだろうなと…。ハードウェア描画と比べて、処理速度の面では不利だろうから、凝った描画はできない予感。
問題その1。描画と表示の同期が、おそらく取れてない。
例えば今回書いたサンプルでは、描画担当の、メインメモリ上のbitmapバッファへの描画は60FPS前後で処理できているけれど。画面に表示されているソレは、かなりガクガクした動きに見える。60FPSで動いてるようには全然見えない。と言うのも、ウインドウ担当は60FPSでバッファ内容をコピーしてきているわけではなく、謎のタイミングでコピーしているので…。
問題その2。状況によっては表示がおかしくなりそう。
- 描画担当がバッファに描画してる最中に、ウインドウ担当がバッファ内容をコピーしようとしたら、どうなるか。
- ウインドウ担当がバッファ内容をコピーしてる最中に、描画担当がバッファを書き換えようとしたら、どうなるか。
あるいは、描画担当側が自前でループを回して処理するのではなく、ウインドウ担当が1フレーム毎に関数を呼びに来る流れのほうがシンプルにならないか…。
問題その3。メインメモリ上のバッファ内容をウインドウ上にコピーしてるあたりからして、描画はCPUがソフトウェア処理で行っているのだろうなと…。ハードウェア描画と比べて、処理速度の面では不利だろうから、凝った描画はできない予感。
[ ツッコむ ]
以上、1 日分です。