2018年2月11日日曜日

東方VGS(iOS)コードレビュー③ VGSView

VGSViewについて解説します。
https://github.com/suzukiplan/tohovgs-ios/blob/master/Touhou%20VGS/VGSView.m

VGSViewはiOS版VGSのグラフィックス描画処理を実装したViewです。
VGSのグラフィックス描画は、vgs2_putBG(BGを描画)やvgs2_putSP(スプライトを描画)といったVGS API(C/C++)を用いて行いますが、それらを用いて描画された情報はVGS VRAMというメモリ上に記憶されています。

VGSViewの責務(役割)を箇条書きで書くと、

  • VGSのメインループを回す(VGS VRAMに1フレームの描画がされる)
  • VGS VRAMを、iOSの描画処理系に合わせた形式に変換
  • 画面に出力(表示)
  • 上記を1秒間に60回の間隔(60fps)で繰り返し実行

となります。

iOSのゲームに適したグラフィックス描画機能としてはOpenGL/ESやMetal(GPU Renderer)がありますが、これらは上述の用途で使うのは不適当です。というのも、GPU Rendererで上記要件を満たそうとすると、

  • 毎フレームVGS VRAMをテクスチャ変換して描画 or
  • 1pixelを2つのポリゴンで表現して、(変化があったpixelの)色情報を設定

といった処理方式で実現することになりますが、前者(テクスチャ変換)は変換オーバヘッドが物凄く大きく、VGS VRAMのサイズ(東方VGSの場合は240x320)のテクスチャを毎フレーム生成することは現時点最新のGPUでも恐らく不可能です。仮にできたとしてもGPUが火を吹く勢いで回り熱暴走を起こします。

後者の処理は前者よりは現実的ですが、その場合全画面のpixelの色情報を変えようとすると最大240x320(76800)回のパレット変更命令をGPUに飛ばす必要があり、GPUへのデータ伝送は物凄く遅いので60fpsではとても動けないようになるでしょう。

つまり、VGSの要件を満たすことができるのは、GPU描画ではなくCPU描画一択になります。

まず、VGSViewを生成するとCALayer(Core Animation Layer)を独自のもの(VGSLayer)へ差し替えます。
https://github.com/suzukiplan/tohovgs-ios/blob/71464aae09f107fbf02e9ce1989d9bfb80381e37/Touhou%20VGS/VGSView.m#L156-L159

そして、CADisplayLinkを用いて垂直同期の発生間隔でsetNeedsDisplayをコールするようにしています。これでVGSLayerの描画更新が1/60秒間隔で行われるようになります。
https://github.com/suzukiplan/tohovgs-ios/blob/71464aae09f107fbf02e9ce1989d9bfb80381e37/Touhou%20VGS/VGSView.m#L170-L171

VGSLayerが初期化されると、まず2枚のCore GraphicsのBitmap Contextを生成します。これが変換後のVGS VRAMバッファになります。
https://github.com/suzukiplan/tohovgs-ios/blob/71464aae09f107fbf02e9ce1989d9bfb80381e37/Touhou%20VGS/VGSView.m#L101-L115
2枚準備している理由は、VGSLayerがダブルバッファリングと呼ばれる方式で描画出力を行っている為です。ダブルバッファリングとは、一方のバッファの内容を表示中にもう一方のバッファ更新を非同期で行うことで、60fpsの描画がスムースに行われるようにするためのものです。(実のところ、最近のiPhoneならこんなことしなくてもシングルバッファで余裕で回せますが...)

その後、ダブルバッファリングを実現するためのGameLoopスレッドを生成して起動しています。
https://github.com/suzukiplan/tohovgs-ios/blob/71464aae09f107fbf02e9ce1989d9bfb80381e37/Touhou%20VGS/VGSView.m#L117-L121

GameLoopスレッドでは、100μs間隔で描画指示が発生するのを待機して、
https://github.com/suzukiplan/tohovgs-ios/blob/71464aae09f107fbf02e9ce1989d9bfb80381e37/Touhou%20VGS/VGSView.m#L34
描画指示が発生したら、VGSのメインループを1回回し、
https://github.com/suzukiplan/tohovgs-ios/blob/71464aae09f107fbf02e9ce1989d9bfb80381e37/Touhou%20VGS/VGSView.m#L35-L42
その後、VGS VRAMの内容をCore GraphicsのBitmap Contextの形式に変換しています。
https://github.com/suzukiplan/tohovgs-ios/blob/71464aae09f107fbf02e9ce1989d9bfb80381e37/Touhou%20VGS/VGSView.m#L48-L61

GameLoopが本来必要な処理は以上ですが、VGSにはグローバルリクエストという機能があり、それに応じてAppStoreのページを開くといった要求処理を実装しています。(東方VGSでは使ってませんが、VGSからのネイティブ広告の表示制御などはこのグローバルリクエストで実現しています)
https://github.com/suzukiplan/tohovgs-ios/blob/71464aae09f107fbf02e9ce1989d9bfb80381e37/Touhou%20VGS/VGSView.m#L63-L88

最後に、CADisplayLinkにより1/60間隔で発生するVGSLayerの描画要求のコールバック(display関数)では、描画指示を出しつつ、描画が確定している方のCore Graphics/Bitmap ContextをViewの表示内容(content)としてセットする処理を行っています。
https://github.com/suzukiplan/tohovgs-ios/blob/71464aae09f107fbf02e9ce1989d9bfb80381e37/Touhou%20VGS/VGSView.m#L131-L139

以上でVGSViewの解説は終わりです。

この部分は東方VGSに限らず、VGSを最初にiOSへ移植した時から一切変更していないので、あまり面白い内容ではないですね。だから、実装内容を淡々と解説しましたが、このVGSViewを最初に作った当初はもの凄く苦労しました。
というのも、市販のiOSアプリ関連の書籍には、役立つ内容が書かれたものが一切無かったので。(仕方が無いのでApple公式の分かり難いCore Graphicsのドキュメントを読むなどして理解しました)
ゲームのグラフィックス描画処理というと、最近ではOpenGL or MetalといったGPU Rendererを用いた方式に一本化されていて、スプライトやラスタといった昔ながらのコンピュータグラフィックスの描画方式は、完全に過去のものになってしまったのかな。

0 件のコメント:

コメントを投稿

注: コメントを投稿できるのは、このブログのメンバーだけです。

合理的ではないものを作りたい

ここ最近、実機版の東方VGSの開発が忙しくて、東方VGSの曲追加が滞っています。 東方VGS(実機版)のデザインを作りながら検討中。基本レトロUIベースですがシークバーはモダンに倣おうかな…とか pic.twitter.com/YOYprlDsYD — SUZUKI PLAN (...