借り初めのひみつきち

仮ブログです。

ビットマップクラス統合

近代的な OS は一部の組み込み用途をのぞくと画像処理が不可欠です。 MYOS や TOE も画像処理にそれなりの割合を割いています。

MYOS と TOE の大きな違い

MYOS の主要なターゲットは UEFI で、 UEFI では 32bit ARGB 形式が標準です。 また、近代的な画像処理は 32bit ARGB 形式で扱うことが多いので、 MYOS では 32bit ARGB で描画します。

また、 MYOS は Rust を勉強し始めて借用とライフタイムの理解が浅い頃に作り始めてとりあえずビットマップ出力できないと何も進まない状況だったため、ビットマップデータを生ポインタで管理していてあまりお行儀のいい設計ではありませんでした。

pub struct Bitmap {
    base: *mut u32,
    size: Size,
    stride: usize,
    flags: BitmapFlags,
}

一方、 TOE の主要なターゲットは古き良き BIOS 、 PC-9821、FM TOWNS と幅広くなっており、これらの機種で共通して使えて変な癖の少ないモードは 8bit インデックスカラーモードなので、 TOE は 8bit インデックスカラーで描画します *1

また、 TOE を始める頃には借用やライフタイムなどの理解がより深まっていたので、 MYOS 時代よりも Rust らしいクラス設計になっています。

pub struct Bitmap8<'a> {
    width: usize,
    height: usize,
    stride: usize,
    slice: UnsafeCell<&'a mut [IndexedColor]>,
}

TOE は基本的にほとんどの部分で MYOS のサブセットとなっていますが、以上の経緯により描画周りに関して全く互換性がありませんでした。

MYOS はアプリ実行環境で 8bit カラー対応する必要があったために別のビットマップクラスを追加し、 TOE は 8bit カラー以外の環境でも動作するように 32bit カラービットマップ描画にも対応する必要が出てきました。

同じようなクラスを何個も作るのはうんざりしますね。

ということで、これらの描画ライブラリをひとつの同じライブラリで統合したいです。 MYOS よりも TOE の方が洗練されているので TOE をベースに統合することになりました。

統合方針

設計方針として、まずは色モードに非依存の統合した抽象クラスを作り、実際のデータ保持や描画を担当する 8bit カラー用のビットマップクラスと 32bit カラー用のビットマップクラスを作ります。 MYOS では多くの場面で引き続きそのまま 32bit クラスを使用し、 TOE は基本的には抽象クラスで受け渡しをして実際に描画するときに 8bit カラーのクラスと 32bit カラーのクラスに分岐することにします。

pub enum Bitmap<'a> {
    Indexed(&'a mut Bitmap8<'a>),
    Argb32(&'a mut Bitmap32<'a>),
}

Rust らしさやパフォーマンスを考慮すると、 blt などで不変借用する Const クラス、描画用に可変借用する Mutable クラス、ウィンドウバッファなどヒープに動的確保するクラスの3種類欲しいです。 ヒープクラスは一時的に借用できますし、可変借用は不変借用に変換できますが、逆方向の変換はできません。

3色3種類で単純に9種類のクラスが必要になることがわかるかと思います。実際はもう少し複雑ですが。

9種類の似たようなクラスがバラバラに存在していると実装効率が悪いので、ある程度同じ特性を持ったクラスを Trait でまとめたり Generics を使って実装します。 また、互換性のあるクラスへのデータ変換が可能なものについては変換用のメソッドも実装します。

これらのことを続けた結果、かなりたくさんの Trait と実装になってしまいましたが、 TOE でも 32bit カラーモードで描画できるようになりました🎉

地獄の統合作業

次はこれを MYOS に移植します。

MYOS のビットマップオブジェクトは先述のように生ポインタを保持していたので、かなりの部分で借用とライフタイムの扱いを誤魔化していました。 過去の過ちを正すときが来ました。

とりあえず単純に移植しただけで100を超えるエラーメッセージの祝福を受けました。

エラーの内容は、クラス構造が変わったことによるメソッド名や型名のエラーと、借用とライフタイム違反のエラーに大別されます。 前半のエラーは対応する別の名前に置き換えれば大抵解決しますが、後者は少しずつ修正してエラーが減ってくると突然新しいエラーがどんどん生まれてきます。

Rust は借用とライフタイムの扱いがかなり厳しいので適当に設計するとコンパイルが通らず正解を見つけるまでに丸1日費やすこともあります。 とはいえ MYOS のコードを TOE に移植するときに大半の問題は解決済みなので、ひとつずつ地道に修正してついにビルドに成功しました🎉

f:id:neriring16:20210316110121p:plain

MYOS のフォント描画クラスがちょっと特殊だったのでまだ完全に移行できていませんが、それ以外は概ね TOE と MYOS で共通の描画コードが使えるようになって保守性が上がりました。

Rust は Borrow Checker 完全理解期と 'lifetime ナンモワカラン期が定期的に繰り返しやってきて難しいですね😿

*1:より高い互換性を考慮すると 4bit カラーモードなどが視野に入ってきますが、 8bit 以下のカラーモードは機種ごとにアクセス方法が異なりコードが複雑難解になるので除外しました