RenderscriptはHoneycombで導入された新しい機能です。 また、以前にRenderscriptがAndroid開発者によって既に使用されていたことも知られています(たとえば、2.1(Eclair)の組み込みライブ壁紙が書かれていました)。 いずれにせよ、APIへのフルアクセスはハニカムでのみ開かれました。 開発者ブログの最初の紹介記事(
original |
translation )は、Renderscriptアーキテクチャのより詳細な説明とその使用例とともに、まもなく2回目になると約束しました。 実際には、カットの下で両方。
以下、Android開発チームのエンジニアであるR. Jason Samsを代表して。Renderscriptの紹介で、このテクノロジーの概要を説明しました。 この投稿では、「コンピューティング」に集中します。 Renderscriptの用語では、「計算」とは、データ処理のDalvikコードからRenderscriptコードへの特定のオフロードを意味し、同じプロセッサまたは他のプロセッサ(複数)で実行できます。
Renderscriptデザインの目標
Renderscriptには3つの主要な目標があり、重要度の高いものから低いものの順にそれらを検討してください。
移植性:アプリケーションコードは、ハードウェアの充填量が大幅に異なる場合でも、すべてのデバイスで実行する必要があります。 現在、ARMにはいくつかのバージョンがあります。VFPサポートの有無にかかわらず、NEONサポートの有無にかかわらず、さまざまなレジスタカウンタがあります。 ARMに加えて、x86に類似した他のCPUアーキテクチャ、異なるGPU、さらにさまざまなDSP(デジタルシグナルプロセッサ)があります。
パフォーマンス: 2番目の目標は、可搬性の制限を伴いながら、可能な最高レベルのパフォーマンスを達成することです。 Renderscriptについては、既にインストールされているソリューションに比べてはるかに高いパフォーマンスを達成しようとしました。
ユーザビリティ: 3番目の目標は、開発を可能な限り簡素化することでした。 「接着剤コード」の出現や他の不必要な開発者のロードを回避するために、いくつかの開発ステップを可能な限り自動化しました。
これら3つの目標は設計を制限し、いくつかの妥協をもたらします。 これらは、RenderscriptをDalvikやNDKなどの既存のソリューションから分離するトレードオフです。 これらは、さまざまなタスクを実行するのに役立つさまざまなツールと見なされる必要があります。
設計段階での主な決定
最初に決定する必要があるのは、開発言語です。 使用する言語を選択する場合、ほとんど無制限のオプションセットがあります。 シェーダーをプログラミングするための言語として、CおよびC ++が検討されました。 その後、シーンなどのグラフィカルアプリケーションのデータ構造を操作する必要があるため、シェーダー言語を放棄する必要がありました。 ポインターの欠如と再帰は、使いやすさの制限と見なされていました。 一方、C ++を使用することは望ましいことですが、移植性の制限に直面する必要があります。 高度なC ++機能は、プロセッサ以外のハードウェアで実行するのが困難です。 その結果、C99標準をRenderscriptに基づいて決定しました。これは、残りのソリューションで同じパフォーマンスを提供し、開発者がそれをよく理解しており、さまざまな異なる機器で実行するために問題が発生しないためです。
2番目のトレードオフは、Renderscriptワークフロー自体です。 特に、ソースコードをマシンコードにコンパイルする方法に焦点を当てました。 開発中にさまざまなオプションを検討した結果、2つのソリューションを実装しました。 古いバージョン(EclairとGingerbreadの間)は、Cソースコードをネイティブコードに完全にコンパイルしました。 これにより、アプリケーションにその場でコード生成などの機能が提供されましたが、ユーザビリティの問題に変わりました。 アプリケーションをコンパイル、インストール、実行してから構文エラーを見つけるのは非常に不便でした。 また、弱いCPUは、静的分析の可能性と実行可能な最適化を奪われました。
次に、LLVM(低レベルの仮想マシン、スクリプトをプラットフォームに依存しないバイトコードにコンパイルする)に切り替え、ホスト(スクリプトが実行されているデバイス)でスクリプトをコンパイルおよび分析するモデルに移動し、clangの修正バージョン(知っている-これはLLVMコンパイラのフロントエンドです)。 この段階では、いくつかの高度な最適化を実行し、その後LLVMバイトコードを選択します。 中間バイトコードからマシンへの変換はホストで発生します(デバイス固有の最適化が追加されます)。
設計における最後の主要なトレードオフは、スレッドを開始することでした。 これは、パフォーマンスと移植性のトレードオフです。 十分な知識があれば、既存のコンピューティングソリューションにより、開発者は特定のハードウェアプラットフォーム向けにアプリケーションを構成できますが、他のプラットフォームは不利になります。 時間とリソースに制限がない場合、開発者はハードウェアの任意の組み合わせに合わせてアプリケーションをカスタマイズできます。 特定のデバイスセットのテストとチューニングは悪くありませんが、まだリリースされておらず、開発者から入手できない機器のアプリケーションをチューニングすることはできません。 よりポータブルなソリューションは、実行時分析であり、ピーク時のパフォーマンスにより平均パフォーマンスが向上します。 移植性が私たちの最大の目標であるため、このソリューションを選択しました。
実行時にスレッドの開始を制御するという選択の2番目の効果は、スクリプトを実行する場所に関する動的な決定でした。 たとえば、一部のコンピューティングハードウェアはポインターと再帰をサポートしていますが、他のハードウェアはサポートしていません。 また、これらのことを禁止し、開発者にAPIの最も一般的な分母の類似性を与えることもできましたが、代わりにランタイム分析を選択しました。 これにより、開発者は、サポートされているハードウェアのすべての機能を使用できますが、同時に信頼できる完全に機能するCPUもあります。 最終的に、開発者は優れたアプリケーションの作成に集中でき、ハードウェアメーカーはフル機能の強力なハードウェアに取り組むことができます。 新しいパフォーマンス向上の機会が現れるとすぐに、アプリケーションはコードを変更することなくそれを使用します。
ユーザビリティは、設計時の主な目標でした。 既存のほとんどのコンピューティングおよびグラフィックスソリューションでは、高性能コードをアプリケーションコードにリンクするための「固定」ロジックが必要です。 このようなコードはエラーが発生しやすく、書くのに問題があります。 ホストでRenderscriptによって実行される静的分析は、この問題の解決に役立ちます。 スクリプトの各ユーザーは、独自の「互いに結びつく」Dalvikクラスを作成します。 接着クラスとそのアクセッサの名前は、スクリプトから派生しています。 これにより、Dalvikのスクリプトの使用が簡単になります。
例:アプリケーションレベル
上記のすべてのトレードオフを考えると、コンピューティングアプリケーションの簡単な例はどのように見えるべきでしょうか? この基本的な例では、通常のandroid.graphics.Bitmapオブジェクトを取得し、別のビットマップにコピーするスクリプトを実行すると同時に、モノクロに変換します。 スクリプト自体を見る前に、スクリプトを呼び出すアプリケーションコードを見てみましょう。 HelloCompute SDKの例:
private Bitmap mBitmapIn; private Bitmap mBitmapOut; private RenderScript mRS; private Allocation mInAllocation; private Allocation mOutAllocation; private ScriptC_mono mScript; private void createScript() { mRS = RenderScript.create(this); mInAllocation = Allocation.createFromBitmap(mRS, mBitmapIn, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); mOutAllocation = Allocation.createTyped(mRS, mInAllocation.getType()); mScript = new ScriptC_mono(mRS, getResources(), R.raw.mono); mScript.set_gIn(mInAllocation); mScript.set_gOut(mOutAllocation); mScript.set_gScript(mScript); mScript.invoke_filter(); mOutAllocation.copyTo(mBitmapOut);
このメソッドは、2つのビットマップが既に作成されており、同じサイズと形式を持っていることを前提としています。 Renderscriptアプリケーションが最初に必要とするものはコンテキストオブジェクトです。 これは、他のRenderscriptオブジェクトを作成および管理するために使用される中心的なオブジェクトです。 コードの最初の行はmRSオブジェクトを作成します。このオブジェクトは、アプリケーションがそれまたはそれを使用して作成されたオブジェクトを使用する間、存続する必要があります。
次の2つの方法により、ビットマップから計算用の割り当てが作成されます。 Renderscriptには独自のメモリアロケータがあります。これは、メモリが複数のプロセッサ間で共有され、複数のメモリスペースが存在する可能性が高いためです。 アロケータが作成されるとき、システムが目的のタスクに必要なメモリのタイプを決定できるように、潜在的な用途に番号を付ける必要があります。
最初のcreateFromBitmap()メソッドは、配置を作成し、ビットマップのコンテンツをその中にコピーします。 ロケーションは、Renderscriptが使用するメモリの基本単位です。 createTyped()で作成された2番目のプレースメントは、最初のプレースメントと同じプレースメントを生成します。 さらに、この構造の定義は、最初からのgetType()要求によって返されます。 Renderscriptタイプはレイアウト構造を定義します。 この場合、タイプは、入力ビットマップの高さ、幅、および形式を使用して生成されます。
次の行は「mono.rs」というスクリプトをロードし、R.raw.monoを使用して取得します。 スクリプト自体は、アプリケーションパッケージ(APK内)に生のリソースとして保存されます。 生成された「接着」クラスの名前ScriptC_monoに注目してください。
次の行は、「gluing」クラスの生成されたメソッドを使用してスクリプトプロパティを設定します。
これですべての準備が整いましたが、実際にはinvoke_filter()メソッドがいくつかの仕事をしてくれました。 一番下の行は、「mono.rs」スクリプト自体のfilter()メソッドの呼び出しです。 メソッドにパラメーターがある場合は、ここに渡す必要があります。 呼び出しは非同期であるため、値を返すことは禁止されています。
最後の行は、スクリプトの結果をビットマップにコピーします。 スクリプトの実行が完了したことを確認するために、いくつかの同期コードが組み込まれています。
例:スクリプト
上記のコードによって呼び出される「mono.rs」スクリプト自体は次のとおりです。
#pragma version(1) #pragma rs java_package_name(com.android.example.hellocompute) rs_allocation gIn; rs_allocation gOut; rs_script gScript; const static float3 gMonoMult = {0.299f, 0.587f, 0.114f}; void root(const uchar4 *v_in, uchar4 *v_out, const void *usrData, uint32_t x, uint32_t y) { float4 f4 = rsUnpackColor8888(*v_in); float3 mono = dot(f4.rgb, gMonoMult); *v_out = rsPackColorTo8888(mono); } void filter() { rsForEach(gScript, gIn, gOut, 0); }
スクリプトの最初の行は、コンパイラにどのリビジョン用に書かれているかを単に伝えます。 2行目は、生成された反射コードとアプリケーションパッケージの関連付けを制御します。
次は、制御コードで作成された変数に対応する3つのグローバル変数です。 4番目のグローバル変数は静的であるため、反射的ではありません。 スクリプトコードはコントロールと同期しているため、定数は常に静的とする方が適切です。
root()メソッドは、Renderscript専用です。 概念的には、Cのmain()メソッドに似ています。実行時にスクリプトが呼び出されると、この関数がエントリポイントになります。 この場合、パラメーターはプレースメントのピクセルです。 共通のユーザーポインターには、この呼び出しによって処理されている場所自体のアドレスも提供されます。 この例では、これらのパラメーターは無視されます。
ルート()メソッドの3行のコードは、RGBA_8888のピクセルをfloat4のベクトルに解凍します。 2行目は、組み込み数学関数dotを使用します。これは、モノクロ定数を入力ピクセルとスカラー積を計算して、モノクロ画像を取得します。 ドットは通常のフロートを返すことに注意してください。フロートはfloat3に割り当てることができ、各コンポーネントx、y、zに値を単純にコピーします。 最後に、組み込みツールを使用して、値を通常の32ビットピクセルにパックします。 これは、RGB(float3)とRGBA(float4)の形式のデータを受け入れるrsPackColorTo8888の異なるバージョンがあるため、メソッドのオーバーロードの例でもあります。
filter()メソッドは、変換を実行するために制御コードから呼び出されます。 各配置要素に対して計算を実行するだけです。 最初のパラメーターは、スクリプトが起動されることを意味します。つまり、各配置要素に対してroot()メソッドが呼び出されます。 2番目と3番目のパラメーターは、入力および出力データの配置です。 最後のパラメーターは、ユーザーデータをroot()メソッドに渡すことを目的としています。
forEachは、デバイス上にある場合、複数のプロセッサで実行されます。 将来、forEachは、あるプロセッサから別のプロセッサに制御を移動できる移行ポイントを提供できるようになります。 この例では、将来フィルター()がCPUで実行され、ルート()がGPUまたはDSPで実行されると想定するのが合理的です。
これにより、Renderscriptの設計を詳しく見て、簡単な例で、その仕組みをよく理解できるようになることを願っています。
翻訳者から:このテクノロジーの使用を何らかの形で始めた開発者からのコメントを歓迎します。