GA 3D Engine の秘密と Zaurus の深い話

2000/06/24 (最終更新:2005/08/28 15:27)

◎ いまさらなコト、ごめんなさいの話

本当はもっと早く書こうと思ってためていた(というか書いていた) いつもの読み物なんですが、いろいろと忙しくて時間も無く、 すっかり遅くなってしまいました。

実際に MORE ソフト開発、特に 3D GA Engine を作っていたのは 今年(2000年)の1月であり、実はそれ以後ほとんど開発には携わっていません。 MOREコンテストまではちょっとしたバグ修正のみやってましたが、 その後はそれすら全く行う時間がありませんでした。 いろいろとバグ報告もいただくのですが全く何もできない(していない) 状態でしてごめんなさい。 希望者には本当にソース公開という形で対応させていただきます。

さて今回は、あらためて GA 3D Engine やら MORE 開発に関わる Zaurus 内部の深いことについて覚えている範囲で解説してみます。 後半は結構ハードな内容なのでご了承ください。

これらの内容を調べたり試していたのは 2000年1月の段階のものであり、 それ以後は本当に研究も調査もしていません。 全部当時調べた記憶を頼りに解説しますので、 忘れていたり間違っていたりしたらごめんなさい。

◎ いまさらながら 第2回MOREソフトコンテスト

とその前に、 完全に風化したお話ですけど、 SHARP の 第2回MOREソフトコンテスト では ゲーム部門で R-panel が入選いたしました。 いまさらながらどうもありがとうございました。

審査員の方のコメントを見ると、 Zaurus で動く3Dポリゴンエンジンはそれなりにインパクトがあったようです。

コンテストには応募作品が何本あったのか分かりませんが、 当時 SZABサポートページに登録されているユーザー MORE ソフトは確か 110本程度だったと覚えています。 にもかかわらず、一人で 11本ものソフトを応募してしまったのは私です。 内容は全部公開済みのソフトなのに、数打って一つ当たってしまいました。

◎ 遅い遅い Zaurus の描画

Zaurus で 3Dポリゴンを出す、なんて話題を切り出そうものなら、 誰もがみな冗談だと思ったはずです。 本気だと気がついた人はおそらく誰もいません。 去年(1999年)の話しです。

たとえやれば出来るかもしれないけれど、 そんなこと無謀なことに挑戦する人がいるわけないでしょ。 出来るかどうか分からないことに労力を割こうなんて無駄無駄。 常識的に考えたら確かにその通り!

3Dポリゴンというと、難しい、重い、複雑、面倒、 そんなイメージがあって、とてもとても、Zaurus なんかで動くわけがない。 携帯用のゲーム専用機だってポリゴンなんて出ないんだから。

事実、SZAB の開発キットを触って最初に感じたのは、 画面描画が非常に遅いこと。 この遅さを見てしまうと、3Dポリゴンどころか、 2Dのリアルタイムのアクションゲームを作ることすら 十分ためらうだけの説得力があります。

いやはや。 高速な画面転送 API 一つあれば、アクションゲームだろうと、 ポリゴンだろうと作ってみせるのに、これでは絶対に無理だ。 そう思ったのがちょうど1年前(1999年5月)、 MI-110M を使って初めて SZAB をさわったときでした。

◎ やられた、MI-110M のワナ

リアルタイム系アプリをあきらめたので、 SZAB ではツールばっかり移植してました。 昔作ったC言語時代のアプリの移植が主です。 それなりに忙しかったので、開発に時間のかからないものばかり選んでました。

MI-P2 を経て、MI-C1 を手に入れた頃(1999年12月7日)、 レポートを書くついでに簡単なベンチマークテストのソフトを 作る機会がありました。 深い意味もなく、C1 の速度がどれくらいのものか、 ネタにするため思いついて実験しただけです。 ここで衝撃的な事実が判明してしまいました。

このベンチマークテストソフト (ZaurusSpeedTest) の結果から、なんとオフスクリーンから画面への描画転送が極端に遅いのは、 MI-106/106M/110M だけということが判明。

MI-C1 はもちろん、同じモノクロの MI-P2 ですら、 数値的にはそこそこ実用に耐えうる速度が出ています。 これなら全然躊躇する必要がなかったのに、後悔してもしかたない。

