RenderScript は、Android で演算負荷の高いタスクを優れたパフォーマンスを維持しながら実行するためのフレームワークです。連続したワークロードにもメリットは考えられますが、RenderScript は主にデータ並列計算に使用することを目的としています。RenderScript ランタイムは、マルチコアの CPU や GPU などのデバイスで使用できるプロセッサ間で作業を並列化します。これにより、作業のスケジュール設定ではなく、アルゴリズムの表現に専念できます。RenderScript は、画像処理、計算写真学、コンピュータ ビジョンを実行するアプリケーションに特に有用です。
RenderScript の使用を開始するには、次の 2 つの主要な概念を理解する必要があります。
- 言語自体は、ハイパフォーマンス コンピューティング コードを記述するための C99 派生言語です。RenderScript カーネルを記述するでは、この言語でコンピューティング カーネルを作成する方法を説明します。
- Control API は、RenderScript リソースのライフタイムの管理とカーネルの実行の制御に使用されます。Java、Android NDK の C++、C99 由来のカーネル言語自体という、3 つの異なる言語で使用できます。Java コードから RenderScript を使用するとシングルソースの RenderScript では、それぞれこのうち 1 つ目と 3 つ目を説明しています。
RenderScript カーネルを記述する
RenderScript カーネルは通常、<project_root>/src/rs
ディレクトリの .rs
ファイルに格納されます。各 .rs
ファイルはスクリプトと呼ばれます。どのスクリプトにも、独自のカーネル、関数、変数のセットが含まれています。スクリプトには次のものを含めることができます。
- スクリプトで使用される RenderScript カーネル言語のバージョンを宣言するプラグマ宣言(
#pragma version(1)
)。現時点では、有効な値は 1 のみです。 - スクリプトから反映された Java クラスのパッケージ名を宣言するプラグマ宣言(
#pragma rs java_package_name(com.example.app)
)。.rs
ファイルは、ライブラリ プロジェクトではなく、アプリ パッケージに含まれる必要がある点に留意してください。 - 0 個以上の呼び出し可能な関数。呼び出し可能な関数は、任意の引数を指定して Java コードから呼び出すことができる、単一スレッドの RenderScript 関数です。これらは多くの場合、初期設定や大規模な処理パイプライン内の連続計算に役立ちます。
0 個以上のスクリプト グローバル。スクリプト グローバルは、C のグローバル変数に似ています。スクリプト グローバルには Java コードからアクセスでき、RenderScript カーネルにパラメータを渡すためによく使用されます。スクリプト グローバルの詳細については、こちらで説明しています。
0 個以上のコンピューティング カーネル。コンピューティング カーネルは、RenderScript ランタイムをデータのコレクション間で並列に実行できるようにする関数または関数のコレクションです。コンピューティング カーネルには、マッピング カーネル(別名 foreach カーネル)とリダクション カーネルの 2 種類があります。
マッピング カーネルは、同じディメンションの
Allocations
のコレクションで動作する並列関数です。デフォルトでは、これらのディメンション内の座標ごとに 1 回実行されます。これは通常、入力Allocations
のコレクションから出力Allocation
への変換でElement
を一度に 1 つずつ実行するために使用されます(ただし、これに限りません)。以下に、シンプルなマッピング カーネルの例を示します。
uchar4 RS_KERNEL invert(uchar4 in, uint32_t x, uint32_t y) { uchar4 out = in; out.r = 255 - in.r; out.g = 255 - in.g; out.b = 255 - in.b; return out; }
ほとんどの点で、これは標準の C 関数と同じです。関数のプロトタイプに適用される
RS_KERNEL
プロパティは、関数が呼び出し可能な関数ではなく RenderScript マッピング カーネルであることを指定します。in
引数は、カーネルの起動に渡される入力Allocation
に基づいて自動的に入力されます。引数x
とy
については、後ほど説明します。カーネルから返された値は、出力Allocation
の適切な位置に自動的に書き込まれます。デフォルトでは、このカーネルは入力Allocation
の全体にわたって実行されます。その際、Allocation
の各Element
に対してカーネル関数が 1 回実行されます。マッピング カーネルは、1 つ以上の入力
Allocations
、1 つの出力Allocation
、またはその両方を持つ場合があります。RenderScript ランタイムは、すべての入力と出力の割り当てが同じディメンションで、入出力割り当てのElement
型がカーネルのプロトタイプと一致することを確認します。いずれかの確認が失敗すると、RenderScript は例外をスローします。注: Android 6.0(API レベル 23)より前では、マッピング カーネルが複数の入力
Allocation
を持たない場合があります。カーネルに割り当てられているよりも多くの入力または出力の
Allocations
が必要な場合は、それらのオブジェクトをrs_allocation
スクリプト グローバルにバインドして、カーネルからアクセスするか、rsGetElementAt_type()
またはrsSetElementAt_type()
を介して呼び出し可能な関数からアクセスする必要があります。注:
RS_KERNEL
は、便宜上 RenderScript によって自動的に定義されるマクロです。#define RS_KERNEL __attribute__((kernel))
リダクション カーネルは、同じディメンションの入力
Allocations
のコレクションで動作する関数のファミリーです。デフォルトでは、アキュムレータ関数がディメンションの座標ごとに 1 回実行されます。通常は、入力Allocations
のコレクションを 1 つの値に「減らす」ために使用されます(ただし、これに限りません)。以下に、入力の
Elements
を合計するシンプルなリダクション カーネルの例を示します。#pragma rs reduce(addint) accumulator(addintAccum) static void addintAccum(int *accum, int val) { *accum += val; }
リダクション カーネルは、ユーザーが記述した 1 つ以上の関数で構成されます。
#pragma rs reduce
は、名前(この例ではaddint
)とカーネルを構成する関数(この例ではaccumulator
関数であるaddintAccum
)の名前と役割を指定して、カーネルを定義するために使用されます。このような関数は、すべてstatic
であることが必要です。リダクション カーネルには、常にaccumulator
関数が必要です。カーネルに必要な動作に応じて、他の関数を使用することもできます。リダクション カーネル アキュムレータ関数は、
void
を返し、少なくとも 2 つの引数を持つ必要があります。最初の引数(この例ではaccum
)は、アキュムレータ データアイテムを指すポインタであり、2 番目の引数(この例ではval
)は、カーネルの起動に渡された入力Allocation
に基づいて自動的に入力されます。アキュムレータ データアイテムは、RenderScript ランタイムによって作成されます。デフォルトではゼロに初期化されます。デフォルトでは、このカーネルは入力Allocation
全体にわたって実行され、Allocation
のElement
ごとにアキュムレータ関数が 1 回実行されます。デフォルトでは、アキュムレータ データアイテムの最終値はリダクションの結果として処理され、Java に返されます。RenderScript ランタイムは、入力割り当てのElement
型がアキュムレータ関数のプロトタイプと一致することを確認します。一致しない場合、RenderScript は例外をスローします。リダクション カーネルは 1 つ以上の入力
Allocations
を持ちますが、出力Allocations
は持ちません。リダクション カーネルの詳細については、こちらをご覧ください。
リダクション カーネルは、Android 7.0(API レベル 24)以降でサポートされています。
マッピング カーネル関数またはリダクション カーネル アキュムレータ関数は、特別な引数
x
、y
、z
を使用して現在の実行の座標にアクセスできます。これらの引数は、int
型またはuint32_t
型であることが必要です。これらの引数は省略可能です。マッピング カーネル関数またはリダクション カーネル アキュムレータ関数は、rs_kernel_context 型のオプションの特別な引数
context
を取ることもできます。rsGetDimX など、現在の実行の特定プロパティを照会するために使用されるランタイム API ファミリで必要です。context
引数は、Android 6.0(API レベル 23)以降で使用できます。- オプションの
init()
関数です。init()
関数は、スクリプトが最初にインスタンス化されたときに RenderScript が実行する、呼び出し可能な関数の特別な型です。これにより、スクリプトの作成時に一部の計算が自動的に行われるようにできます。 - ゼロ以上の静的なスクリプト グローバルと関数。静的なスクリプト グローバルは、Java コードからはアクセスできない点を除いてスクリプト グローバルと同等です。静的関数は、スクリプト内の任意のカーネル関数または呼び出し可能な関数から呼び出せる標準の C 関数ですが、Java API には公開されません。スクリプト グローバルまたは関数に Java コードからアクセスする必要がない場合は、宣言された
static
にすることを強くおすすめします。
浮動小数点精度を設定する
浮動小数点精度の必要なレベルはスクリプトで制御できます。これは、完全な IEEE 754-2008 標準(デフォルトで使用)が不要な場合に便利です。次のプラグマは、異なるレベルの浮動小数点精度を設定できます。
#pragma rs_fp_full
(何も指定しない場合のデフォルト): IEEE 754-2008 標準で規定されている浮動小数点精度が必要なアプリの場合。#pragma rs_fp_relaxed
: 厳密な IEEE 754-2008 を遵守する必要がなく、低い精度でも運用できるアプリの場合。このモードでは、denorms と round-towards-zero に対して flush-to-zero が有効になります。#pragma rs_fp_imprecise
: 厳密な精度要件のないアプリの場合。このモードでは、下記に加えてrs_fp_relaxed
のすべてが有効になります。- 結果が -0.0 の演算では、代わりに +0.0 を返すことができます。
- INF と NAN での演算は未定義です。
ほとんどのアプリケーションでは、副作用を生じることなく rs_fp_relaxed
を使用できます。これは、SIMD CPU 命令など、ゆるやかな精度でのみ使用できる追加の最適化があるため、一部のアーキテクチャでは非常に有益な可能性があります。
Java から RenderScript API にアクセスする
RenderScript を使用する Android アプリを開発する場合は、次の 2 つの方法のいずれかにより Java から API にアクセスできます。
android.renderscript
- このクラス パッケージの API は、Android 3.0(API レベル 11)以降を搭載したデバイスで使用できます。android.support.v8.renderscript
- このパッケージの API は、サポート ライブラリを通じて、Android 2.3(API レベル 9)以降を搭載したデバイスで使用できます。
トレードオフは次のとおりです。
- サポート ライブラリ API を使用する場合、アプリケーションの RenderScript 部分は、使用する RenderScript 機能に関係なく、Android 2.3(API レベル 9)以降を搭載したデバイスに対応します。これにより、ネイティブ(
android.renderscript
)の API を使用する場合よりも多くのデバイスでアプリを動作させることができます。 - RenderScript の一部の機能は、サポート ライブラリ API からは利用できません。
- サポート ライブラリの API を使用すると、ネイティブ(
android.renderscript
)の API を使用した場合よりも APK のサイズが(おそらく大幅に)増加します。
RenderScript サポート ライブラリ API を使用する
サポート ライブラリ RenderScript API を使用するには、開発環境を構成してアクセスできるようにする必要があります。これらの API を使用するには、次の Android SDK ツールが必要です。
- Android SDK Tools リビジョン 22.2 以降
- Android SDK Build-Tools リビジョン 18.1.0 以降
Android SDK Build-Tools 24.0.0 から、Android 2.2(API レベル 8)はサポートされなくなった点に注意してください。
これらのツールのインストールされているバージョンについて、Android SDK Manager で確認と更新を行うことができます。
サポート ライブラリ RenderScript API を使用するには:
- 必要な Android SDK のバージョンがインストールされていることを確認します。
- Android ビルドプロセスの設定を更新して、RenderScript の設定を含めます。
- アプリケーション モジュールのアプリフォルダにある
build.gradle
ファイルを開きます。 - 次の RenderScript 設定をファイルに追加します。
Groovy
android { compileSdkVersion 33 defaultConfig { minSdkVersion 9 targetSdkVersion 19 renderscriptTargetApi 18 renderscriptSupportModeEnabled true } }
Kotlin
android { compileSdkVersion(33) defaultConfig { minSdkVersion(9) targetSdkVersion(19) renderscriptTargetApi = 18 renderscriptSupportModeEnabled = true } }
上記の設定は、Android のビルドプロセスで特定の動作を制御します。
renderscriptTargetApi
- 生成するバイトコードのバージョンを指定します。この値を、使用するすべての機能を提供できる最小の API レベルに設定し、renderscriptSupportModeEnabled
をtrue
に設定することをおすすめします。この設定の有効な値は、11 から最後にリリースされた API レベルまでの任意の整数値です。アプリ マニフェストで指定する最小の SDK バージョンが異なる値に設定されている場合、その値は無視され、ビルドファイルのターゲット値が最小の SDK バージョンの設定に使用されます。renderscriptSupportModeEnabled
- 実行中のデバイスがターゲット バージョンをサポートしていない場合、生成されたバイトコードを互換性のあるバージョンに戻すことを指定します。
- アプリケーション モジュールのアプリフォルダにある
- RenderScript を使用するアプリクラスで、サポート ライブラリ クラスのインポートを追加します。
Kotlin
import android.support.v8.renderscript.*
Java
import android.support.v8.renderscript.*;
Java または Kotlin のコードから RenderScript を使用する
Java または Kotlin のコードから RenderScript を使用することは、android.renderscript
または android.support.v8.renderscript
パッケージの API クラスに依存します。ほとんどのアプリは、同じ基本的な使用パターンに従います。
- RenderScript コンテキストを初期化します。
create(Context)
で作成されたRenderScript
コンテキストは、RenderScript を使用できることを保証し、後続するすべての RenderScript オブジェクトの全期間を制御するオブジェクトを提供します。コンテキスト作成は、ハードウェアごとにリソースを作成する可能性があるため、長時間実行の可能性を考慮することが必要です。そのため、可能な場合はアプリのクリティカル パスに含めないでください。通常、アプリは一度に 1 つの RenderScript コンテキストのみを持ちます。 - スクリプトに渡す
Allocation
を少なくとも 1 つ作成します。Allocation
は一定量のデータを格納するための RenderScript オブジェクトです。スクリプト内のカーネルは、Allocation
オブジェクトを入力および出力として扱います。スクリプト グローバルとしてバインドされている場合、Allocation
オブジェクトには、カーネル内でrsGetElementAt_type()
とrsSetElementAt_type()
を使用してアクセスできます。Allocation
オブジェクトによって、Java コードから RenderScript コードに配列を渡すことと、その逆のことが可能になります。Allocation
オブジェクトは通常、createTyped()
またはcreateFromBitmap()
を使用して作成されます。 - 必要なスクリプトをすべて作成します。RenderScript を使用する際、次の 2 種類のスクリプトを使用できます。
- ScriptC: 上記の RenderScript カーネルを記述するで説明したユーザー定義のスクリプトです。各スクリプトには、Java コードから簡単にアクセスするための RenderScript コンパイラによって反映された Java クラスがあります。このクラスは
ScriptC_filename
という名前になります。たとえば、上記のマッピング カーネルがinvert.rs
にあり、RenderScript コンテキストがすでにmRenderScript
にある場合、スクリプトをインスタンス化する Java コードまたは Kotlin コードは次のようになります。Kotlin
val invert = ScriptC_invert(renderScript)
Java
ScriptC_invert invert = new ScriptC_invert(renderScript);
- ScriptIntrinsic: Gaussian blur、convolution、image blending などの一般的な組み込み RenderScript カーネルです。詳しくは、
ScriptIntrinsic
のサブクラスをご覧ください。
- ScriptC: 上記の RenderScript カーネルを記述するで説明したユーザー定義のスクリプトです。各スクリプトには、Java コードから簡単にアクセスするための RenderScript コンパイラによって反映された Java クラスがあります。このクラスは
- 割り当てにデータを入力する。
createFromBitmap()
を使用して作成された割り当てを除いて、割り当てが最初に作成されると空のデータが入力されます。データを入力するには、Allocation
のいずれかの「copy」メソッドを使用します。「copy」メソッドは、同期されます。 - 必要なスクリプト グローバルを設定します。同じ
ScriptC_filename
クラスのset_globalname
という名前のメソッドを使用して、グローバルを設定することもできます。たとえば、threshold
という名前のint
変数を設定するには、Java メソッドset_threshold(int)
を使用します。また、lookup
という名前のrs_allocation
変数を設定するには、Java メソッドset_lookup(Allocation)
を使用します。set
メソッドは非同期です。 - 適切なカーネルと呼び出し可能な関数を起動します。
指定されたカーネルを起動するメソッドは、
forEach_mappingKernelName()
またはreduce_reductionKernelName()
という名前のメソッドで同じScriptC_filename
クラスに反映されます。これらの起動は非同期です。カーネルに渡す引数に応じて、メソッドは 1 つ以上の割り当てを受け取ります。割り当ては、すべて同じディメンションを持つ必要があります。デフォルトでは、カーネルはこれらのディメンション内のすべての座標で実行されます。座標のサブセットでカーネルを実行するには、最後の引数として適切なScript.LaunchOptions
をforEach
メソッドまたはreduce
メソッドに渡します。同じ
ScriptC_filename
クラスに反映されたinvoke_functionName
メソッドを使用して呼び出し可能な関数を起動します。これらの起動は非同期です。 Allocation
オブジェクトと javaFutureType オブジェクトからデータを取得します。Java コードからAllocation
のデータにアクセスするには、Allocation
の「copy」メソッドのいずれかを使用して、対象データを Java にコピーする必要があります。リダクション カーネルの結果を取得するには、javaFutureType.get()
メソッドを使用する必要があります。「copy」メソッドとget()
メソッドは、同期されます。- RenderScript コンテキストを破棄します。RenderScript コンテキストを破棄するには、
destroy()
を使用するか、RenderScript コンテキストのオブジェクトをガベージ コレクトできるようにします。これにより、後からそのコンテキストに属する任意のオブジェクトを使用した場合に、例外がスローされます。
非同期実行モデル
反映済みの forEach
、invoke
、reduce
、set
の各メソッドは非同期で、要求されたアクションを完了する前にそれぞれ Java に値を返す場合があります。ただし、個々のアクションは、起動された順にシリアル化されます。
Allocation
クラスには、割り当て間でデータをコピーするための「copy」メソッドが用意されています。「copy」メソッドは同期的で、同じ割り当てに関連する上記の非同期アクションのいずれかに対してシリアル化されます。
反映済みの javaFutureType クラスには、リダクションの結果を取得するための get()
メソッドが用意されています。get()
は同期され、リダクション(非同期)に対してシリアル化されます。
シングルソース RenderScript
Android 7.0(API レベル 24)では、シングルソース RenderScript という新しいプログラミング機能が導入されています。この機能ではカーネルは、Java ではなく定義されたスクリプトから起動されます。現在、この手法はマッピング カーネルに限定されており、簡潔にするためこのセクションでは単に「カーネル」と呼びます。この新機能は、スクリプト内部から rs_allocation
のタイプの割り当てを作成することもサポートしています。複数のカーネルの起動が必要な場合でも、すべてスクリプト内でアルゴリズム全体を実装できるようになりました。このメリットは 2 つあります。アルゴリズムの実装を 1 つの言語でのみ行うため、コードが読みやすくなります。さらに複数のカーネルを起動した際に Java と RenderScript 間の移行が少ないため、コードが高速化される可能性があります。
シングルソース RenderScript で、RenderScript カーネルを記述するの説明に従ってカーネルを作成します。次に rsForEach()
を呼び出す、呼び出し可能な関数を記述して起動します。この API では、最初のパラメータとしてカーネル関数を取り、続いて入力と出力の割り当てを行います。同様の API である rsForEachWithOptions()
は、rs_script_call_t
型の追加の引数を取ります。これにより、カーネル関数が処理する入力と出力の割り当てから要素のサブセットを指定します。
RenderScript の計算を開始するには、Java から呼び出し可能な関数を呼び出します。Java コードから RenderScript を使用するの手順に従います。適切なカーネルを起動する手順で、invoke_function_name()
を使用して呼び出し可能な関数を呼び出します。これにより、カーネルの起動を含む、全体的な計算が開始されます。
1 つのカーネル起動から別のカーネル起動に中間結果を保存して渡すために、頻繁に割り当てが必要になります。これらは rsCreateAllocation() を使用して作成できます。その API の使いやすい形式には
rsCreateAllocation_<T><W>(…)
があります。ここで T は要素のデータ型で、W は要素のベクトル幅です。API は、ディメンション X、Y、Z のサイズを引数として取ります。1D または 2D の割り当てでは、ディメンション Y または Z のサイズは省略できます。たとえば、rsCreateAllocation_uchar4(16384)
は 16,384 個の要素の 1D 割り当てを作成します。ここで、各要素の型は uchar4
です。
割り当てはシステムによって自動的に管理されます。明示的に公開または解放する必要はありません。ただし、システムができるだけ早くリソースを解放できるように、rsClearObject(rs_allocation* alloc)
を呼び出してハンドル alloc
が不要になったことを元の割り当てに対して示せます。
RenderScript カーネルを記述するのセクションに、画像を反転するカーネルの例を示しています。以下の例では、シングルソース RenderScript を使用してカーネルを展開し、画像に複数の効果を適用しています。他のカーネル greyscale
も含まれています。これは、カラー画像をモノクロに変換します。次に、呼び出し可能な関数 process()
が 2 つのカーネルを連続して入力画像に適用し、出力画像を生成します。入力と出力の両方の割り当ては、rs_allocation
型の引数として渡されます。
// File: singlesource.rs #pragma version(1) #pragma rs java_package_name(com.android.rssample) static const float4 weight = {0.299f, 0.587f, 0.114f, 0.0f}; uchar4 RS_KERNEL invert(uchar4 in, uint32_t x, uint32_t y) { uchar4 out = in; out.r = 255 - in.r; out.g = 255 - in.g; out.b = 255 - in.b; return out; } uchar4 RS_KERNEL greyscale(uchar4 in) { const float4 inF = rsUnpackColor8888(in); const float4 outF = (float4){ dot(inF, weight) }; return rsPackColorTo8888(outF); } void process(rs_allocation inputImage, rs_allocation outputImage) { const uint32_t imageWidth = rsAllocationGetDimX(inputImage); const uint32_t imageHeight = rsAllocationGetDimY(inputImage); rs_allocation tmp = rsCreateAllocation_uchar4(imageWidth, imageHeight); rsForEach(invert, inputImage, tmp); rsForEach(greyscale, tmp, outputImage); }
次のように、Java または Kotlin から process()
関数を呼び出すことができます。
Kotlin
val RS: RenderScript = RenderScript.create(context) val script = ScriptC_singlesource(RS) val inputAllocation: Allocation = Allocation.createFromBitmapResource( RS, resources, R.drawable.image ) val outputAllocation: Allocation = Allocation.createTyped( RS, inputAllocation.type, Allocation.USAGE_SCRIPT or Allocation.USAGE_IO_OUTPUT ) script.invoke_process(inputAllocation, outputAllocation)
Java
// File SingleSource.java RenderScript RS = RenderScript.create(context); ScriptC_singlesource script = new ScriptC_singlesource(RS); Allocation inputAllocation = Allocation.createFromBitmapResource( RS, getResources(), R.drawable.image); Allocation outputAllocation = Allocation.createTyped( RS, inputAllocation.getType(), Allocation.USAGE_SCRIPT | Allocation.USAGE_IO_OUTPUT); script.invoke_process(inputAllocation, outputAllocation);
この例では、2 つのカーネル起動を伴うアルゴリズムを、RenderScript 言語自体で完全に実装する方法を示します。シングルソース RenderScript を使用しない場合は、Java コードから両方のカーネルを起動することが必要になり、カーネルの起動をカーネルの定義と分けることになり、アルゴリズム全体を理解することが難しくなります。シングルソース RenderScript コードは読みやすく、カーネルの起動全体で Java とスクリプト間の移行も不要になります。反復アルゴリズムの中には、何百回もカーネルを起動し、そうした移行の負荷が著しく高くなるものもあります。
スクリプト グローバル
スクリプト グローバルは、スクリプト(.rs
)ファイル内の通常の非 static
グローバル変数です。filename.rs
ファイルで定義されている、var という名前のスクリプト グローバルの場合は、クラス ScriptC_filename
に反映されたメソッド get_var
があります。グローバルが const
でない場合は、メソッド set_var
もあります。
特定のスクリプト グローバルは、Java 値とスクリプト値の 2 つの個別の値を持ちます。これらの値は次のように動作します。
- var がスクリプト内に静的イニシャライザを持つ場合、Java とスクリプトの両方で var の初期値が指定されます。それ以外の場合、初期値はゼロです。
- スクリプト内の var にアクセスして、そのスクリプト値の読み取りと書き込みを行います。
get_var
メソッドは Java 値を読み取ります。set_var
メソッド(存在する場合)は、すぐに Java 値を書き込み、非同期にスクリプト値を書き込みます。
注: スクリプト内の静的イニシャライザを除いて、スクリプト内からグローバルに書き込まれた値は Java からは見えません。
リダクション カーネルの詳細
リダクションは、データのコレクションを 1 つの値にまとめるプロセスです。これは、次のようなアプリの並列プログラミングで役立つプリミティブです。
- すべてのデータの合計または積を計算する
- すべてのデータに対して論理演算を計算する(
and
、or
、xor
) - データ内の最小値または最大値を見つける
- 特定の値またはデータ内の特定の値の座標を検索する
Android 7.0(API レベル 24)以降では、RenderScript はリダクション カーネルをサポートして、ユーザー作成の効率的なリダクション アルゴリズムを実現しました。リダクション カーネルは、1、2、または 3 つのディメンションの入力で起動できます。
上記の例は、単純な addint リダクション カーネルを示しています。1 つのディメンションの Allocation
で long
の最小値と最大値の位置を見つける、より複雑な findMinAndMax リダクション カーネルを以下に示します。
#define LONG_MAX (long)((1UL << 63) - 1) #define LONG_MIN (long)(1UL << 63) #pragma rs reduce(findMinAndMax) \ initializer(fMMInit) accumulator(fMMAccumulator) \ combiner(fMMCombiner) outconverter(fMMOutConverter) // Either a value and the location where it was found, or INITVAL. typedef struct { long val; int idx; // -1 indicates INITVAL } IndexedVal; typedef struct { IndexedVal min, max; } MinAndMax; // In discussion below, this initial value { { LONG_MAX, -1 }, { LONG_MIN, -1 } } // is called INITVAL. static void fMMInit(MinAndMax *accum) { accum->min.val = LONG_MAX; accum->min.idx = -1; accum->max.val = LONG_MIN; accum->max.idx = -1; } //---------------------------------------------------------------------- // In describing the behavior of the accumulator and combiner functions, // it is helpful to describe hypothetical functions // IndexedVal min(IndexedVal a, IndexedVal b) // IndexedVal max(IndexedVal a, IndexedVal b) // MinAndMax minmax(MinAndMax a, MinAndMax b) // MinAndMax minmax(MinAndMax accum, IndexedVal val) // // The effect of // IndexedVal min(IndexedVal a, IndexedVal b) // is to return the IndexedVal from among the two arguments // whose val is lesser, except that when an IndexedVal // has a negative index, that IndexedVal is never less than // any other IndexedVal; therefore, if exactly one of the // two arguments has a negative index, the min is the other // argument. Like ordinary arithmetic min and max, this function // is commutative and associative; that is, // // min(A, B) == min(B, A) // commutative // min(A, min(B, C)) == min((A, B), C) // associative // // The effect of // IndexedVal max(IndexedVal a, IndexedVal b) // is analogous (greater . . . never greater than). // // Then there is // // MinAndMax minmax(MinAndMax a, MinAndMax b) { // return MinAndMax(min(a.min, b.min), max(a.max, b.max)); // } // // Like ordinary arithmetic min and max, the above function // is commutative and associative; that is: // // minmax(A, B) == minmax(B, A) // commutative // minmax(A, minmax(B, C)) == minmax((A, B), C) // associative // // Finally define // // MinAndMax minmax(MinAndMax accum, IndexedVal val) { // return minmax(accum, MinAndMax(val, val)); // } //---------------------------------------------------------------------- // This function can be explained as doing: // *accum = minmax(*accum, IndexedVal(in, x)) // // This function simply computes minimum and maximum values as if // INITVAL.min were greater than any other minimum value and // INITVAL.max were less than any other maximum value. Note that if // *accum is INITVAL, then this function sets // *accum = IndexedVal(in, x) // // After this function is called, both accum->min.idx and accum->max.idx // will have nonnegative values: // - x is always nonnegative, so if this function ever sets one of the // idx fields, it will set it to a nonnegative value // - if one of the idx fields is negative, then the corresponding // val field must be LONG_MAX or LONG_MIN, so the function will always // set both the val and idx fields static void fMMAccumulator(MinAndMax *accum, long in, int x) { IndexedVal me; me.val = in; me.idx = x; if (me.val <= accum->min.val) accum->min = me; if (me.val >= accum->max.val) accum->max = me; } // This function can be explained as doing: // *accum = minmax(*accum, *val) // // This function simply computes minimum and maximum values as if // INITVAL.min were greater than any other minimum value and // INITVAL.max were less than any other maximum value. Note that if // one of the two accumulator data items is INITVAL, then this // function sets *accum to the other one. static void fMMCombiner(MinAndMax *accum, const MinAndMax *val) { if ((accum->min.idx < 0) || (val->min.val < accum->min.val)) accum->min = val->min; if ((accum->max.idx < 0) || (val->max.val > accum->max.val)) accum->max = val->max; } static void fMMOutConverter(int2 *result, const MinAndMax *val) { result->x = val->min.idx; result->y = val->max.idx; }
注: その他のリダクション カーネルの例は、こちらに記載しています。
RenderScript ランタイムは、リダクション カーネルを実行するために、アキュムレータ データアイテムという変数を 1 つ以上作成して、リダクション プロセスの状態を保持します。RenderScript ランタイムは、パフォーマンスを最大化する場合と同様の方法でアキュムレータ データアイテムの数を選択します。アキュムレータ データアイテムの型(accumType)は、カーネルのアキュムレータ関数によって決まります。この関数の最初の引数は、アキュムレータ データアイテムを指すポインタです。デフォルトでは、すべてのアキュムレータ データアイテムはゼロに初期化されます(memset
による場合のように)。ただし、イニシャライザ関数を作成して別の処理を行うこともできます。
例: addint カーネルでは、アキュムレータ データアイテム(int
型)を使用して入力値を加算します。イニシャライザ関数はないため、各アキュムレータ データアイテムはゼロに初期化されます。
例: findMinAndMax カーネルでは、アキュムレータ データアイテム(MinAndMax
型)は、これまでに見つかった最小値と最大値を追跡するために使用されます。これをそれぞれ LONG_MAX
と LONG_MIN
に設定し、それらの値の位置を値が処理済みの入力の(空の)部分に実際に存在しないことを示す -1 に設定するイニシャライザ関数があります。
RenderScript は、入力内の座標ごとにアキュムレータ関数を 1 回呼び出します。通常、関数は、入力に応じてアキュムレータ データアイテムを更新する必要があります。
例: addint カーネルでは、アキュムレータ関数は、入力要素の値をアキュムレータ データアイテムに追加します。
例: findMinAndMax カーネルでは、アキュムレータ関数は、入力要素の値がアキュムレータ データアイテムに記録された最小値以下であるか、アキュムレータ データアイテムに記録された最大値以上であるかどうかを確認し、それに応じてアキュムレータ データアイテムを更新します。
入力の座標ごとにアキュムレータ関数が 1 回呼び出された後、RenderScript はアキュムレータ データアイテムを結合して 1 つのアキュムレータ データアイテムにまとめる必要があります。これを行うには、コンバイナ関数を記述します。アキュムレータ関数が単一の入力を持ち、特別な引数を持たない場合は、コンバイナ関数を記述する必要はありません。RenderScript はアキュムレータ関数を使用してアキュムレータ データアイテムを結合します(このデフォルトの動作が意図したものと異なる場合でも、コンバイナ関数を記述できます)。
例: addint カーネルではコンバイナ関数がないため、アキュムレータ関数が使用されます。これは正しい動作です。値のコレクションを 2 つに分けて、値を 2 つ別々に合計し、2 つの合計値を合わせると、コレクション全体を合計した場合と同じになるためです。
例: findMinAndMax カーネルでは、コンバイナ関数は、「ソース」アキュムレータ データアイテム *val
に記録されている最小値が「宛先」アキュムレータ データアイテム *accum
に記録されている最小値より小さいかどうかを確認し、それに応じて *accum
を更新します。最大値に対しても同様の処理が行われます。これにより *accum
が更新され、一部の値がそれぞれ *accum
と *val
に合算されるのではなく、すべての入力値が *accum
に合算された場合の状態になります。
すべてのアキュムレータ データアイテムが結合された後、RenderScript は Java に返すリダクションの結果を決定します。これを実行するには、アウトコンバータ関数を記述します。結合済みのアキュムレータ データアイテムの最終値をリダクションの結果にする場合は、outconverter 関数を記述する必要はありません。
例: addint カーネルでは、アウトコンバータ関数はありません。結合されたデータアイテムの最終値は、入力のすべての要素の合計です。これが、返される必要がある値です。
例: findMinAndMax カーネルでは、アウトコンバータ関数は、int2
の結果の値を初期化して、すべてのアキュムレータ データアイテムの組み合わせから得られる最小値と最大値の位置を保持します。
リダクション カーネルを記述する
#pragma rs reduce
は、名前とカーネルを構成する関数の名前と役割を指定して、リダクション カーネルを定義します。このような関数は、すべて static
であることが必要です。リダクション カーネルには、常に accumulator
関数が必要です。カーネルに必要な動作に応じて、他の関数の一部またはすべてを省略できます。
#pragma rs reduce(kernelName) \ initializer(initializerName) \ accumulator(accumulatorName) \ combiner(combinerName) \ outconverter(outconverterName)
#pragma
のアイテムの意味は次のとおりです。
reduce(kernelName)
(必須): リダクション カーネルが定義されていることを指定します。反映された Java メソッドreduce_kernelName
がカーネルを起動します。initializer(initializerName)
(省略可): このリダクション カーネルのイニシャライザ関数の名前を指定します。カーネルを起動すると、RenderScript はアキュムレータ データアイテムごとにこの関数を 1 回呼び出します。この関数は次のように定義する必要があります。static void initializerName(accumType *accum) { … }
accum
は、この関数が初期化するアキュムレータ データアイテムを指すポインタです。イニシャライザ関数を指定しない場合、RenderScript はすべてのアキュムレータ データアイテムを 0 に初期化します(
memset
による場合と同様)。この場合、次のようなイニシャライザ関数があるかのように動作します。static void initializerName(accumType *accum) { memset(accum, 0, sizeof(*accum)); }
accumulator(accumulatorName)
(必須): このリダクション カーネルのアキュムレータ関数の名前を指定します。カーネルを起動すると、RenderScript は入力内の座標ごとにこの関数を 1 回呼び出して、入力に応じてなんらかの方法でアキュムレータ データアイテムを更新します。この関数は次のように定義する必要があります。static void accumulatorName(accumType *accum, in1Type in1, …, inNType inN [, specialArguments]) { … }
accum
は、この関数が変更するアキュムレータ データアイテムを指すポインタです。in1
からinN
は、カーネルの起動に渡される入力に基づいて自動的に入力される、1 つまたは複数の引数で、入力あたり 1 つの引数が割り当てられます。アキュムレータ関数は、オプションでいずれかの特別な引数を取ることができます。複数の入力を持つカーネルの例が
dotProduct
です。combiner(combinerName)
(省略可): このリダクション カーネルのコンバイナ関数の名前を指定します。RenderScript が入力内のすべての座標に対してアキュムレータ関数を 1 回ずつ呼び出すと、この関数を必要な回数だけ呼び出して、すべてのアキュムレータ データアイテムを 1 つのアキュムレータ データアイテムに結合します。この関数は次のように定義する必要があります。
static void combinerName(accumType *accum, const accumType *other) { … }
accum
は、この関数が変更する「宛先」アキュムレータ データアイテムを指すポインタです。other
は、この関数が*accum
に「結合」するための「ソース」アキュムレータ データアイテムを指すポインタです。注:
*accum
、*other
、または両方が初期化されていてもアキュムレータ関数に渡されていない可能性があります。その場合は、いずれか、または両方が入力データに応じて更新されていません。たとえば、findMinAndMax カーネルでは、コンバイナ関数fMMCombiner
は明示的にidx < 0
を確認します。これは、値が INITVAL であるアキュムレータ データアイテムを示すためです。コンバイナ関数を指定しない場合、RenderScript はその代わりにアキュムレータ関数を使用して、次のようなコンバイナ関数があるかのように動作します。
static void combinerName(accumType *accum, const accumType *other) { accumulatorName(accum, *other); }
カーネルに複数の入力があり、入力データ型がアキュムレータ データ型と同じでない場合、またはアキュムレータ関数が 1 つまたは複数の特別な引数を取る場合、コンバイナ関数は必須です。
outconverter(outconverterName)
(省略可): このリダクション カーネルのアウトコンバータ関数の名前を指定します。RenderScript は、すべてのアキュムレータ データアイテムを結合した後、この関数を呼び出して、Java に返すリダクションの結果を決定します。この関数は次のように定義する必要があります。static void outconverterName(resultType *result, const accumType *accum) { … }
result
は、この関数が縮小の結果に応じて初期化する結果データアイテム(割り当てられていても RenderScript ランタイムにより初期化されていない)を指すポインタです。resultType は、そのデータアイテムの型であり、accumType と同じである必要はありません。accum
は、コンバイナ関数によって計算された最終的なアキュムレータ データアイテムを指すポインタです。アウトコンバータ関数を指定しない場合、RenderScript は最終的なアキュムレータ データアイテムを結果データアイテムにコピーし、次のようなアウトコンバータ関数があるかのように動作します。
static void outconverterName(accumType *result, const accumType *accum) { *result = *accum; }
アキュムレータのデータ型とは異なる結果型が必要な場合は、アウトコンバータ関数は必須です。
カーネルには、入力型、アキュムレータ データアイテム型、結果型があり、いずれも同じである必要はありません。たとえば、findMinAndMax カーネルでは、入力型 long
、アキュムレータ データアイテム型 MinAndMax
、結果型 int2
はすべて異なります。
想定できないこと
特定のカーネルの起動に対して、RenderScript により作成されたアキュムレータ データテイムの数に依存しないでください。同じ入力を使用して同一のカーネルを 2 回起動しても、同じ数のアキュムレータ データアイテムが生成される保証はありません。
RenderScript がイニシャライザ、アキュムレータ、コンバイナの各関数を呼び出す順序に依存しないでください。それらの一部を並列に呼び出すこともできます。同じ入力を持つ同じカーネルの 2 回の起動が、同じ順序で行われる保証はありません。唯一保証されているのは、イニシャライザ関数にのみ、初期化されていないアキュムレータ データアイテムが示されることです。次に例を示します。
- すべてのアキュムレータ データアイテムが、アキュムレータ関数が呼び出される前に初期化される保証はありませんが、初期化されたアキュムレータ データアイテムに対してのみ呼び出されます。
- 入力要素がアキュムレータ関数に渡される順序は保証されません。
- コンバイナ関数が呼び出される前に、アキュムレータ関数のすべての入力要素が呼び出されていたという保証はありません。
このことによる 1 つの結果は、findMinAndMax カーネルは決定的ではないということです。入力に同じ最小値や最大値が複数ある場合、カーネルがどのような値を見つけるかを知る方法はありません。
保証する必要があること
RenderScript システムは、カーネルを多くの異なる方法で実行することを選択できるため、カーネルが意図したとおりに動作するよう、特定の規則に従う必要があります。これらのルールに従わないと、結果が正しくないまたは動作が決定しない状態、あるいはランタイム エラーが発生することがあります。
以下のルールでは、2 つのアキュムレータ データアイテムに「同じ値」が必要であることが、多くの箇所で規定されています。これは何を意味するでしょうか。これは、カーネルに求める動作によります。addint のような数学的なリダクションの場合、通常、「同じ」は数学的に等しいことを意味します。findMinAndMax(最小入力値と最大入力値の位置を見つける)のような「pick any」検索では、同じ入力値が複数回出現する可能性があり、特定の入力値の位置はすべて「同じ」とみなす必要があります。同様のカーネルを作成して「左端の最小入力値と最大入力値の位置を見つける」とします。この場合(たとえば)位置 100 の最小値が位置 200 の同じ最小値よりも優先されます。このカーネルでは、「同じ」は同一の位置を指し、単に同一値を指すのではありません。アキュムレータ関数とコンバイナ関数は、findMinAndMax の場合とは異なるものであることが必要です。
イニシャライザ関数は、ID 値を作成する必要があります。つまり、I
と A
がイニシャライザ関数で初期化されたアキュムレータ データアイテムであり、I
がアキュムレータ関数に一度も渡されていない(しかし A
は渡された可能性がある)場合は、
例: addint カーネルでは、アキュムレータ データアイテムは 0 に初期化されます。このカーネルのコンバイナ関数は加算を行います。ゼロは加算のための ID 値です。
例: findMinAndMax カーネルでは、アキュムレータ データアイテムは INITVAL
に初期化されます。
I
がINITVAL
であるため、fMMCombiner(&A, &I)
はA
を同じ値のままにします。I
がINITVAL
であるため、fMMCombiner(&I, &A)
はI
をA
に設定します。
したがって、INITVAL
は実際に ID 値です。
コンバイナ関数は可換にする必要があります。つまり、A
と B
がイニシャライザ関数によって初期化されたアキュムレータ データアイテムであり、0 回以上アキュムレータ関数に渡された可能性がある場合は、combinerName(&A, &B)
は A
を、combinerName(&B, &A)
が B
に設定したのと同じ値に設定する必要があります。
例: addint カーネルでは、combiner 関数は、2 つのアキュムレータ関数データアイテム値を加算します。加算は可換です。
例: findMinAndMax カーネルでは、fMMCombiner(&A, &B)
は A = minmax(A, B)
と同じであり、minmax
は可換であり、fMMCombiner
も同様です。
コンバイナ関数は結合にする必要があります。つまり、A
、B
、C
がイニシャライザ化関数によって初期化されたアキュムレータ データアイテムであり、0 回以上アキュムレータ関数に渡された可能性がある場合は、次の 2 つのコード シーケンスにより A
を同じ値に設定する必要があります。
combinerName(&A, &B); combinerName(&A, &C);
combinerName(&B, &C); combinerName(&A, &B);
例: addint カーネルでは、コンバイナ関数は、2 つのアキュムレータ データアイテム値を追加します。
A = A + B A = A + C // Same as // A = (A + B) + C
B = B + C A = A + B // Same as // A = A + (B + C) // B = B + C
加算は結合的であり、コンバイナ関数も同様です。
例: findMinAndMax カーネルでは、
fMMCombiner(&A, &B)
A = minmax(A, B)
A = minmax(A, B) A = minmax(A, C) // Same as // A = minmax(minmax(A, B), C)
B = minmax(B, C) A = minmax(A, B) // Same as // A = minmax(A, minmax(B, C)) // B = minmax(B, C)
minmax
は結合的であり、fMMCombiner
も同様です。
アキュムレータ関数とコンバイナ関数はともに基本的な折りたたみルールに従う必要があります。つまり、A
と B
がアキュムレータ データアイテムであり、A
がイニシャライザ関数によって 0 回以上初期化されていて、B
は初期化されておらず、args が入力引数とアキュムレータ関数の特定の呼び出しのための特別な引数のリストである場合、次の 2 つのコード シーケンスでは、A
を同じ値に設定する必要があります。
accumulatorName(&A, args); // statement 1
initializerName(&B); // statement 2 accumulatorName(&B, args); // statement 3 combinerName(&A, &B); // statement 4
例: addint カーネルでは、入力値 V は:
- ステートメント 1 は
A += V
と同じです。 - ステートメント 2 は
B = 0
と同じです。 - ステートメント 3 は
B += V
と同じです。これはB = V
と同じです。 - ステートメント 4 は
A += B
と同じです。これはA += V
と同じです。
ステートメント 1 と 4 は A
を同じ値に設定し、そのためこのカーネルは基本の折りたたみルールに従います。
例: findMinAndMax カーネルでは、座標 X の入力値 Vは:
- ステートメント 1 は
A = minmax(A, IndexedVal(V, X))
と同じです。 - ステートメント 2 は
B = INITVAL
と同じです。 - ステートメント 3 は次と同じです。
これは、B が初期値であるため、次と同じです。B = minmax(B, IndexedVal(V, X))
B = IndexedVal(V, X)
- ステートメント 4 は次と同じです。
これは次と同じです。A = minmax(A, B)
A = minmax(A, IndexedVal(V, X))
ステートメント 1 と 4 は A
を同じ値に設定し、そのためこのカーネルは基本の折りたたみルールに従います。
Java コードからリダクション カーネルを呼び出す
filename.rs
ファイルで定義された kernelName という名前のリダクション カーネルでは、クラス ScriptC_filename
に反映された 3 つのメソッドがあります。
Kotlin
// Function 1 fun reduce_kernelName(ain1: Allocation, …, ainN: Allocation): javaFutureType // Function 2 fun reduce_kernelName(ain1: Allocation, …, ainN: Allocation, sc: Script.LaunchOptions): javaFutureType // Function 3 fun reduce_kernelName(in1: Array<devecSiIn1Type>, …, inN: Array<devecSiInNType>): javaFutureType
Java
// Method 1 public javaFutureType reduce_kernelName(Allocation ain1, …, Allocation ainN); // Method 2 public javaFutureType reduce_kernelName(Allocation ain1, …, Allocation ainN, Script.LaunchOptions sc); // Method 3 public javaFutureType reduce_kernelName(devecSiIn1Type[] in1, …, devecSiInNType[] inN);
addint カーネルを呼び出す例をいくつか示します。
Kotlin
val script = ScriptC_example(renderScript) // 1D array // and obtain answer immediately val input1 = intArrayOf(…) val sum1: Int = script.reduce_addint(input1).get() // Method 3 // 2D allocation // and do some additional work before obtaining answer val typeBuilder = Type.Builder(RS, Element.I32(RS)).apply { setX(…) setY(…) } val input2: Allocation = Allocation.createTyped(RS, typeBuilder.create()).also { populateSomehow(it) // fill in input Allocation with data } val result2: ScriptC_example.result_int = script.reduce_addint(input2) // Method 1 doSomeAdditionalWork() // might run at same time as reduction val sum2: Int = result2.get()
Java
ScriptC_example script = new ScriptC_example(renderScript); // 1D array // and obtain answer immediately int input1[] = …; int sum1 = script.reduce_addint(input1).get(); // Method 3 // 2D allocation // and do some additional work before obtaining answer Type.Builder typeBuilder = new Type.Builder(RS, Element.I32(RS)); typeBuilder.setX(…); typeBuilder.setY(…); Allocation input2 = createTyped(RS, typeBuilder.create()); populateSomehow(input2); // fill in input Allocation with data ScriptC_example.result_int result2 = script.reduce_addint(input2); // Method 1 doSomeAdditionalWork(); // might run at same time as reduction int sum2 = result2.get();
メソッド 1 は、カーネルのアキュムレータ関数のすべての入力引数に対して 1 つの入力 Allocation
引数を持ちます。RenderScript ランタイムは、すべての入力割り当てが同じディメンションで、各入力割り当ての Element
型が、アキュムレータ関数のプロトタイプの対応する入力引数のものと一致することを確認します。いずれかの確認が失敗すると、RenderScript は例外をスローします。カーネルはこれらのディメンション内のすべての座標に対して実行されます。
メソッド 2 は、カーネルの実行を座標のサブセットに制限するために使用できる追加の引数 sc
を取る点を除いて、メソッド 1 と同じです。
メソッド 3 は割り当て入力の代わりに Java 配列の入力を取得する点を除いてメソッド 1 と同じです。これは、コードを記述して明示的に割り当てを作成し、Java 配列からデータをコピーする必要がなくなり便利です。ただし、メソッド 1 の代わりにメソッド 3 を使用してもコードのパフォーマンスは改善されません。入力配列ごとに、メソッド 3 は、適切な Element
タイプと setAutoPadding(boolean)
を有効にした 1 つのディメンションの一時的な割り当てを作成し、Allocation
の適切な copyFrom()
メソッドであるかのように配列を割り当てに対してコピーします。次に、メソッド 1 を呼び出して、これらの一時的な割り当てを渡します。
注: アプリが同じ配列、または同じディメンションの異なる配列と要素型を持つ複数のカーネルを呼び出した場合は、メソッド 3 を使用せずに、自身で割り当てを明示的に作成、移入、再利用することでパフォーマンスを改善できます。
javaFutureType は、反映されたリダクション メソッドの戻り型であり、ScriptC_filename
クラス内の反映済みの静的なネストされたクラスです。リダクション カーネルの実行に関する将来の結果を表します。実際の実行結果を取得するには、そのクラスの get()
メソッドを呼び出します。これにより、javaResultType 型の値が返されます。get()
は同期されます。
Kotlin
class ScriptC_filename(rs: RenderScript) : ScriptC(…) { object javaFutureType { fun get(): javaResultType { … } } }
Java
public class ScriptC_filename extends ScriptC { public static class javaFutureType { public javaResultType get() { … } } }
javaResultType は、アウトコンバータ関数の resultType によって決定されます。resultType が符号なし型(スカラー、ベクトル、または配列)でない限り、javaResultType は直接対応する Java 型です。resultType が符号なし型であり、Java の符号付き型のほうが大きい場合、javaResultType は、その大きいほうの Java 符号付き型になります。それ以外の場合は、直接対応する Java 型です。次に例を示します。
- resultType が
int
、int2
、またはint[15]
の場合、javaResultType はint
、Int2
、またはint[]
です。resultType の値はすべて、javaResultType で表すことができます。 - resultType が
uint
、uint2
、またはuint[15]
の場合、javaResultType はlong
、Long2
またはlong[]
です。resultType の値はすべて、javaResultType で表すことができます。 - resultType が
ulong
、ulong2
、またはulong[15]
の場合、javaResultType はlong
、Long2
またはlong[]
です。javaResultType で表現できない resultType の特定の値があります。
javaFutureType は、アウトコンバータ関数の resultType に対応する将来の結果型です。
- resultType が配列型ではない場合、javaFutureType は
result_resultType
です。 - resultType が memberType 型のメンバーを持つ長さである Count の場合、javaFutureType は
resultArrayCount_memberType
です。
次に例を示します。
Kotlin
class ScriptC_filename(rs: RenderScript) : ScriptC(…) { // for kernels with int result object result_int { fun get(): Int = … } // for kernels with int[10] result object resultArray10_int { fun get(): IntArray = … } // for kernels with int2 result // note that the Kotlin type name "Int2" is not the same as the script type name "int2" object result_int2 { fun get(): Int2 = … } // for kernels with int2[10] result // note that the Kotlin type name "Int2" is not the same as the script type name "int2" object resultArray10_int2 { fun get(): Array<Int2> = … } // for kernels with uint result // note that the Kotlin type "long" is a wider signed type than the unsigned script type "uint" object result_uint { fun get(): Long = … } // for kernels with uint[10] result // note that the Kotlin type "long" is a wider signed type than the unsigned script type "uint" object resultArray10_uint { fun get(): LongArray = … } // for kernels with uint2 result // note that the Kotlin type "Long2" is a wider signed type than the unsigned script type "uint2" object result_uint2 { fun get(): Long2 = … } // for kernels with uint2[10] result // note that the Kotlin type "Long2" is a wider signed type than the unsigned script type "uint2" object resultArray10_uint2 { fun get(): Array<Long2> = … } }
Java
public class ScriptC_filename extends ScriptC { // for kernels with int result public static class result_int { public int get() { … } } // for kernels with int[10] result public static class resultArray10_int { public int[] get() { … } } // for kernels with int2 result // note that the Java type name "Int2" is not the same as the script type name "int2" public static class result_int2 { public Int2 get() { … } } // for kernels with int2[10] result // note that the Java type name "Int2" is not the same as the script type name "int2" public static class resultArray10_int2 { public Int2[] get() { … } } // for kernels with uint result // note that the Java type "long" is a wider signed type than the unsigned script type "uint" public static class result_uint { public long get() { … } } // for kernels with uint[10] result // note that the Java type "long" is a wider signed type than the unsigned script type "uint" public static class resultArray10_uint { public long[] get() { … } } // for kernels with uint2 result // note that the Java type "Long2" is a wider signed type than the unsigned script type "uint2" public static class result_uint2 { public Long2 get() { … } } // for kernels with uint2[10] result // note that the Java type "Long2" is a wider signed type than the unsigned script type "uint2" public static class resultArray10_uint2 { public Long2[] get() { … } } }
javaResultType がオブジェクト型(配列型を含む)である場合、同じインスタンスでの javaFutureType.get()
に対する各呼び出しによって、同じオブジェクトが返されます。
javaResultType が、resultType 型のすべての値を表すことができず、リダクション カーネルが表現できない値を生成する場合、javaFutureType.get()
は例外をスローします。
メソッド 3 と devecSiInXType
devecSiInXType は、アキュムレータ関数の対応する引数の inXType に対応する Java 型です。inXType が符号なし型またはベクトル型でない限り、devecSiInXType は直接対応する Java 型です。inXType が符号なしスカラー型の場合、devecSiInXType は、同じサイズの符号付きスカラー型に直接対応する Java 型です。inXType が符号付きベクトル型の場合、devecSiInXType はベクトル コンポーネント型に直接対応する Java 型です。inXType が符号なしベクトル型である場合、devecSiInXType は、ベクトル コンポーネント型と同じサイズの符号付きスカラー型に直接対応する Java 型です。次に例を示します。
- inXType が
int
の場合、devecSiInXType はint
です。 - inXType が
int2
の場合、devecSiInXType はint
です。この配列はフラット化された状態で表示されます。割り当てが 2 要素で構成されるベクトル要素を持つことから、スカラー要素の 2 倍の数となります。これは、Allocation
処理のcopyFrom()
メソッドと同じです。 - inXType が
uint
の場合、deviceSiInXType はint
です。Java 配列の符号付き値は、割り当て内の同じビットパターンの符号なし値として解釈されます。これは、Allocation
処理のcopyFrom()
メソッドと同じです。 - inXType が
uint2
の場合、deviceSiInXType はint
です。これはint2
とuint
を処理する方法を組み合わせたものです。配列はフラット化された表現で、Java 配列の符号付き値は RenderScript の符号なし要素値として解釈されます。
メソッド 3 の場合、入力型は結果型とは異なる方法で処理される点に留意してください。
- スクリプトのベクトル入力は Java 側でフラット化されますが、スクリプトのベクトル結果はフラット化されません。
- スクリプトの符号なし入力は、Java 側で同じサイズの符号付き入力として表されますが、スクリプト側の符号なしの結果は Java 側で拡張された符号付き型として表されます(
ulong
の場合を除く)。
リダクション カーネルのその他の例
#pragma rs reduce(dotProduct) \ accumulator(dotProductAccum) combiner(dotProductSum) // Note: No initializer function -- therefore, // each accumulator data item is implicitly initialized to 0.0f. static void dotProductAccum(float *accum, float in1, float in2) { *accum += in1*in2; } // combiner function static void dotProductSum(float *accum, const float *val) { *accum += *val; }
// Find a zero Element in a 2D allocation; return (-1, -1) if none #pragma rs reduce(fz2) \ initializer(fz2Init) \ accumulator(fz2Accum) combiner(fz2Combine) static void fz2Init(int2 *accum) { accum->x = accum->y = -1; } static void fz2Accum(int2 *accum, int inVal, int x /* special arg */, int y /* special arg */) { if (inVal==0) { accum->x = x; accum->y = y; } } static void fz2Combine(int2 *accum, const int2 *accum2) { if (accum2->x >= 0) *accum = *accum2; }
// Note that this kernel returns an array to Java #pragma rs reduce(histogram) \ accumulator(hsgAccum) combiner(hsgCombine) #define BUCKETS 256 typedef uint32_t Histogram[BUCKETS]; // Note: No initializer function -- // therefore, each bucket is implicitly initialized to 0. static void hsgAccum(Histogram *h, uchar in) { ++(*h)[in]; } static void hsgCombine(Histogram *accum, const Histogram *addend) { for (int i = 0; i < BUCKETS; ++i) (*accum)[i] += (*addend)[i]; } // Determines the mode (most frequently occurring value), and returns // the value and the frequency. // // If multiple values have the same highest frequency, returns the lowest // of those values. // // Shares functions with the histogram reduction kernel. #pragma rs reduce(mode) \ accumulator(hsgAccum) combiner(hsgCombine) \ outconverter(modeOutConvert) static void modeOutConvert(int2 *result, const Histogram *h) { uint32_t mode = 0; for (int i = 1; i < BUCKETS; ++i) if ((*h)[i] > (*h)[mode]) mode = i; result->x = mode; result->y = (*h)[mode]; }
その他のコードサンプル
BasicRenderScript、RenderScriptIntrinsic、Hello Compute のサンプルでは、このページで扱った API の使用方法をさらに詳しく説明しています。