6 月 3 日の「#Android11: The Beta Launch Show」にぜひご参加ください。

RenderScript の概要

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 ファイルはスクリプトと呼ばれます。すべてのスクリプトには、独自のカーネル、関数、変数のセットが含まれています。スクリプトには次の対象を含めることができます。

  • このスクリプトで使用される 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 に基づいて自動的に入力されます。引数 xy については、以下で説明します。カーネルから返された値は、出力 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 全体にわたって実行され、AllocationElement あたりアキュムレータ関数が 1 回実行されます。デフォルトでは、アキュムレータ データ項目の最終値はリダクションの結果として処理され、Java に返されます。RenderScript ランタイムは、入力割り当ての Element 型がアキュムレータ関数のプロトタイプと一致することを確認します。一致しない場合、RenderScript は例外をスローします。

      リダクション カーネルは 1 つ以上の入力 Allocations を持ちますが、出力 は持ちません。

      リダクション カーネルの詳細については、こちらをご覧ください。

      リダクション カーネルは、Android 7.0(API レベル 24)以降でサポートされています。

    マッピング カーネル関数またはリダクション カーネル アキュムレータ関数は、特別な引数 xyz を使用して現在の実行の座標にアクセスできます。これらの引数は、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 にアクセスできます。

トレードオフは次のとおりです。

  • サポート ライブラリ 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 を使用するには:

  1. 必要な Android SDK のバージョンがインストールされていることを確認します。
  2. Android ビルドプロセスの設定を更新して、RenderScript の設定を含めます。
    • アプリケーション モジュールのアプリフォルダにある build.gradle ファイルを開きます。
    • 次の RenderScript 設定をファイルに追加します。
          android {
              compileSdkVersion 28
      
              defaultConfig {
                  minSdkVersion 9
                  targetSdkVersion 19
          
                  renderscriptTargetApi 18
                  renderscriptSupportModeEnabled true
          
              }
          }
          

      上記の設定は、Android のビルドプロセスで特定の動作を制御します。

      • renderscriptTargetApi - 生成するバイトコードのバージョンを指定します。この値を、使用するすべての機能を提供できる最小の API レベルに設定し、renderscriptSupportModeEnabledtrue に設定することをおすすめします。 この設定の有効な値は、11 から最後にリリースされた API レベルまでの任意の整数値です。アプリケーション マニフェストで指定した最小の SDK バージョンが、異なる値に設定されている場合、その値は無視され、ビルドファイルのターゲット値が最小の SDK バージョンの設定に使用されます。
      • renderscriptSupportModeEnabled - 実行中のデバイスがターゲット バージョンをサポートしていない場合、生成されたバイトコードを互換性のあるバージョンに戻すことを指定します。
  3. 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 クラスに依存します。ほとんどのアプリケーションは、同じ基本的な使用パターンに従います。

  1. RenderScript コンテキストを初期化します。create(Context) で作成された RenderScript コンテキストは、RenderScript を使用できることを保証し、後続のすべての RenderScript オブジェクトのライフサイクルを制御するオブジェクトを提供します。コンテキスト作成は、ハードウェアごとにリソースを作成する可能性があるため、長時間実行の可能性を考慮することが必要です。そのため、可能な場合はアプリケーションのクリティカル パスに含めないでください。通常、アプリケーションは一度に 1 つの RenderScript コンテキストのみを持ちます。
  2. スクリプトに渡す Allocation を少なくとも 1 つ作成します。Allocation は一定量のデータを格納するための RenderScript オブジェクトです。スクリプト内のカーネルは、Allocation オブジェクトを入力および出力として扱います。スクリプト グローバルとしてバインドされている場合、Allocation オブジェクトには、カーネル内で rsGetElementAt_type()rsSetElementAt_type() を使用してアクセスできます。Allocation オブジェクトによって、Java コードから RenderScript コードへの配列の受け渡しと、その逆のことが可能になります。Allocation オブジェクトは通常、createTyped() または createFromBitmap() を使用して作成されます。
  3. 必要なスクリプトをすべて作成します。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 のサブクラスをご覧ください。
  4. 割り当てにデータを入力する。createFromBitmap() を使用して作成された割り当てを除いて、割り当てが最初に作成されると空のデータが入力されます。データを入力するには、Allocation のいずれかの「copy」メソッドを使用します。「copy」メソッドは、同期されます
  5. 必要なスクリプト グローバルを設定します。同じ ScriptC_filename クラスの set_globalname という名前のメソッドを使用して、グローバルを設定することもできます。たとえば、threshold という名前の int 変数を設定するには、Java メソッド set_threshold(int) を使用します。また、lookup という名前の rs_allocation 変数を設定するには、Java メソッド set_lookup(Allocation) を使用します。set メソッドは非同期です。
  6. 適切なカーネルと呼び出し可能な関数を起動します。

    指定されたカーネルを起動するメソッドは、forEach_mappingKernelName() または reduce_reductionKernelName() という名前のメソッドで同じ ScriptC_filename クラスに反映されます。 これらの起動は非同期です。 カーネルに渡す引数に応じて、メソッドは 1 つ以上の割り当てを受け取ります。それらの割り当ては、すべて同じディメンションを持つ必要があります。デフォルトでは、カーネルはこれらのディメンション内のすべての座標で実行されます。それらの座標のサブセットでカーネルを実行するには、最後の引数として適切な Script.LaunchOptionsforEach メソッドまたは reduce メソッドに渡します。

    同じ ScriptC_filename クラスに反映された invoke_functionName メソッドを使用して呼び出し可能な関数を起動します。 これらの起動は非同期です。

  7. Allocation オブジェクトと javaFutureType オブジェクトからデータを取得します。 Java コードから Allocation のデータにアクセスするには、Allocation の「copy」メソッドのいずれかを使用して、対象データを Java にコピーする必要があります。 リダクション カーネルの結果を取得するには、javaFutureType.get() メソッドを使用する必要があります。 「copy」メソッドと get() メソッドは、同期されます
  8. RenderScript コンテキストを破棄します。RenderScript コンテキストを破棄するには、destroy() を使用するか、RenderScript コンテキストのオブジェクトをガベージ コレクトできるようにします。これにより、後からそのコンテキストに属する任意のオブジェクトを使用した場合に、例外がスローされます。