◎ 出来ちゃった3Dエンジン

ZaurusSpeedTest の結果を元に、 リアルタイムにバッファ転送を行うアプリケーションの実験を行っていました。 正月休みに Dreamcast のシェンムーをやりながら。

当初は 2D スプライトのパターン転送を使ったもの (ChiToShuのようなもの) だったら簡単にできるだろうと思って作っていたのですが、 ついついポリゴンまで動かすことになってしまいました。 GA 3D Engine の GA とは、 たまたまライブラリ関数名が、全部 Ga で始まっていたからです。 (ライブラリファイル名も galib) なんで Ga かというと、直前に作った別マシンの 3D Engine が、 Gs で始まる名前になっていたのでそれをもじっただけ、それだけです。

第一段アプリは意地でも 1999年内(12月29日)に公開した 3D IM-Clock for Zaurus です。 Zaurus でポリゴンアプリが登場したのは紛れもなく 1999年ですので、 もし聞かれたら「Zaurus には 1999年からポリゴンアプリがあったんだ」 と堂々と答えましょう。

◎ 年越しで作った R-panel

3D Engine のライブラリを整備しながら作った 第一段ゲームは、 166MHz のノートPC + CE-170 + MI-C1 のみという最小構成で、 モバイル状態で開発を行いました。 1999/12/31 から作り始めて、完成が 2000/01/01。

次の日初めて人にテストさせたら、 液晶を割りそうなほど激しくペンでたたきながらプレイする姿が印象的。 買ったばかりで一ヶ月もたっていない新品 MI-C1 を こんなに雑に扱っていいんだろうか。見てる方がはらはら。 パネルを割りそうなゲームなので R-panel と命名させていただきました。

◎ 動作速度のボトルネックを探る

ここから突然かなりまじめな話になります。

MI Zaurus は、32bit の RISC CPU である HITACHI の SH3 を積んでいます。 これは各種組み込み系の機器や WindowsCE 機などにも使われており、 それなりに高速に動作します。

ところが MORE ソフト開発をしていると、 ときたま動作がやたら遅いなと感じる場合、感じる瞬間があります。 いったいなぜ遅いのか、 そしていったいどこがボトルネックになっているのでしょうか。

リアルタイム系アプリケーションを作っている場合は、 描画の遅い原因として ZaurusOS の 描画API に原因があるのではないかと 考えがちです。 実際にブロック転送 API の速度は、測定してみると実に遅いことがわかります。 GA 3D Engine でも、全体の動作速度のうちこの API が占める割合は決して 少ないものではありません。

CPU 自体はそれほど遅くないのだから、 この API さえ速ければ GA 3D Engine ももっと快適に動くのに。 もしくは、もっと速い転送 API さえ用意してくれれば、 3D Engine もリアルタイムアプリケーションも作りやすくなるはずです。

ところが、実際のところそう単純なものではありません。

ポリゴンのようなフレームバッファ方式で描画を行う場合、 直接画面に描画せず、あらかじめオフスクリーンバッファに対して 描画シーンの構築作業を行います。 その前に、このバッファをいったんクリアしなければならないのですが、 そのクリアを組み込んだだけで極端に処理速度が低下します。

もともと Zaurus は、内部的に描画内容が全部 16bit RGB 値で管理されています。 モノクロカラー機種を問わず、常に 16bit カラーで扱われるので、 ピクセルを扱うコストはかなり高くなっていると考えられます。 当然転送するメモリ量も馬鹿にはなりません。

これまで ZaurusSpeedTest をはじめとするベンチマークテストを行い、 GA 3D Engine の開発を行いました。 特に GA 3D Engine の場合は、アセンブラでいくつものコードを書きながら、 速度を比較し、高速化手段を見つけるために試行錯誤を繰り返しています。

以上の結果ら導き出される結論は単純で、 MI Zaurus のメモリバスは低速であること、 この一言ですべてが片づけられます。

特に、連続するメモリアクセスに対してはキャッシュが効果的に働きますが、 キャッシュが利きにくいメモリアクセスに対しては、 極端なほどの速度低下を招きます。 MORE ソフトの開発で、遅いなと感じるのはメモリアクセスが原因であり、 実際のところ、キャッシュのヒット率を考えてプログラムを書かなければ それなりの高速化は望めないことがわかります。

