2012年7月24日火曜日

効果音の作り方

アマチュアがゲームを作るとき、効果音を自作している人はどれぐらい居るのでしょうか?
ふと、疑問に思いました。
割と有名なフリー素材があり、そこから拝借・・・という手があります。
しかし、私の経験上、自分で作った方が楽です。
素材だと、気に入った音を探すのが大変だし、ライセンス(使用許諾)の縛りも発生するので。
つまり、技術的にも権利云々(著作権とか)でも、自作した方が楽です。

(0)必要な機材

必要なのは、PCと波形エディタソフトだけです。
マイクとかは不要です。
自然音を加工して使いたいのであれば別ですが、ゲームの場合、自然音を使うと逆に不自然。

私は、3種類の波形エディタを使っています。(全部フリーウェア)
効果音エディタ_D
wavy
③Tiny Wave Editor


(1)音を作る


一番大事なことは、「効果音エディタ_Dで目的の音を自在に作れること」だと思います。
適当に弄っていても作れますが、音色の仕組みを理解すれば、ある程度狙って作れます。

a) サイン波
最も基本的な音色はサイン波(下図)です。

効果音エディタ_Dで波形(音色)のところに上図のような線を描き、再生してみてください。
たぶん、「ぽーーー」という感じの丸い音が鳴ると思います。
笛系の楽器の音がだいたいサイン波です。
マイクを持っている人は、笛の音を録音して、1周期(凸と凹の1セット)を見れば分かります。



b) 三角波

サイン波を少し変化させた感じの音色に三角波(下図)があります。
三角波は、概ねサイン波と同じような感じの音です。
ただ、ちょっと鋭い感じになります。
「ぺーーー」という感じでしょうか。
ちなみに、ファミコンのベース音でよく使われるのが、この三角波です。


c) 矩形派

嘗て、電子音といえば矩形派でした。
理由は波形を作るための計算が物凄く簡単なので。
つまり、安価なマイクロチップで処理できます。
AY-3-8190とかのPSG(Programmable Sound Generator)は矩形派ですね。

「ピーーー」という感じの音が鳴ります。
楽器でいうとクラリネットとかよく言われますが、個人的には「?」です。

ポイントは、
・サイン波=ポーーー
・三角波=ペーーー
・矩形波=ピーーー
です。

相違(図では黒線の位置)の変化が大きいと、音が鋭くなるという感じでしょうか。
それだけ抑えておけば、だいたいどんな音でも狙って作れます。
あとは、デューティー比(プラスの相違とマイナスの相違の比率)を調整したり、合成したり等々。


d) ノコギリ波
メロディーとしてよく使われる音色がノコギリ波ですね。
ちょっと、分かり易くするために2周期分のノコギリ波を描いてみました。
サイン波や三角波との決定的な違いは、1周期終わった後の変化量です。
上から下まで一気に降下しているのが分かると思います。
この大きな変化量が、非常に心地よい違和感を与えてくれます。
楽器でいうと、ストリングスとかの音になります。

上図だと、2周期分のノコギリ派を鳴らしますが、単位時間あたりの波の周期の数(周波数)が多くなると、音が高くなります。1秒間に440周期で鳴らせば、オクターブ4のラの音になります。
880周期鳴らせば、オクターブ5のラですね。

効果音エディタ_Dでは、音色と同様、周波数の波(時系列での変化)も編集できます。
あとは、音量の波も編集できます。
そして、これらを弄って目的の音を作ります。
左右の出力バランスも調整できますが、私は効果音は全てモノラルで作るので使いません。

e) ノイズ
あとは、相違をランダムに変化させれば、ノイズになります。
ちなみに、ファミコンで使えるプリセット波形は、三角波、矩形波、ノコギリ派、ノイズの4種類です。

(2)音を加工する

wavyなどのフィルターツールを使い、適当な加工をします。
加工のコツですが、「加工し過ぎないこと」です。
実はこれは結構重要です。
だから、私の場合、加工フェーズで使うフィルターは概ねエコーのみです。


(3)周波数などの調整


私の場合、ゲームで使う効果音は、22050Hzのモノラルと決めています。
そういった周波数の調整をするのに、Tiny Wave Editorが役立ちます。
Tiny Wave Editorは、かつてヤマハのサイトで無料で入手できました。
ただ、最近は配っていないようです。

海外のヤマハサイトを巡回すれば見つかるかもしれません。
ちなみに、私は3年ぐらい前にカナダのヤマハサイトで見つけました。
(一応、まだ無いかチェックしてみましたが、もう無いようです)


(4)効果音を入れるコツ

