メインコンテンツまでスキップ

出力の合成と 3D

COMPOSITOR.rendering.composition は各出力の組み立て方を決めます。 ウィンドウ合成の出力版にあたり、あちらがウィンドウの装飾を 組み立てるのに対して、こちらはレイヤーシェルのサーフェス・ウィンドウ・オフスクリーン テクスチャ・3D シーンから画面全体を組み立てます。

このページでは考え方を説明します。ノードとヘルパーの一覧は リファレンス、完成した演出(キューブ遷移、 クロスフェード、ズーム、ウィンドウごとの 3D 配置)は レシピ集にあります。

既定の重なり​

合成関数が無いとき、すべての出力は次のように描かれます:

<>
<Layers layers={["background", "bottom"]} />
<Windows />
<Layers layers={["top", "overlay"]} />
<LayerPopups />
</>

これは <DefaultComposition /> が返すものと同じです。子要素は奥から手前の順で、 後の子ほど上に描かれます。合成関数はこのようなツリーを返し、並べ替えたり、一部を 省いたり、renderTexture で任意の部分をテクスチャに描いたり、テクスチャを平面 (<TextureView>)や 3D(<Scene3D> と <Plane>)で描いたりできます。

import { COMPOSITOR, DefaultComposition } from "shoji_wm";

COMPOSITOR.rendering.composition = (output) => <DefaultComposition />;

関数には出力の OutputInfo が渡され、出力ごとに違うツリーを返せます。ロック画面・ カーソル・設定エラー表示は、合成が何を描いても常に最前面です。

よく使う形は「演出中でなければ既定」です:

COMPOSITOR.rendering.composition = (output) =>
myEffect.compose(output) ?? <DefaultComposition />;

評価のタイミング​

関数は出力の追加・変更時と、読んだ signal が変わったときにだけ実行されます(自分から 毎フレーム実行されることはありません)。コンポジターは最後に受け取ったプランを保持し、 毎フレームそれをたどってこれまでと同じ描画要素を集めるので、damage 追跡・遮蔽・ ダイレクトスキャンアウトはそのまま働きます。

アニメーションは出力の createPoll で signal を動かします(フレームタイミング 参照)。変化があったフレームごとに 1 回だけ再評価されます。signal は合成関数の中で 読んでください。読んだ出力だけが再評価されます。

const [angle, setAngle] = signal(0);
createPoll(16, (poll) => setAngle(angle.peek() + 1), { output: "DP-1" });

COMPOSITOR.rendering.composition = (output) => {
const { width, height } = outputLogicalSize(output);
const desktop = renderTexture({ content: <DefaultComposition /> });
return (
<Scene3D camera={screenCamera(output, { distance: 400 })}>
<Plane texture={desktop} width={width} height={height}
transform={transform3d().rotateY(angle())} />
</Scene3D>
);
};

テクスチャ​

renderTexture({ content }) は合成ノードをオフスクリーンに描きます。結果は <TextureView> や <Plane> で何度でも使えます:

  • 何か所で使っても1 フレームに 1 回だけ、内容が変わった部分だけ描き直されます。 静止したテクスチャのコストはほぼゼロです。
  • エフェクトはテクスチャの中でも効きます。ブラー・<ShaderEffect>・ウィンドウ エフェクトもテクスチャの内容に含まれます。
  • テクスチャは他のテクスチャを使えます(テクスチャを貼った 3D シーンのテクスチャも可)。 自分自身は使えません。
  • key は再評価をまたいで GPU テクスチャを保ちます。既定は呼び出し順で、ツリーの形が 変わらない限り安定します。条件付きで現れるテクスチャには key を付けてください。
  • width・height・scale の既定は出力のものです。小さいテクスチャほど安く、 scale を上げるとズームしても鮮明です。

