6

TextureViewAndroidでお絵かきアプリを作ろうとしています。最大 4096x4096 ピクセルの描画面をサポートしたいと考えています。これは、適切なクアッド コア CPU と 2GB メモリを搭載した Google Nexus 7 2013 である最小ターゲット デバイス (テストに使用) には妥当と思われます。

私の要件の 1 つは、ビューをズームイン、ズームアウト、およびパンできるビュー内に配置する必要があることです。これはすべて、私が作成したカスタム コードです (iOS の UIScrollView を考えてください)。

私はTextureViewOnDraw で通常のビュー (ではない) を使用しようとしましたが、パフォーマンスはまったくひどいものでした - 1 秒あたり 1 フレーム未満でした。これはInvalidate(rect)、変更された rect だけで呼び出しても発生しました。ビューのハードウェア アクセラレーションをオフにしようとしましたが、4096x4096 はソフトウェアには大きすぎるため、何もレンダリングされませんでした。

その後、使用してみTextureViewましたが、パフォーマンスは少し良くなりました.毎秒約5〜10フレームです(それでもひどいですが、より良いです). ユーザーはビットマップに描画し、後でバックグラウンド スレッドを使用してテクスチャに描画します。私は Xamarin を使用していますが、コードが Java の人々にとって意味のあるものであることを願っています。

private void RunUpdateThread()
{
    try
    {
        TimeSpan sleep = TimeSpan.FromSeconds(1.0f / 60.0f);
        while (true)
        {
            lock (dirtyRect)
            {
                if (dirtyRect.Width() > 0 && dirtyRect.Height() > 0)
                {
                    Canvas c = LockCanvas(dirtyRect);
                    if (c != null)
                    {
                        c.DrawBitmap(bitmap, dirtyRect, dirtyRect, bufferPaint);
                        dirtyRect.Set(0, 0, 0, 0);
                        UnlockCanvasAndPost(c);
                    }
                }
            }
            Thread.Sleep(sleep);
        }
    }
    catch
    {
    }
}

rect の代わりに null を渡すように lockCanvas を変更すると、パフォーマンスは 60 fps で優れていますが、コンテンツがちらついてTextureView破損し、残念です。単純に OpenGL フレーム バッファを使用するか、その下にテクスチャをレンダリングするか、少なくともコンテンツを保持するオプションがあると考えていました。

Android の生の OpenGL ですべてを実行する以外に、ドローコール間で保持されるサーフェス上で高性能の描画とペイントを行うオプションはありますか?

4

2 に答える 2

21

まず、内部で何が起こっているかを理解したい場合は、Android Graphics Architecture ドキュメントを読む必要があります。長いですが、「なぜ」を本気で理解したいならここから始めましょう。

TextureViewについて

TextureView は次のように機能します。これには、生産者と消費者の関係を持つバッファーのキューである Surface があります。ソフトウェア (キャンバス) レンダリングを使用している場合は、Surface をロックしてバッファを提供します。あなたはそれを描きます。次に、バッファを消費者に送信する Surface のロックを解除します。この場合のコンシューマーは同じプロセスにあり、SurfaceTexture または (内部的にはより適切に) GLConsumer と呼ばれます。バッファを OpenGL ES テクスチャに変換し、ビューにレンダリングします。

ハードウェア アクセラレーションをオフにすると、GLES が無効になり、TextureView は何もできなくなります。これが、ハードウェア アクセラレーションをオフにしても何も得られない理由です。 ドキュメントは非常に具体的です。「TextureView はハードウェア アクセラレーション ウィンドウでのみ使用できます。ソフトウェアでレンダリングすると、TextureView は何も描画しません。

ダーティ rect を指定すると、レンダリングが完了した後、ソフトウェア レンダラーは前のコンテンツをフレームに memcpy します。クリップ矩形を設定するとは思わないので、drawColor() を呼び出すと、画面全体が塗りつぶされ、それらのピクセルが上書きされます。現在クリップ四角形を設定していない場合は、そうすることでパフォーマンスが向上することがあります。(ただし、コードは確認していません。)

ダーティ rect はインアウト パラメータです。を呼び出すときに必要な四角形を渡すlockCanvas()と、システムは呼び出しが戻る前にそれを変更できます。(実際には、これを行う唯一の理由は、前のフレームがないか、サーフェスのサイズが変更された場合です。その場合、画面全体をカバーするように拡大されます。これは、より直接的な方法でより適切に処理されたと思います。 「あなたの四角形を拒否します」信号。)返された四角形内のすべてのピクセルを更新する必要があります。サンプルで行おうとしているように見える四角形を変更することは許可されていませんlockCanvas()-成功した後のダーティ四角形にあるものは何でも、描画する必要があります。

ちらつきの原因は、汚い四角形の取り扱いの誤りだと思います。悲しいことに、arg の動作はSurface クラス自体lockCanvas() dirtyRectでのみ文書化されているため、これは犯しやすい間違いです。