非同期実行モデル

反映済みのforEachinvokereduceset の各メソッドは非同期で、要求されたアクションを完了する前にそれぞれ 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 つの値にまとめるプロセスです。これは、次のようなアプリケーションの並列プログラミングで役立つプリミティブです。

  • すべてのデータの合計または積を計算する
  • すべてのデータに対して論理演算を計算する(andorxor
  • データ内の最小値または最大値を見つける
  • 特定の値またはデータ内の特定の値の座標を検索する

Android 7.0(API レベル 24)以降では、RenderScript はリダクション カーネルをサポートして、ユーザー作成の効率的なリダクション アルゴリズムを実現しました。リダクション カーネルは、1、2、または 3 つのディメンションの入力で起動できます。

上記の例は、単純な addint リダクション カーネルを示しています。 1 つのディメンションの Allocationlong の最小値と最大値の位置を見つける、より複雑な 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_MAXLONG_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 値を作成する必要があります。 つまり、IA がイニシャライザ関数で初期化されたアキュムレータ データ項目であり、I がアキュムレータ関数に一度も渡されていない(しかし A は渡された可能性がある)場合は、
  • combinerName(&A, &I)A同じ値のままにする必要があります。
  • combinerName(&I, &A)IA同じ値のままにする必要があります。

例: addint カーネルでは、アキュムレータ データ項目は 0 に初期化されます。このカーネルのコンバイナ関数は加算を行います。ゼロは加算のための ID 値です。

例: findMinAndMax カーネルでは、アキュムレータ データ項目は INITVAL に初期化されます。

  • IINITVAL であるため、fMMCombiner(&A, &I)A を同じ値のままにします。
  • IINITVAL であるため、fMMCombiner(&I, &A) は を A に設定します。

したがって、INITVAL は実際に ID 値です

コンバイナ関数は可換にする必要があります。つまり、AB がイニシャライザ関数によって初期化されたアキュムレータ データ項目であり、0 回以上アキュムレータ関数に渡された可能性がある場合は、combinerName(&A, &B)A を、combinerName(&B, &A)B に設定したのと同じ値に設定する必要があります。

例: addint カーネルでは、combiner 関数は、2 つのアキュムレータ関数データ項目値を加算します。加算は可換です。

例: findMinAndMax カーネルでは、fMMCombiner(&A, &B)A = minmax(A, B) と同じであり、minmax は可変であり、fMMCombiner も同様です。

コンバイナ関数は結合にする必要があります。つまり、ABC がイニシャライザ化関数によって初期化されたアキュムレータ データ項目であり、0 回以上アキュムレータ関数に渡された可能性がある場合は、次の 2 つのコード シーケンスにより A同じ値に設定する必要があります。

  •     combinerName(&A, &B);
        combinerName(&A, &C);
        
  •     combinerName(&B, &C);
        combinerName(&A, &B);
        

例: addint カーネルでは、combiner 関数は、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)
    
したがって、2 つのシーケンスで、
  •     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 も同様です。

アキュムレータ関数とコンバイナ関数はともに基本的な折りたたみルールに従う必要があります。つまり、AB がアキュムレータ データ項目であり、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 = minmax(B, IndexedVal(V, X))
        
    これは、B が初期値であるため、次と同じです。
        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 型です。次に例を示します。

  • resultTypeintint2、または int[15] の場合、javaResultType は intInt2、または int[] です。resultTypeのすべての値は、javaResultTypeで表すことができます。
  • resultTypeuintuint2、または uint[15] の場合、javaResultType は longLong2、または long[] です。resultTypeのすべての値は、javaResultTypeで表すことができます。
  • resultTypeulongulong2、または ulong[15] の場合、javaResultType は longLong2、または 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 型です。次に例を示します。

  • inXTypeint の場合、devecSiInXType は int です。
  • inXTypeint2 の場合、devecSiInXType は int です。この配列はフラット化された状態で表示されます。割り当てが 2 要素で構成されるベクトル要素を持つことから、スカラー要素の 2 倍の数となります。これは、Allocation 処理の copyFrom() メソッドと同じです。
  • inXTypeuint の場合、deviceSiInXType は int です。Java 配列の符号付き値は、割り当て内の同じビットパターンの符号なし値として解釈されます。これは、Allocation 処理の copyFrom() メソッドと同じです。
  • inXTypeuint2 の場合、deviceSiInXType は int です。これは int2uint を処理する方法を組み合わせたものです。配列はフラット化された表現で、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];
    }
    

その他のコードサンプル

BasicRenderScriptRenderScriptIntrinsicHello Compute のサンプルでは、このページで扱った API の使用方法をさらに詳しく説明しています。