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+非公式ツールの方が標準ブラウザ+フラッシュの頃よりも見易くなった気がします。

2012年7月9日月曜日

3面進捗が・・・

結局、NHKがらみの対応(参照)で、殆ど開発が着手できず・・・
実際にやったことといえば、テレビの処分と解約の手続きだけで昨日(7/7)全て完了していたのですが、主に法律関係の調査と理論武装などに若干の時間が掛かりました。法律は難しいですねぇ。でも、法律を勉強するのは楽しいので、あまり入れ込みすぎないように注意する必要があります。(私の場合、下手をするとネタで司法試験予備試験でも受けてみようかという発想になりかねない)

本当は、今日、3面が完成する予定だったんですが、当然ながら完成せず。
9月公開という線については、若干暗雲が立ち込めてきましたが、まだ焦るような時期じゃない。
平日が割りと暇になる頃合なので、平日にチャージできそうですし。

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

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