細かい全ての動作に対して効果音を入れると良いです。
過剰に設定するぐらいがちょうど良いです。
ちょっと邪魔っぽい感じだったら、音を鳴らさなくする前に、音を小さくしてみると良いかも。
ちなみに、SHOT04(NokogiRider)の体験版で使っている効果音は、34種類。
現段階(3面途中)では40種類。
完成版では、少なくとも50種類ぐらいになると思います。

2012年7月19日木曜日

今後の対応について

今後のアプリ開発の方向性について、Androidの扱いをどうするか真剣に考える必要がありそうです。
とりあえず、前の記事に書いたパフォーマンス劣化の問題が酷すぎるので。
最初から、iPhoneをメインを据えるべきだったか。。。

ただ、この問題が私の使った端末(Razr)固有のものだったら、まだ救いはあります。
たぶん、2.3から4.0へアップデートできる端末はそんなにはないので、そのケースの地雷を踏んだだけかも。
そうであれば、まだAndroidを切るという判断は早い。

更に、SHOT04(NokogiRider)の体験版を既にAndroidMarketで無料配布していて、尚且つ1,000本近いダウンロードもして頂いたので、ここで頓挫してしまったら楽しみにして頂いている方たちに申し訳ないです。まだ、日本の方からは感想は貰ってませんが、海外の方からはメールで感想を貰っていて、楽しみにしているとのことでしたし。


でも、私のメイン端末でデバッグができないのは痛すぎますが。
その点は、2.3系のスマホを別途(白ロムで)入手して、何とかしようと思います。
一応、2.3系のタブレットは持ってますが、やはりメインターゲットはスマホなので、スマホのデバッグ機は1台は欲しい。

まさか、1年で3台買うハメになるとはね。。。orz
開発環境のコストはタダ同然(最初に25$必要なだけ)だから、最初は(iPhoneではなく)Androidにしたのですが、もしかすると裏目だったかも。

2012年7月16日月曜日

Android 4.0で発生するパフォーマンス劣化の問題について

下記の記事で記載している性能劣化の問題ですが、Razrに対して2012年9月に提供されたシステムアップデートを適用することで、だいぶ改善されることを確認しました。ただし、まだまだ不安定です。概ね60fps程度で動くようになったのですが、バラツキが非常に激しいため、Android2.3の頃のような安定度はまだありません。
2012年9月7日・記

前の記事に書いている内容と重なる部分があるかと思いますが、Android4.0(3.0を含む?)で発生する、アプリケーションのパフォーマンス劣化に関する問題について、今、私が把握している情報を纏めておきます。

(1)どんな問題?

Android4.0(4.0.4)では、SurfaceViewCanvasを使って画面描画を行っているアプリケーションの動作性能(パフォーマンス)が極端に劣化する問題が発生します。
私はこの現象を、Motorola社のRazrという機種で確認しました。

SurfaceViewCanvasとは、2Dゲームなどでよく使われる「高速な描画」を行うための機能です。
私(SuzukiPlan)が開発しているアプリの場合、次のようなロジックでこれを利用しています。
/*
 *============================================================================
 * サーフェース・ビュー
 *============================================================================
 */
class VgeSurfaceView extends SurfaceView implements SurfaceHolder.Callback,Runnable {
    public Thread thread;
    public boolean isAttached;
    private final int XSIZE=240;
    private final int YSIZE=320;
    private Bitmap vram;
    private Rect vramRect;
    private Rect screenRect;
    private Paint screenPaint;
    /*
     *------------------------------------------------------------------------
     * コンストラクタ
     *------------------------------------------------------------------------
     */
    public VgeSurfaceView(Context context) {
        super(context);
        vram=Bitmap.createBitmap(XSIZE,YSIZE,Bitmap.Config.RGB_565);
        vramRect=new Rect(0,0,XSIZE,YSIZE);
        getHolder().addCallback(this);
    }
    /*
     *------------------------------------------------------------------------
     * サーフェース変更の検出
     *------------------------------------------------------------------------
     */
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        int xPos=0;
        int yPos=0;
        float wX=(float)width;
        float wY=(float)height;
        if((float)YSIZE*(wX/XSIZE)<wY) {
            wY=YSIZE*(wX/XSIZE);
            yPos=0;
            height=(int)wY;
        } else {
            wX=XSIZE*(wY/YSIZE);
            xPos=(width-(int)wX)/2;
            width=(int)wX;
        }
        Log.i("SHOT04-J","Width="+width);
        Log.i("SHOT04-J","Height="+height);
        screenRect=new Rect(xPos,yPos,width+xPos,height+yPos);
        screenPaint=new Paint();
    }
    /*
     *------------------------------------------------------------------------
     * サーフェース作成の検出 (スレッド起動)
     *------------------------------------------------------------------------
     */
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        isAttached=true;
        thread = new Thread(this);
        thread.start();
    }
    /*
     *------------------------------------------------------------------------
     * サーフェース破棄の検出 (スレッド停止)
     *------------------------------------------------------------------------
     */
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        isAttached = false;
        while (thread.isAlive());
    }
    /*
     *------------------------------------------------------------------------
     * メインループ (スレッド)
     *------------------------------------------------------------------------
     */
    @Override
    public void run() {
        SurfaceHolder holder=getHolder();
        Canvas canvas;
        int rc=0;
        while (isAttached) {
            rc=SHOT04.setVram2(vram);
            canvas=holder.lockCanvas();
            if(null!=canvas) {
                canvas.drawBitmap(vram,vramRect,screenRect,screenPaint);
                holder.unlockCanvasAndPost(canvas);
            }
            if(rc!=0) break;
        }
        if(0!=rc) {
            System.exit(0);
        }
    }
}