よって、ZaurusOS のブロック転送 API は手抜き処理のために遅いわけではなく、 Zaurus 全体の速度そのものがその程度しかないといえるのです。

◎ バスの話

実際に詳細を調べたわけではありませんし、確実な証拠があるわけでもありません。 あくまでプログラムを書いてみての経験上のものなので、 話半分程度にしてください。 まず、Zaurus のバスはかなり遅いのではないかと思われます。

高速転送の実験をしていると、連続アクセスでは word アクセスと longword アクセスの差があまり表面にでてきません。 ところが非連続アクセスの場合は、速度に対する挙動が異なっています。 おそらくキャッシュやメモリバッファのせいだと思うのですが、 もしかしてメインバスが 16bit ではないか、との推測が成り立ちます。

もともと HITACHI の SH シリーズは、バス設計の柔軟性が売りで、 さまざまなバスを混在させることができました。 また ZaurusOS の API では、32bit CPU らしくない short データの多用がかなり目に付きます。 少ないメモリを有効に活用する目的だと思いますが、 ひょっとしたら 16bit で済むデータは 16bit に納めた方が 速いという理由も兼ねているのかもしれません。

◎ メモリバスを通るデータ量

フレームバッファ方式でリアルタイムアプリケーションを描画する場合、 次のような手順を踏むことになります。

1. オフスクリーンバッファのクリア
2. オフスクリーンバッファに対して描画シーンの構築(スプライト描画など)
3. オフスクリーンの内容を実際の画面に描画転送

1. は単純な 0 クリアとします。 2. はほぼ画面に対して 1画面埋める程度のスプライトパターンの描画が 行われるとします。 3. は単純なメモリ上のコピー転送であると仮定します。

このとき、バスを通るデータ量は、画面サイズ分のメモリ量の5倍に相当します。 計算の仕方は次の通りです。

1. は書き込みのみ発生するため 1倍。
2. は、スプライト画像の読み込みと書き込みの2回分データがバスを通るので 2倍
3. も同様に、読み込みと書き込みの 2回分で、2倍
合計5倍

1. のクリアは、2. の段階で全画面埋め尽くすとわかっていれば省略することが できます。このときは 4倍です。

1. のクリアの代わりに、背景画像を先に全転送して埋め尽くすことも考えられます。 このとき転送量は、1でも読み書きの2回分必要で 6倍になります。

GA 3D Engine のアプリケーションの動作速度は、 メモリバスを通るデータ量に比例します。 つまり、動作時間の大半が、バス上のメモリアクセスに取られていることになります。

◎ Zaurus でポリゴンが動く秘密

GA 3D Engine は、Zaurus 上でも比較的軽快にポリゴンが動き回ります。 それはなぜでしょうか。実は大きな秘密があります。

バスの話でしたように、GA 3D Engine のアプリケーションの動作速度は、 バスを通るメモリアクセスの量にほぼ完全に比例します。 ポリゴン描画の場合、バスアクセスの量は 4倍であり、2D スプライトよりも 1画面分転送量が少なくて済みます。

1. オフスクリーンのクリアで1倍
2. シーンレンダリングで1画面分書き込むとして、1倍
3. オフスクリーンの転送で、読み書き2回分アクセスが発生して2倍

2Dスプライトの場合は、あらかじめ用意した画像を読み込んで書き込み転送する わけですが、 フラットシェーディング、テクスチャなしのポリゴンの場合は、 面を塗りつぶす色は完全に計算だけで求まります。 そのため読み込みが不要であり、メモリバスには書き込みアクセスしか発生しません。

よって、Zaurus のように CPU に比べてバスが極端に遅いシステムでは、 2D よりも 3D の方が高速に動いてしまうことになるわけです。

都合のいいことに、単色で塗りつぶしたとしても 光源処理が施されたポリゴンはそれなりの 説得力を持つので、画面的なクオリティはさほど失われずに済みます。

さらに、ポリゴンは難しいもの、すごいもの、という先入観があるために、 Zaurus でポリゴンが表示できるのは すごい! と思いこんでしまう トリックがあります。

