借り初めのひみつきち

仮ブログです。

myos の描画アーキテクチャ

myos のウィンドウ描画アーキテクチャについて解説します。

執筆時点での情報なのでバージョンによっては詳細が異なる場合があります。 なお、ここにあるのはカーネルの構造なので、アプリケーションレイヤーではラッピングしたオブジェクトなど詳細は異なります。

クラス名やメソッド名が違いますが myos の基礎となった moe も類似した構造になっています。

Bitmap

Bitmap は画像だったりバッファだったり画面そのものだったり myos が色々な場面で利用しているグラフィックスオブジェクトで、サイズやピクセルの配列などの情報を持っています。 Bitmap に対する描画命令もたくさんあります。

また、ウィンドウバッファの内容場合、枠線やタイトルバーを除外したビットマップビューという特殊な状態の Bitmap を扱うことがあります。

WindowManager

WindowManager はウィンドウ制御に関するマネージャクラスで、以下のようなメンバーを持っています。(一部抜粋)

main_screen

実際のスクリーンに相当するビットマップオブジェクトです。 ブートローダーから渡された EfiGraphicsOutputProtocol の値を使って初期化しています。

    main_screen: &'static Bitmap,

off_screen

実際のスクリーンと同じサイズのビットマップでオフスクリーン描画に使います。 myos ではアルファブレンディング処理のために一旦全てのウィンドウを重ねて描画する必要があるため(現在はほぼ全てのウィンドウが影付きのため)、いったんこのバッファで合成して描画します。

    off_screen: Box<Bitmap>,

実装上のポリシーとして、現状このバッファは複数のスレッドから同時に描画する可能性があり、一時的に画面が乱れることがあります。(半透明のウィンドウが濃く描画されるなど) この解決方法はいくつか考えられますが、実装コストや速度低下の見返りに画面が一時的に乱れる現象を許容しています。

sem_redraw

sem_redraw にシグナルを送信するとウィンドウマネージャがアクティブになります。 主にマウスカーソルを移動した際の再描画に使われます。

    sem_redraw: Semaphore,

root window

他の全てのウィンドウの最背面に位置するウィンドウでいわゆるデスクトップです。 ウィンドウヒエラルキーのルートになります。

    root: Option<WindowHandle>,

Rust の言語仕様上現行は Option で定義されています。

pointer window

マウスポインターの描画に使われるウィンドウです。他の全てのウィンドウよりも最前面に表示されます。

    pointer: Option<WindowHandle>,

Rust の言語仕様上現行は Option で定義されています。

ウィンドウヒエラルキーとウィンドウレベル

画面に表示されているすべてのウィンドウはウィンドウヒエラルキーという階層を持っていて、ウィンドウの表示・非表示はウィンドウヒエラルキーへの追加・削除と同義になります。 デスクトップウィンドウが再背面、マウスポインターが最前面で、それ以外のウィンドウはその中間にあります。

また、全てのウィンドウはウィンドウレベル (WindowLevel) を持っていて、レベルの高いウィンドウの方が前面に表示されます。 ウィンドウレベルが同じ場合、後から表示された(ウィンドウヒエラルキーに追加された)ウィンドウの方が前面に表示されます。

一般のウィンドウは WindowLevel::NORMALWindowLevel::FLOATING のどちらかを選択することができます。システムが管理している特殊ウィンドウではそれ以外のレベル (WindowLevel::ROOTWindowLevel::POINTER など) もあります。

WindowHandle

WindowHandle はウィンドウのハンドルで、どのウィンドウに対する操作なのかをウィンドウマネージャと他のプログラムの間で情報交換するために使います。 WindowHandle の実体はただの整数値です。

RawWindow

RawWindow は実際のウィンドウ管理に使われているオブジェクトです。 アプリケーションから直接アクセスする方法はありませんが、 WindowHandle が有効なウィンドウを指している場合にウィンドウマネージャ内部で RawWindow に変換して処理します。

ウィンドウの内容は bitmap というメンバーに描画され、その他ウィンドウの位置やサイズなどに関する情報も持っています。

ウィンドウの描画は3ステップあります。

フレームの描画

システムが必要と判断したときに RawWindow.draw_frame() で枠線やタイトルバーを描画します。

ウィンドウ内容の描画

アプリが WindowHandle.draw() などを呼び出すと RawWindow.bitmap から枠線やタイトルバーを除外したビットマップのビューを返却されるのでその中でウィンドウ内容を描画することができます。

画面への反映

WindowHandle.set_needs_display() を呼び出してイベントループが WindowMessage::Draw を受け取った場合、 WindowHandle.set_needs_display() を呼び出した後に WindowHandle.refresh_if_needed() を呼び出した場合、 WindowHandle.draw() を呼び出した場合、 ウィンドウ本体を移動した場合、 ウィンドウの上にあるウィンドウの状態が変わってシステムで再描画が必要と判断された場合などに画面に描画します。

最終的に RawWindow.draw_to_screen() の中で RawWindow.draw_into() を呼び出して RawWindow.bitmap の内容をオフスクリーンバッファに合成し、最後にメインスクリーンに転送します。

つまり通常の描画は RawWindow.bitmapoff_screenmain_screen というトリプルバッファになり、さらにアプリ側で個別にバッファを設けている場合はクワドロプルバッファになります。

このように myos の描画は複数のバッファを経由するため、直接描画する場合に比べて若干ラグがあります。