NetBSDで絵を書く話

これはNetBSD Advent Calendar 2016の21日めの記事です。

はじめに

なんとなくNetBSDで絵を書く(描くではない)話を書きます。

普段のデスクトップ環境では、通常X Window SystemのXserver(1)を使うのがある意味当たり前です。これはNetBSDだけでなく、多くのUNIX/Linux環境でも同じでしょう。

こういう環境で普通絵を書く/絵を描くと言えば、このXの上で何らかのアプリケーションソフトウェアを使うことを意味します。gimpや各種オフィスツールなんか有名ですし、最近はFirefox等のブラウザを使ってブラウザ上でお絵描きできるものもありますね。

で、今回はそういう話ではなく、NetBSDのグラフィカルコンソール画面に直接絵を出力する話をしてみます。

NetBSDのコンソール画面表示

NetBSDでは画面を表示するデバイスとして、wsdisplay(4)というものがあります。 これはwscons(4)フレームワークの一つで、ハードウェア固有の下位ドライバの差異を吸 収し、「プログラムから画面に表示する」という目的だけを行うものです。 Linuxで言うところのFBdevと似たものです。

wsdisplay(4)では実際のハードウェアによって画面に表示可能なものはテキストだけというものもありますが、そういう場合は今回の対象外です。 あくまでもビットマップディスプレイが表示できる場合のみの話です。

PCの画面表示

いわゆるPCのコンソール画面は長らくテキスト表示だけに使うドライバが標準になっていたようで、そのままではビットマップディスプレイとして利用できない設定になっていましたが、NetBSD 7.0以降でintelfbやradeonfbが標準になったおかげで割と多くのPCではデフォルトでビットマップ表示ができるようになりました。 システムの起動メッセージ(あの緑色の文字)の途中に見た目が変わる瞬間があれば、それがビットマップ可能なドライバになっています。

仮に切り替わらなかった場合でも、vesaドライバが使える可能性があります。VMware等の仮想マシン上で起動した場合はこれに該当します。この場合、NetBSDの起動前、ブートローダーのプロンプトでDrop to boot prompt を選択し、vesa listコマンドで対応する画面リストが表示されればビットマップ可能なvesaフレームバッファが使用可能です。

ここで

 vesa 1024x768x32
 boot

などと入力すれば、vesaドライバを使うにカーネルが認識します。

genfb0: framebuffer at 0xec000000, size 1024x768, depth 32, stride 4096
wsdisplay0 at genfb0 kbdmux 1: console (default, vt100 emulation), using wskbd0

使えることが分かれば、以下の用に/boot.cfgの起動コマンドを書き換えれば、次回からは自動的に入力されたことになります。

menu=Boot with Vesa:rndseed /var/db/entropy-file;vesa 1024x768x32;boot netbsd

グラフィカル画面の使い方

さて、フレームバッファモードが使えるようになった画面ですが、それでもそのままでは単に文字表示がちょっと多いだけのテキストコンソールです。ここに自由に絵を書くには、それなりのプログラムが必要です。そんなわけで、ものすごく簡単なサンプルを作りました。

wsdisp0.c

これを実行すると、画面が真っ白になって、その後また元のテキストコンソールに戻ると思います。なお、これの実行はフレームバッファコンソールで実行しなければなりません。Xを使っていても実は実行できたりしますが、画面がどうなっても知りません(ぇ。

解説というほどのものではありませんが、一応。フレームバッファとして使うには以下の様にします。

まぁこれだけです。要は、ある一定の手続きを行うと、ビデオカードのメモリを直接アクセスできるということです(最近のGPUでは実はちょっと事情が違うらしいのですがそれはひとまず置いておきます)。

絵を書くために

さて、ビデオメモリにアクセスできました。35年以上前風に言えば、パソコンのVRAMへのアクセス方法が分かったということになります。

となれば、35年以上前のパソコン少年的には、当然絵を書くためにはline文が必要です。

ということで、line文ではなくCでline関数を実装してみます。

必要なのは、画面上の任意の場所のピクセルの色を設定する、つまりpset()関数を作ること、それを連続して実行するようにすれば線が書けます。

てなわけで、pset()とline()を実装し、それを使って実行した結果が以下。

(最初の画面は単に塗りつぶしだった)

ソースはこちら: wsdisp1.c

pset()は単にフレームバッファの位置を計算し、RGBの値からピクセル値に変換して値を書き込んでいるだけです。 line()に関しては古より使われるブレゼンハムアルゴリズム。これたぶんはるか昔の雑誌(たぶんOh!MZ/Oh!X)に載ってたコードです。

次は?

任意の色で線分を描画できるようになりました。こうなれば次はcircle()だのpaint()だの実装できそうです。 が、時間切れ。さすがに夜2時間*2くらいではちょっと間に合いませんでした。 また、先のプログラムは手抜きでRGB 各8bitかつ、1ピクセルは32bitの場合しか実行できません。 このあたり、wsdisplay(4)は機種非依存なフレームワークドライバなので、差異を吸収した実装にするべきでしょう。

さらに、今時のintelfb(4)なnote pcで実行すると、なんか妙に遅いのです。Core-i7、おまえの実力はその程度か?というか、単にフレームバッファとしてはあまり考えられてないのかもしれません。むしろRPi2の方が速いくらい。 そのあたり何かあるのかもしれません。

おわりに

今時のGPU当たり前の時代に、何が悲しくてline()で絵を書くのか? という疑問があるでしょうが、35年以上前のマイコン少年的にはビットマップディスプレイの登場に喜び、嬉々としてBASICのline文で絵を(表示するためのプログラムを)書いていたのです。そんなことをたまにはまたやってみたくなったりして遊んでみたという話でした。

本当はクリスマス仕様のらむちゃんをline()で書きたかったのですが、そんな時間はどこにも無かったのでした。残念。またいつか、どこかの機会に。