もちろん、バス帯域だけで 2D より 3D の方が高速であるという結論を 導き出すことはできません。 メモリバスのアクセスに対して、CPUによる 3D計算等の処理は、 比較にならないほど高速であるという前提条件が必要なのです。

◎ 3D 計算の話

ボトルネックがバスアクセスである件については、 割と早い段階から判明していました。 ところが 3Dポリゴンの場合は、同じくらい重い処理であるジオメトリ 演算が付き物です。

困ったことに、Zaurus の CPU である SH3 には FPU が内蔵されていません。 FPU 内蔵の SH3 もありますが Zaurus には使われていないのです。

SH3 には浮動小数演算用のライブラリルーチンが存在しており、 実数演算を行うことが可能です。 もちろん、既存の CPU 命令の組み合わせで作られているわけで、 整数演算より速いわけがありません。

SH3 には、64bit の積和演算命令 mac.l が搭載されています。 これは 32bit × 32bit + 64bit = 64bit の整数演算を 1命令でこなす高速なものです。 積和演算は、ベクトルの内積やマトリクス計算など、 ジオメトリ演算で多用されるので、うまく使えば高速化が期待できます。

この命令は特殊なので、intel でいう MMX のように、 通常 Cコンパイラは使ってくれません。 アセンブラを使う必要があります。 またレイテンシ2の実行2サイクルで乗算器が並列動作するので、 うまくレイテンシを隠すように命令を配置しなければなりません。

ポリゴン用の実数演算を行うために、32bit (16bit小数) の固定小数演算を用いることにします。 積和演算命令が 64bit で値を返すので、 この方が都合がいいからです。

他にも SH3 には、32bit × 32bit = 64bit の乗算命令や、 64bit から 32bit の固定少数値へ変換するための便利な命令が存在しています。

例として、 内積演算を mac.l を使って書くと、このような非常にシンプルなコードになります。 (ただしこのコードの並列度は低い)

;DVAL
;GaVectDot( GaVect *vp0, GaVect *vp1 )
	.text
	.align	4
	.global	_GaVectDot
_GaVectDot:

	sts.l	macl,@-sp
	clrmac
	mac.l	@r4+,@r5+
	mac.l	@r4+,@r5+
	mac.l	@r4+,@r5+
	sts	MACL,r0
	sts	MACH,r6
	xtrct	r6,r0
	rts
	lds.l	@sp+,macl

	.text

GA 3D Engine では、 このようなジオメトリ演算とライティング計算は ほとんどがアセンブラで書かれており、 すべて固定小数演算で完結するようにライブラリが組まれています。

その結果として、メモリアクセスに比べると、 3D等のCPU計算はずっとコストがかからなくなりました。 高速化のためには、複雑な計算をさせてでもバスアクセスを減らすことが 重要になります。

余談
余談ですが、SH4 になると最初から FPU 内蔵で、さらに内積程度の演算は 1命令実行1サイクル(128bit演算器)で実行することが可能になってます。 うらやましい限り。 SH5ではさらに 64bit 化、レジスタの大幅追加、32bit命令セット、 MMX のようなマルチメディア命令の追加が行われるそうです。

◎ GA 3D Engine が 16bit テクスチャを使う理由

テクスチャアクセスは容量も大きく、 ピクセルの読み出しがランダムに発生するので決して高速な処理ではありません。

ソフトウエアのレンダリングを考える場合、光源処理を行うにはテクスチャの 階調表現をどうにかしなければなりません。 通常使うのは、8bit パレットのテクスチャを用いて、パレットテーブルだけ 必要な階調分だけあらかじめ用意する方法です。

DirectX5 までの Direct3D には、このような高速レンダリングを行うための ソフトウエアデバイスとして HEL の RAMP ドライバが存在していました。

GA 3D Engine も、最初は 8bit のパレット形式のテクスチャを使用する方針で 設計をしていました。 ところが実際に作ってみると、思ったほど速くないどころか、 かえって遅くなってしまいました。 これは、ピクセル単位に byte アクセスの後、パレットインデックスによる 16bit 変換でさらにメモリアクセスが発生するためです。 この段階で 16bit プレーンのテクスチャの2倍のバスアクセスが発生します。