座標​

  • 合成の矩形(<TextureView>・<Solid>・<Scene3D> の x・y・width・height)は、 合成(出力、または描画中のテクスチャ)の左上からの論理ピクセルです。省略した値は残り 全体を覆い、すべて合成の範囲に切り取られます。
  • テクスチャの中でも、レイヤーとウィンドウは出力上と同じ位置に現れます。出力と同じ サイズのテクスチャには出力がそのまま映り、小さいテクスチャには左上の部分が映ります。 <Windows offsetX offsetY> でウィンドウをずらせるので、これで 1 つのウィンドウを テクスチャに収められます(ウィンドウごとのテクスチャ)。
  • 3D のワールド座標の単位は論理ピクセル、+Y が上、+Z がカメラ側です。 screenCamera(output) は z = 0 の平面を出力に 1:1 で写すので、原点に出力サイズの 平面を置くとちょうど画面を覆い、平らなデスクトップから演出を始められます。

プランが選ぶウィンドウ​

props の無い <Windows /> は出力自身の重なりです。今その出力に表示されているもので、 閉じかけのウィンドウと装飾ポップアップも含みます。<Windows windows={[...]} /> は 指定したウィンドウ(オブジェクトか ID)だけを重なり順で描き、非表示でも描きます (別ワークスペースのウィンドウ、最小化中のウィンドウなど)。非表示のウィンドウは、 隠すための変形やフェードを外し、表示時と同じ姿で矩形の位置に描かれます。

  • 合成を通してだけ表示されるウィンドウにも frame callback が届くので、非表示のまま描画を 続けます。合成の範囲外にあるウィンドウは描かれず、frame callback も届きません。
  • 入力には影響しません。合成が何を描いても、入力はウィンドウの実際の位置と表示状態に 届きます。画面上でウィンドウを動かして見せるプラン(一覧表示や 3D スイッチャー)は、 表示中は入力グラブで入力を引き受け、 自分の配置で当たり判定をし(pickPlane)、 表示が終わった後に何を見せるかはウィンドウマネージャーに任せてください。

backdrop エフェクト​

backdrop エフェクト(ブラー、ガラス、backdropSource() を使うもの全般)には、 プランでその下に描いたものが映ります。ウィンドウ・レイヤー・テクスチャビュー・ 3D シーン・単色のどれもです。バーのブラーにはその下で回転する 3D シーンが映り、 テクスチャの中のウィンドウはテクスチャ内でその下に描いたものをぼかします。

  • 既定の重なりでは、これまでと同じものを同じコストで取り込みます。
  • xrayBackdropSource() は、そのサーフェスの下で一番近いウィンドウ群より下にあるものを 見ます。既定の重なりでは壁紙、ウィンドウ群が無ければ下にあるものすべてです。
  • レイヤーのポップアップは取り込みません。
  • backdrop の下のテクスチャや 3D シーンが変わると、backdrop は計算し直します。止まって いる間は安く、動いている間は毎フレーム 1 回ぶんのブラーがかかります。

ウィンドウ 1 つだけを描いたテクスチャでは、そのウィンドウの下には何も無いので、ブラーに 映るものがありません。そのテクスチャを見せている間は、そうしたウィンドウを backdrop の 無いエフェクトに切り替えてください(ウィンドウごとのテクスチャ)。

性能​

  • 既定の重なりのコストはこれまでと同じです。余計なパスは無く、フルスクリーンの ファストパスとダイレクトスキャンアウトも働きます。
  • 合成のルートに <Windows />(出力自身の重なり)が無い間、その出力ではフルスクリーンの ファストパスとダイレクトスキャンアウトは無効です。演出していない間は <DefaultComposition /> を返してください。
  • テクスチャと 3D シーンはそれぞれ範囲と同じ大きさのオフスクリーンバッファで、プランが 使っている間だけ保持され、使わなくなると解放されます。
  • 3D シーンはカメラ・平面・テクスチャが変わったときだけ描き直されます。
  • 1 つのプランで宣言できるテクスチャは 64 枚までです。

バックエンド​

TTY とネスト(winit)の両バックエンドで使えます。