mieki256's diary



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

これを使わせてもらえば、比較的簡単にスクリーンセーバを作れるのでは…?

サンプルを試用してみる :

前述のページ内の「Download it here」というリンクをクリックすると Dropboxのページが開くので、ページ右上のあたりにある、下向きの矢印(ダウンロード)をクリックすれば、関連ファイル一式が入っている FB_ScreenSaverKit Updated.zip をダウンロードできる。

消えると怖いので、一応ここにも置かせてもらいます。元になった最初のファイル群はリンク切れになってて入手できない状態だし…。こういうのはあちこちに置いとかないと後から入手できなくなるので…。

_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 を 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用のスクリーンセーバは、与えられたコマンドラインオプションによって、以下のように動作を変えないといけない。
  • /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 でコンパイルできる。やっていることは以下。
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がソフトウェア処理で行っているのだろうなと…。ハードウェア描画と比べて、処理速度の面では不利だろうから、凝った描画はできない予感。

以上です。

過去ログ表示

Prev - 2024/02 - Next
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project