GA 3D Engine で最終的に 16bit テクスチャを採用したのはそのためで、 実際にためしてみてこの方が速かったからなのです。 メモリを大量に消費する力業ですが、 テクスチャ+光源処理+グーローシェーディング もこの方法で実現しています。

◎ GA 3D Engine の Zソートライブラリが遅いわけ

GA 3D Engine は Z バッファを持っていません。 メモリアクセスが遅いので、Zバッファの使用はほとんど不可能だと思われます。 既存のアプリケーションでは厳密なソートもしておらず、 逆にソートしなくて済む程度の内容にアプリケーションが制限されているといえます。

最終的なリリースでは GA 3D Engine は、 Zソートライブラリをオプションで提供しています。 ところがこれは非常に遅く、ソートをかけるだけで極端に処理落ちします。

その原因は、ソーティングにリスト構造を使用していて、 メモリのランダムアクセスが発生するからです。 リスト構造はシンプルな処理になるものの、キャッシュのミスヒットが多くなります。 大した量のメモリじゃないのですが、 Zaurus のメモリアクセスが遅いことが再び露呈してしまう羽目になりました。

ちなみに GA 3D Engine のページのトップに飾ってある teapot のポリゴンモデルは、 この Zソートライブラリを使用しており、グーローシェーディングがかかっています。 この状態で MI-C1 で 10fps をやっと越える程度の速度しかでていません。 (ソートを切ると 16〜20fps 近く出ていたように覚えています)

◎ ChiToShu のパターンデータ

ChiToShu は、 GA 3D Engine の 2D スプライト機能のみ使ったアプリケーションです。 先に述べたように、パターン転送を伴うスプライト描画は書き込み性能で 3Dポリゴン(テクスチャなし)に劣ります。

ChiToShu のキャラクタデータの多くが抜き色を多用し、穴あきパターンが多いのは ほんのわずかでもメモリアクセスを減らそうという考えがあったからです・・。

◎ ZaurusOS の描画APIは本当に遅いのか

CPU転送によるメモリアクセスの結果から、 バス速度に関するデータや考察を導き出すことができます。 その結果と ZaurusOS のブロック転送描画API (ZaurusSpeedTest でいう COPY) の速度結果を比較すると、いろいろとわかってくる面があります。

まず、実測値との比較によって、 描画転送はすべて CPU による単純なデータコピーであることがわかります。 SH3 には SH2/4 のように DMA は載っておらず、 画面描画に専用のアクセラレータを用意しているようには見えません。 CPU コピーだけで実現されているとなると、 描画 API は速度的にはそこそこの内容であることがわかります。

モノクロ機種の場合、ZaurusSpeedTest で COPY だけが極端に遅いことから、 16bit カラーから 4bit モノクロ16階調への変換を、 CPU 処理していることが推測されます。 おそらく、CPU でコピーしながら減色変換を行っているのではないでしょうか。

これにより、同じモノクロ機種でも最初に登場した MI-100シリーズだけ 極端に遅い理由も想像がつきます。 後の機種ではソフトウエアのチューニングによって、 高速化が図られたのではないでしょうか。

この結論にはある程度の根拠があります。 実は Zaurus の SH3 アセンブラで 16bitカラーから 4bitモノクロ16階調への変換ルーチンを実際に書いたことがあります。 これは割とがんばってチューニングを施して、 ようやく MI-P2 igeti の転送とほぼ同じ速度を実現する事ができました。 大きく越えられなかったのが悔しいのですが、 MI-110M に比べて MI-P2 igeti のブロック転送描画 API は、 かなりぎりぎりまでチューニングされていることがわかります。

◎ 最後にちょっとした注意事項

今回述べている速度に関しての考察は、 Zaurus の CPU 動作やメモリアクセスといった割とハード面でのお話です。

ファイルシステムがどうだとかフラッシュアクセスがどうとか、 そういう OS 設計がらみの部分とは無関係ですので、 お間違えないようどうぞよろしくお願いいたします。

[戻る]
[メニューに戻る] [ZAURUS総合] [DirectX] [Ko-Window] [Win32] [WinCE] [携帯電話] [その他]
フルパワー全開 Hyperでんち

Hiroyuki Ogasawara <ho>