表面とバッファリング

すべてのサーフェスは、ダブルまたはトリプル バッファリングされています。これを回避する方法はありません。読み取りと書き込みを同時に行うことはできず、テアリングも発生しません。必要に応じて変更およびプッシュできる単一のバッファーが必要な場合は、そのバッファーをロック、コピー、およびロック解除する必要があり、構成パイプラインでストールが発生します。最高のスループットとレイテンシーを得るには、バッファーをフリッピングする方が優れています。

ロック-コピー-ロック解除の動作が必要な場合は、自分で書くことができます (またはそれを行うライブラリを見つけることができます)。システムがそれを行う場合と同じくらい効率的です (あなたが得意であると仮定すると)ブリット ループ)。オフスクリーン キャンバスに描画してビットマップをブリットするか、OpenGL ES FBO に描画してバッファをブリットします。後者の例は、Grafika の「record GL app」アクティビティで見つけることができます。このアクティビティには、画面外で 1 回レンダリングしてから 2 回ブリットするモードがあります (1 回は表示用、もう 1 回はビデオ録画用)。

スピードアップなど

Android でピクセルを描画するには、キャンバスを使用する方法と OpenGL を使用する方法の 2 つの基本的な方法があります。サーフェスまたはビットマップへの Canvas のレンダリングは常にソフトウェアで行われますが、OpenGL のレンダリングは GPU で行われます。唯一の例外は、カスタム Viewにレンダリングする場合、ハードウェア アクセラレーションの使用を選択できることですが、これは SurfaceView または TextureView の Surface にレンダリングする場合には適用されません。

描画アプリまたは描画アプリは、描画コマンドを記憶するか、ピクセルをバッファーに投げてそれをメモリとして使用することができます。前者はより深い「元に戻す」ことができ、後者ははるかに単純で、レンダリングするものの量が増えるにつれてパフォーマンスが向上します。後者をやりたいように聞こえるので、画面外からのブリットは理にかなっています。

ほとんどのモバイル デバイスでは、GLES テクスチャに対して 4096x4096 以下のハードウェア制限があるため、単一のテクスチャをそれより大きいものに使用することはできません。サイズ制限値 (GL_MAX_TEXTURE_SIZE) を照会できますが、必要なだけ大きい内部バッファーを使用して、画面に収まる部分だけをレンダリングする方がよい場合があります。Skia (Canvas) の制限がオフハンドである理由はわかりませんが、より大きなビットマップを作成できると思います。

必要に応じて、中間の GLES テクスチャ ステップを回避するため、SurfaceView が TextureView よりも望ましい場合があります。Surface に描画したものはすべて、システム コンポジター (SurfaceFlinger) に直接送られます。このアプローチの欠点は、Surface のコンシューマーがインプロセスではないため、ビュー システムが出力を処理する機会がないため、Surface が独立したレイヤーになることです。(描画プログラムの場合、これは有益です。描画される画像は 1 つのレイヤーにあり、UI はその上の別のレイヤーにあります。)

FWIW、私はコードを見ていませんが、Dan Sandler のMarkersアプリは一見の価値があるかもしれません (ソースコードはこちら)。

更新:破損はバグとして特定され、 'L' で修正されました。

于 2015-06-26T15:40:44.770 に答える
1

更新私はTextureViewを捨て、今ではglTexSubImage2Dを呼び出してレンダリングテクスチャの変更された部分を更新するOpenGLビューを使用しています。

OLD ANSWER 最終的に TextureView を 4x4 グリッドに並べて表示しました。各フレームの汚れた四角形に応じて、適切な TextureView ビューを更新します。Invalidate を呼び出したフレームで更新されていないビュー。

Moto G 電話などの一部のデバイスには、1 フレームのダブル バッファリングが破損するという問題があります。親ビューでonLayoutが呼び出されたときにlockCanvasを2回呼び出すことで修正できます。

private void InvalidateRect(int l, int t, int r, int b)
{
    dirtyRect.Set(l, t, r, b);

    foreach (DrawSubView v in drawViews)
    {
        if (Rect.Intersects(dirtyRect, v.Clip))
        {
            v.RedrawAsync();
        }
        else
        {
            v.Invalidate();
        }
    }

    Invalidate();
}

protected override void OnLayout(bool changed, int l, int t, int r, int b)
{
    for (int i = 0; i < ChildCount; i++)
    {
        View v = GetChildAt(i);
        v.Layout(v.Left, v.Top, v.Right, v.Bottom);
        DrawSubView sv = v as DrawSubView;
        if (sv != null)
        {
            sv.RedrawAsync();

            // why are we re-drawing you ask? because of double buffering bugs in Android :)
            PostDelayed(() => sv.RedrawAsync(), 50);
        }
    }
}
于 2015-06-25T20:40:35.683 に答える