しくみを知る — 宣言的UIから描画パイプラインへ
React Native Skiaのコンポーネントツリーが、どのようにしてSkiaのシーングラフを経てGPUの描画命令になるのかを図解します。
宣言したコンポーネントはシーングラフに整理され、GPUが最終的なピクセルを描きます。
<Canvas>の中に<Circle>や<Rect>を並べて書くと、見た目としては通常のReact Nativeコンポーネントを書いているのと変わりません。ですが、その裏側で起きていることはViewやTextとはまったく違います。
Skiaは自前のReactレンダラーを持っている
React Nativeは、Reactが組み立てた要素ツリーを実際のネイティブUIに変換するために、専用のレンダラー(Renderer)を持っています。React Native Skiaも同様に、@shopify/react-native-skia専用のレンダラーを内部に持っていて、<Canvas>配下のJSXはこのレンダラーによって処理されます。
つまり<Canvas>の外側(通常のViewやText)はReact Nativeのレンダラーが、<Canvas>の内側はSkia専用のレンダラーが担当するという、二つのレンダラーが同居する構造になっています。この境界があるため、<Canvas>の外で作ったReact Contextは、そのままでは内側から参照できません。両者は別のレンダラーツリーとして動いているためです。Contextをどうしても描画側に渡したい場合は、its-fineのようなライブラリでブリッジする必要があります(詳しくは公式のContextsを参照してください)。
描画までの3段階
<Canvas>に書いたコンポーネントツリーが画面に表示されるまでは、大まかに次の3段階を経ます。
1. Reactコンポーネントツリー
<Canvas>の中身は、他のReactコンポーネントとまったく同じ書き方をします。propsを渡し、useStateで状態を持ち、子コンポーネントに分割することもできます。この時点ではまだ「何を描くか」の宣言でしかなく、実際のピクセルは1つも計算されていません。
2. Skiaのシーングラフへの変換
Skia専用のレンダラーが、このコンポーネントツリーをSkiaのシーングラフ(内部的には描画命令の並んだリスト)に変換します。<Circle cx={128} cy={128} r={64} color="cyan" />は、最終的に「この座標にこの半径・色で円を描け」という命令に変換されます。<Group>でネストした場合は、その中の変換(transform)やブレンドモードもまとめて命令に反映されます。
3. GPUによるラスタライズ
組み立てられた描画命令は、Skiaのネイティブ実装(C++)からGPUに渡されます。iOSではMetal、AndroidではOpenGL(実験的にGraphite経由のVulkan)が実際のピクセル計算を担当し、その結果が画面に転送されます。React NativeのJavaScript側は、この最後の段階には直接関与しません。
worklet — UIスレッドで直接動くコード
アニメーションのように毎フレーム値を更新する処理を、JavaScriptスレッドとUIスレッドの間で毎回やり取りしていると、フレーム落ちの原因になります。そこでReact Native Skiaは、Reanimatedのworkletという仕組みと組み合わせて使えるようになっています。
workletは"worklet"という文字列を関数の先頭に書くだけの、ごく普通のJavaScript関数です。Reanimatedがこれを解析し、UIスレッド上でも実行できる形に変換します。
const radius = useSharedValue(64);
const transform = useDerivedValue(() => {
"worklet";
return [{ scale: 1 + Math.sin(radius.value) * 0.1 }];
});
このtransformをそのまま<Circle>のtransformpropに渡すと、UIスレッド上で計算された値がJavaScriptスレッドを介さずに直接Skiaの描画命令へ反映されます。図の2段階目(シーングラフへの変換)がUIスレッド上で完結するイメージです。workletとReanimatedの詳しい組み合わせ方は、第5部で改めて扱います。
まとめ
<Canvas>の中身は、Reactの見慣れた書き方のまま、Skia専用のレンダラーによってシーングラフに変換され、最終的にGPUで描かれます。この構造を頭に入れておくと、次の章で扱う「最初の描画」や「レンダリングモード」の違いも理解しやすくなります。