上記の青色の部分が問題が発生する箇所です。
問題の処理は、
 1. 240x320(QVGA)サイズのBitmap(AndroidBitmap)を更新(SHOT04.setVram2
 2. そのBitmapを表示(lockCanvasdrawBitmapunlockCanvasAndPost
という単純な処理を繰り返し実行しています。
Android2.3では、この処理では60fpsの性能を(かなりの余裕をもって)キープできていました。
しかし、Android4.0では、40fps~50fps程度の性能しか出なくなります。(※Razrの場合)

(2)原因は何?

推測ですが、Android2.3の場合、上記の描画処理でハードウェアアクセラレーション(GPU)が作用してました。
※なお、GPUを搭載していない端末の場合を除きます。(Android2.3を搭載しているスマホのほぼ全てに搭載していますが、ただし、一部の端末では搭載していない場合があるようです。GPUを搭載しているかどうかについては、あなたが所有する端末の公式サイトなどに掲載されているスペック表で確認できます)

しかし、Android4.0の場合、上記の描画処理でGPUが作用せず、ソフトウェア描画になったものと思われます。
その結果、遅延が発生したものと考えています。
その原因について調査した結果、下記のフォーラムを発見しました。
https://groups.google.com/forum/?fromgroups#!topic/android-developers/CK_jkHEQoOM

なお、上記フォーラムはAndroid3.0に関するものなので、本件とは同件では無いかもしれません。
ただし、質問者のコンディションと上記の実装が一致するので、私は同じ問題だと考えています。
上記の回答にあるGoogleのAndroid開発者、Romain Guy氏曰く、
In this case you won't get hardware acceleration.
この場合(SurfaceViewとlockCanvasを使用する場合)、ハードウェアアクセラレーションは作用しません。
We couldn't add this feature on time for Android 3.0. Sorry about this.
我々(Google)は、この機能をAndroid3.0で実装する時間がありませんでした。申し訳ありません。
とのことです。
私の和訳は若干怪しい部分があります。具体的には、on time forのところ。
Google翻訳の場合、「Android3.0の時」という風になりました。
ただ、「could not X on time Y」って「XがYに間に合わなかった」って意味では?(※カンです)
という訳で、上記のような翻訳にしておきました。

ただ、上記の記事は1年半ぐらい前のものです。
そして、Android4.0ではなくAndroid3.0に関するものです。
なので、不良ではなく仕様の可能性が高いと思っています。

追記:あと、そもそもAndroid2.3の時もこのケースではGPUは働いていなかった可能性もあります。
つまり、ソフトウェア描画だったけど、極端に性能が劣化してしまったということ。
劣化の度合いが大きいので、その可能性は低いと想定していますが。
まぁ、結局のところ、本当の原因はAndroidのソースコードを追いかけなければ分からない・・・ということ。

(3)アプリ利用者が問題を回避する手段は?

ありません。
Android4.0の場合、「設定」の「開発」で「全ての2D描画処理をGPUで行う」というモードがあります。
しかし、そのモードを設定しても、上記問題を回避することはできません。

(4)アプリ開発者が問題を回避する手段は?

■マニフェストによる回避は不可能
しばしば、android:hardwareAccelerated="trueをマニフェストに定義すれば良いんでない?」という意見を見ますが、その方法での回避は不可能です。
まず、この設定項目のデフォルト値は、"true"です。
そもそも、この設定項目は、GPUの使用を抑制するために定義するものです。
(そして、上記(3)の設定(強制GPUモード)は、falseを強制的にtrueにするものです)
なので、マニフェストの定義で、問題を解決することはできません。

[訂正]
上記については、若干間違いがありました。(マニフェストで回避不可能な点は正しいです)
デフォルト値はfalseでした。
ただし、実機確認した限り、SurfaceView+lockCanvasではtrueにしてもハードウェアアクセラレーションは作用しません。性能劣化があまりにも極端だったので、GPUが作用していたものがしなくなったものと実動作から判断しましたが、Android3.0以降(4.0以降を含む)は、有り得ないレベルで性能がデグっているだけという可能性もあります。(これについては、Androidのソースコードを追いかけて調べるしかないですが、未だ調べてません)

■GLSurfaceView(OpenGL/ES)へ移行による回避
この回避方法を用いれば、大半のアプリケーションは問題を回避できます。

しかし、(私が開発しているアプリのように)ピクセル単位で描画内容を更新するアプリの場合、この方式での問題回避は不可能です。ピクセル単位で描画内容を更新するアプリとは、例えば、エミュレータ(SNES,NES,MAME)などが該当します。

ピクセル単位で描画内容を更新するアプリがOpenGL/ESで目的とする処理を実現しようとする場合、テクスチャのフレーム単位での更新が必要になります。しかし、画像情報をテクスチャに変換する処理は(GPUがやっていますが)メチャクチャ遅い欠点があります。Android(やiPhone)のテクスチャは、GPUの内部では「PVRTC」という圧縮形式で保持する必要があるのですが、この圧縮方式への変換に極めて大きなオーバヘッドを要するため、240x320もの大きなサイズのテクスチャを60fpsで変換することはRazrで検証した限り不可能でした。(これについての詳しい検証内容は、前の記事を参照してください)

■RSSurfaceViewへの移行による回避
この方式については検証していません。
しかし、私の場合、そもそもこの方式を採用することが不可能です。
というのも、RSSurfaceViewはAPI level 11以降でないと使えません。
API level 11以降の場合、Android2.3以前での動作が不可能になります。
API level 11以降は、Android3.0以降で動作するアプリケーション専用

現在、世の中で最も普及しているAndroid2.3を切るという選択肢は、当面ありえません。
今のところ、Android3以降の機能を使いたいアプリ限定で利用可能な選択肢といえます。
少なくとも、ゲーム開発者でそれを考えている人はほぼ居ないと思います。

■結論
大半のアプリは、GLSurfaceViewへの移行で問題を解決できます。
私が検証してみた限り、けっこう大変な作業になると思いますが。。。
ガッツがある方は、がんばって移行してください。
しかし、ピクセル単位で描画内容を更新するアプリの場合、アプリ開発者でも問題の回避は不可能です。

つまり、Android4.0(or3.0)でデグってます。
Googleが修正しない限り、何ともなりません。
「Googleが修正しない限り」というのは、必ずしも正しくはないかもしれません。今では、Motorola側の作りこみ部分の不具合の可能性の方が高いかも・・・と、思っていたりします。
2012年9月7日・記
なお、この結論は、私なりにちゃんと実機で検証した上で得たものです。
しかし、私が技術的に至らないだけで、別に良い回避手段があるかもしれません。
私は、その可能性について、探り続けるしかありません。
(ですが、全然見つからないので、開発自体を投げ出しそうです...)

もちろん、Googleが本件を不良と認めて、修正するのがベストですが。
ただ、私の英語力では、Googleと交渉する自信が全くありません。
他力本願でアレですが、そこは英語が得意な人or米国のデベロッパーにお任せで。
たぶん、4.0がもっと普及すれば、米国でもすぐに叩かれる筈。

なお、この記事を英訳して、blogへ記事を転載してくれる方が居るとすれば、有り難いです...
この記事の内容の転載はご自由にどうぞ。
ただし、転載先で発生したトラブル等についての責任ついて、私が取ることはできません。
その点はご了承ください。

2012年7月14日土曜日

OpenGL/ESによる2D描画の高速化 (update-6)

VG-EngineのJava部分の描画処理をSurfaceView+CanvasからOpenGL(GLSurfaceView+GL10)に変更したところ、処理性能が大分落ちてしまった問題が直りつつあります・・・ですが、今一歩。。。

ポイントとなるonDrawFrameの処理は以下のような感じ。
public void onDrawFrame(GL10 gl10) {
// VG-Engineの描画処理を呼び出す
int rc=SHOT04.setVram(vram);
if(0!=rc) {
System.exit(0);
}

// VRAMの内容をテクスチャ領域へ転送
// GLUtils.texSubImage2D(GL10.GL_TEXTURE_2D,0,0,0,vram);
gl10.glTexSubImage2D(GL10.GL_TEXTURE_2D,0,0,0
,TXSIZE
,YSIZE
,GL10.GL_RGB
,GL10.GL_UNSIGNED_SHORT_5_6_5
,vbuf
);

// 描画
drawUv.put(vramPositions);

drawUv.position(0);
gl10.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl10.glVertexPointer(3, GL10.GL_FLOAT, 0, drawUv);
gl10.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);

}

最初のSHOT04.setVramというのは、VG-Engine(エミュレータ)の中核描画ルーチンです。
従来、引数に渡しているvramはBitmapクラス(AndroidBitmap)でしたが、byte配列に変更。
この部分で、仮想VRAM(240x320pixel)の描画処理を行います。
AndroidBitmapのlock/unlockが無くなった分、高速になったかも。
ただ、ポイントはそこではないです。

ポイントは、「VRAMの内容をテクスチャ領域へ転送」というコメントのところです。
この部分を、GLUtils.texSubImage2DからGL10.glTexSubImage2Dに変更しました。
GLUtils.texSubImage2Dの場合、AndroidBitmapを引数に渡します。
しかし、転送するバッファサイズを指定できないから、常に全体を転送する無駄が生じます。
OpenGLの仕様上、テクスチャ画像の辺の長さが2のn乗でなければならないので、VG-EngineのVRAM仕様(QVGA=240x320)の場合、AndroidBitmapのサイズは256x512pixelという具合になり、無駄なデータ転送が発生する訳です。

GL10.glTexSubImage2Dの場合、テクスチャバッファに転送するバッファの幅と高さ情報を指定できるので、転送量を256x320にすることが可能になります。(ちなみに横方向は240にすると危なそうなので、256のままにしておきました)

これにより、転送量を262,144byteから、163,840byteに削減できます。
ほぼ半分程度の削減になりますね。(47.5%の削減
OpenGL内部でのバッファ⇒テクスチの変換処理が恐ろしく遅いので、この削減効果は極めて大きいです。

ちなみに、テクスチャやテクスチャの表示位置を設定する処理は、全てonSurfaceChangedで纏めてやってます。
つまり、変換イベントを検出しない限り、1回ポッキリ。
こうすることで、バッファリング演算の処理回数を大幅に削減できます。
大幅っていっても1テクスチャのみだから高が知れてますが。



描画時の頂点バッファ演算(2ポリゴン分)は削れませんが。(この部分も最適化の余地があります)
なお、サーフェースのクリア処理(glClearとか)は大して重くないけど、やる意味が無いから、やってません。
(テクスチャは画面全体のVRAMが1枚のみだから、こういうやり方でOK)

しかし、このやり方でやっても52~58fps程度しか出ない。(IdeaPadA1の場合)
やはり、常識的に考えて、テクスチャの書き換えというのは重過ぎる。。。
(当然ながら、ボトルネックはglTexSubImage2Dです)

ちなみに、上記はBVQ(Basic Vert Quads)という、割とオーソドックスな手法です。
DTE(Draw Texture Extension)の方が高速という噂があるので、DTEも試したのですが、結果は同程度。
(性能が同程度なら、オーソドックスな手法の方が良かろうと思います)

やはり、テクスチャの書き換えによる描画という手法自体が鬼門ですねぇ・・・。
まぁ、最初から知っていたことですが。(知っていたから、当初Canvasを使っていた訳で)
surface lockをサポートしている分、OpenGLよりはDirect3Dの方が2D向きです。
何故、OpenGLはsurface lockをサポートしていないのやら・・・
3D onlyのゲームでも普通に要ると思うのですが。

半分、諦めの境地です。
ですが、諦めるのはまだ早い。
「思い切って、フルネイティブ(NativeActivity)にしてしまえばどうだろう?」というのも、無くは無いです。
当初、Android特有の拡張機能を取り込むのに不便だから避けてましたが、必要になるとすれば広告(アフィリエイト)や何らかの通信サービスを入れる時ぐらいだろうし、そういうものを実装するつもりは今の所無いので。(でも、後からやりたくなったら困るから、中々踏み切れない)





追記

上記のonDrawFrameの処理時間を測定してみたところ、だいたい平均で9ms~10msぐらい。

個別に測定してみたところ、
・仮想VRAM作成(setVram)が3~6msぐらい(ゲーム本編の処理量による)
・テクスチャ作成’(glTexSubImage2D)が5~15msぐらい(平均すると7ぐらい)
・描画が1~2msぐらい

たぶん、これが限界性能なのかも。
もうちょっと最適化余地を探ってみますが。

あとは、JavaVM(ダルビッシュでしたっけ?)とかAndroid側の制御や、VGSの処理(OpenSL)の制御やらが色々絡んでって感じかなぁ・・・ただ、VGSは別スレッドの筈だから、そんな大したものではないです。なので、やはりNativeActivity化が一番優良の策なのかも。。。


キツイなぁ。


追記2

とりあえず、NativeActivity化する前に、onDrawFrameの処理(全部)をJNIで実行する形に改造。

↓こんな感じ
@Override
public void onDrawFrame(GL10 gl10) {
// VG-Engineの処理を呼び出す
if(0!=SHOT04.setVram(gl10)) {
System.exit(0); // Exitボタンが押された
}
}

まぁ、Javaで書く意味は無いですからねぇ。
頂点座標についても、onSurfaceChangedで、Cのバッファへコピーし、Cで作成。
ループ処理中のJavaからのデータ受け渡しは一切無いです。

こうすれば、NativeActivityで実装するのとほぼ同等性能だと思います。
たしかに、少しばかり速くなりました。
遅いタブレットマシンでだいたい55~58fpsぐらい。
Razrならほぼ60fps。

う~ん・・・今一歩です。
やっぱり、NativeActivityにするしか無いかなぁ・・・



追記3

まぁ、追記2のコードでダメだったということは、このままNativeActivity化しても効果はほぼ無いでしょう。
Javaコードは、タッチ等のイベント処理とonDrawFrameしか走らないので。
なので、圧縮テクスチャを利用してみようと思います。

一般論で、「圧縮」というと遅そうなイメージですが。
ただ、OpenGLの世界では、圧縮=色のビットレートを落とすことという意味らしい。
まぁ、圧縮であることには違いません。
データ転送量が減るから、圧縮した方が高速だということです。

16bitカラーから8bitカラーに変更した場合、glTexSubImage2Dの転送量が次のようになります。
・16bitカラーの場合 = 240×320×2 = 153,600byte
・8bitカラーの場合 = 256色×3byte+240×320×1byte = 77,568byte


VGEの仮想ハードウェアの色表示性能は8bit(256色)なので、表現性に劣化は生じません。
(VGEのハードスペックを設計した時、8bitカラーを採用しておいて本当に良かった・・・)
表現性に劣化が生じずに転送量を1/2にできるというのは美味しすぎる。
つまり、これをやらない手はないです!



追記4

ほぼ、チェックメイトになりました・・・
圧縮したテクスチャ情報を送信する場合
glCompressedTexImage2D または
glCompressedTexSubImage2D を使います。

glCompressedTexImage2Dの場合、転送する画像サイズが、テクスチャサイズと同値でないと失敗します。
・VRAMサイズ = 240x320pixel(QVGA)
・テクスチャサイズ = 256x512pixel
という差異があり、テクスチャサイズでしか転送できない場合、倍程度の無駄領域が発生してしまいます。
なので、折角圧縮して転送量が半分になるのに、無駄領域の転送により転送量が倍になる・・・
・・・要するに、完全なる無意味状態。

glCompressedTexSubImage2Dの場合、圧縮形式が通常のGL_PALETTE8_R5_G6_B5_OESとかではなく、PVRTC方式というGPU内部のアルゴリズムで圧縮しなければなりません。
一応、glext.hに以下の4種類が#defineされています。

#define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG                      0x8C00
#define GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG                      0x8C01
#define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG                     0x8C02
#define GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG                     0x8C03

で、この圧縮方式ですが、けっこう複雑です。
要するに、GPU内部でこのアルゴリズムでテクスチャ情報を変換して保持しているから、テクスチャイメージの書き換えには膨大な処理時間を持っていかれてしまう訳ですね。
なので、仮に自前のCPU演算で同じ圧縮演算をしたとしても、GPU任せの変換と比較して膨大な処理時間を食う訳です。


という訳で、ほぼチェックメイトです。
「ほぼ」というのは、まだ2つほど別のアイディアがあるという意味です。

【アイディア1:ベースロジックの改造】
テクスチャ変換処理を完全に別スレッド(非同期)にして、onDrawFrameのサイクルが呼ばれる時点で完成した前フレームのテクスチャを画面に出力する(毎回1フレームの遅延が発生するけど、かなり軽くなる筈)


【アイディア2:総転送量を減らす】
テクスチャを2枚(256x256と256x64にして、組み合わせて256x320にする。
この方式はかなり危険です。
・つなぎ目の境界がボンヤリ見えてしまう可能性がある。
・2回glCompressedTexImage2Dを呼び出す必要がある。





追記5


とりあえず、【アイディア2:総転送量を減らす】を実装してみました。
つなぎ目は割りと気になりません。
しかし、やはり、2回glCompressedTexImage2Dを呼び出すオーバーヘッドが思ったよりも大きい。
一応速くなりましたがね。


IdeaPadA1で、55~56fpsで安定していたものが57~58fpsで安定する程度に。。。
もはや、ギブアップ寸前。。。

やっぱり、OpenGL/ESは使い物にならない。。。

そもそも、ポリゴンのテクスチャとは、頻繁な書き換えを想定したものではないので。
OpenGL/ESで性能を出そうと思えば、パターンデータは完全に固定化する必要があるというのが、結論かな。
しかし、2Dゲームの場合、そんなんだと使い物になりません。。。
ピクセル単位での書き込みや、スプライトのマスキング表示などが必須なので。
それらはパターンデータの加工が必須。
テスクチャを書き換えずに利用する方式だと、パターンデータの加工はできない。
よって、チェックメイト・・・。


ちなみに、onFrameDrawのC側の関数の入口と出口にログ出力処理を入れてみたところ、
I/SHOT04  (16557): 2012/07/15 19:01:37.562 start
I/SHOT04  (16557): 2012/07/15 19:01:37.578 end (4ms)
I/SHOT04  (16557): 2012/07/15 19:01:37.579 start
I/SHOT04  (16557): 2012/07/15 19:01:37.595 end (16ms)
I/SHOT04  (16557): 2012/07/15 19:01:37.597 start
I/SHOT04  (16557): 2012/07/15 19:01:37.612 end (15ms)
I/SHOT04  (16557): 2012/07/15 19:01:37.613 start
I/SHOT04  (16557): 2012/07/15 19:01:37.629 end (16ms)
I/SHOT04  (16557): 2012/07/15 19:01:37.630 start
I/SHOT04  (16557): 2012/07/15 19:01:37.646 end (16ms)
I/SHOT04  (16557): 2012/07/15 19:01:37.647 start
I/SHOT04  (16557): 2012/07/15 19:01:37.662 end (15ms)
I/SHOT04  (16557): 2012/07/15 19:01:37.664 start
I/SHOT04  (16557): 2012/07/15 19:01:37.671 end (7ms)
I/SHOT04  (16557): 2012/07/15 19:01:37.686 start
I/SHOT04  (16557): 2012/07/15 19:01:37.694 end (8ms)
という具合。

絶望的な遅さです。
何よりバラツキが激しい。
やっぱり、PVRTCが相当なクセモノです。
完全なソフトウェアレンダリングの方が高速に処理できる気がしてきたので、そっちの方向で探ってみるか。。。



追記6


そもそも、今回の問題の発端になったCanvasがICS(Android4)で劇的に性能劣化するようになった原因のGoogle公式回答が見つかりました。
https://groups.google.com/forum/?fromgroups#!topic/android-developers/CK_jkHEQoOM


Google「すまんね、lockCanvasはAndroid3以降、ハードウェアアクセラレーションしないよ。

えっ?
それだけ?

質問している方は、代替策を聞いているのに、代替策に関しては無策ということはバグですね。
ICSからのバグならまだしも、Android3の頃からのバグ(仕様欠陥)ってことは、直す気ナシか?
Googleみたいなスカタンな連中にOSを作らせた結果がコレです。

完全に推測ですが、ICS開発中のGoogle内部でのやりとりは、大方
・電池持ちについて検討
・SurfaceView+CanvasのH/Wアクセラレーションを切れば電池持ちが良くなる
・ゲームが遅くなる可能性については黙認 (デグっても問題なしと判断)
といった感じですかね。

さて、どうしたものか。
Android 2.3専用にして作り続けるか、開発自体をドロップするかの二択?
厳しいなぁ。。。

とりあえず、やる気が大分削げました。
Windowsでゲーム本編の開発を進めることはできるから、作りながら待つべきか。

OpenGL化に苦戦中

とりあえず、SurfaceView + Canvasで描画していたVRAM転送処理を、OpenGL化。
VRAM情報(AndroidBitmap)はそのまま使える筈なので、骨組みを変更するだけ・・・・
・・・なんですがね。

Java部分というかAndroid固有部分の実装は、正直かなり面倒です。
とりあえず、本屋でOpenGL/ES関連の書籍を入手。
http://www.amazon.co.jp/%E5%88%9D%E3%82%81%E3%81%A6%E3%81%AEOpenGL-ES-%E5%B1%B1%E4%B8%8B-%E6%AD%A6%E5%BF%97/dp/4873114969

とりあえず、OpenGL/ESの1.0で実装してみました。
しかし、Android2.3の検証機で動かしてみたのですが、重い。。。
Android2.3で遅くなるのはアウト過ぎます。
私自身のスマホは4.0にアップしましたが、やはり、メインターゲットは2.3なので。

まぁ、まだかなり改善余地がありそうなので、頑張ってみます。
AndroidBitmapで作成したVRAM情報を、毎フレームtexSubImage2Dで転送しているのが無駄に思えてしょうがない。

OpenGL側で管理しているテクスチャ原本のメモリポインタを取得できれば、一気に解決なんですが。

2012年7月13日金曜日

Android4大幅性能劣化

Android4で、自作アプリの処理性能が大幅に劣化する問題が発生。
たぶん、CanvasがGPUを介さずに動いているのかも。

私のアプリは、Cで仮想ビデオメモリ(240x320pixel )の情報をAndroidBitmapとして作成し、それをSurfaceViewでCanvasを使って、画面サイズにあわせて拡大描画するという処理方式。
スプライト描画などは、Cで実装している完全に自前のロジックです。
その部分では問題が出る筈が無いので、問題の原因はほぼ100%、SurfaceView or Canvasのどちらか。

実は、Canvasが相当重い処理であろうことは想定済みです。
ただ、手元にあるシングルコアの陳腐なAndroid2.3機(Lenovo製)でもそのCanvas方式で60fpsを出せていたから、「問題ないのかな?」と、思っていたのですが、どうやらAndroid4だと、問題が出てしまったという訳です。

もちろん、Android4がバグっているだけという可能性も否定できません。
しかし、改善の余地はあるから、この部分の処理方式を大幅に変えてみようと思います。
とりあえず、OpenGLでテクスチャを使う方式に変更してみようか・・・。
OpenGLは苦手ですが....


しかし、こんなショボイ性能デグを起こすあたり、流石、Googleさん。
やはり、Java脳の人々は、性能に無頓着すぎる。

2012年7月12日木曜日

Android4へアップデート

電話機(Razr)のOSをAndroid4.0.4へアップデートしました。
ちなみに、自作アプリの動作保障バージョンは、2.3.3以降です。
デバッグ専用のタブレットは2.3系なので、検証環境は問題ありません。

アップデートの主目的は、自作アプリの動作検証のため。
まぁ、私のアプリの場合、設計上OSのバージョン固有の問題は出る可能性が極めて低いので、早急にやる必要は無いと思っていましたが、それでも一応、実機で動作を確認した方が良いのは確かな筈。
あと、ホーム画面のフォルダ表示とかが便利そうだったというのもあります。
ザックリと使ってみた感じでは、

ダメなところ

(1) 描画パフォーマンスが大幅に劣化
描画処理の仕様変更により、相当パフォーマンスが劣化してます。
ゲームの動作がモッサリした感じになります。
あと、動画とかも2.3の頃よりも粗い感じで表示される気がしないでもないです。
(これは、気のせいかも)

微妙なところ

(1) 標準ブラウザのお気に入りがワンタッチで表示できなくなった?
(2) ホーム画面でWi-FiのON/OFFができなくなった?
(3) ロック画面で最初に鍵ボタンを押してからパターンやpin入力になるのが意味不明
(4) アプリ画面やホーム画面のスライド時に誤反応みたいな感じになることがある

(5) フラッシュ実質非対応

フラッシュは、4.0からプリインストールから外れるけど、インストールは可能でサポートもされる・・・
・・・とのことですが、現状、真っ当に動きません。
たぶん、Adobeもやる気は全然無いはずなので、今後の改善も期待できないと思います。
なお、Android4.0からはフラッシュではニコニコ動画とかを見ることが出来なくなりました。
ただし、ニコニコ動画については、非公式のニコニコブラウザを入れて、Chromeからそれを起動させれば、Android2.3の時とほぼ同じ感じで見れるので、問題無いっぽいです。


良いところ

(1) ホーム画面にフォルダを作れる

結構便利です。
使用するアプリの種類が多い人にとっては嬉しいと思います。

(2) 電池持ちが若干良くなったかも

描画処理の仕様変更によるプラスの影響かも。


(3) Chromeが使える


パソコンのブックマークと共有できます。
あと、個人的には標準のブラウザよりも使い易い。



概ね想定通りです。
微妙なところは、使っていくうちに慣れるかも。

ただ、総合していえることは、「通常のユーザは現時点では2.3にしておいた方が良い」です。
総合的に見て、Android2.3からAndroid4.0にすると損するケースの方が多いかもしれません。
どちらかといえば、デベロッパに叩いて貰うためのリリースだと思います。
ゲームや動画は見ない人にとっては、電池持ちが良くなったメリットは大きいので、アップしても良いかも。

なお、フラッシュが使えなくなったことで、当初色々とデメリットがありそうだと思ってましたが、そうでもなさそうです。ニコニコ動画については、今のところChrome+非公式ツールの方が標準ブラウザ+フラッシュの頃よりも見易くなった気がします。

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

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