RenderScript

RenderScript is a framework for running computationally intensive tasks at high performance on Android. RenderScript is primarily oriented for use with data-parallel computation, although serial computationally intensive workloads can benefit as well. The RenderScript runtime will parallelize work across all processors available on a device, such as multi-core CPUs, GPUs, or DSPs, allowing you to focus on expressing algorithms rather than scheduling work or load balancing. RenderScript is especially useful for applications performing image processing, computational photography, or computer vision.

To begin with RenderScript, there are two main concepts you should understand:

  • High-performance compute kernels are written in a C99-derived language.
  • A Java API is used for managing the lifetime of RenderScript resources and controlling kernel execution.

Writing a RenderScript Kernel

A RenderScript kernel typically resides in a .rs file in the <project_root>/src/ directory; each .rs file is called a script. Every script contains its own set of kernels, functions, and variables. A script can contain:

  • A pragma declaration (#pragma version(1)) that declares the version of the RenderScript kernel language used in this script. Currently, 1 is the only valid value.
  • A pragma declaration (#pragma rs java_package_name(com.example.app)) that declares the package name of the Java classes reflected from this script. Note that your .rs file must be part of your application package, and not in a library project.
  • Some number of invokable functions. An invokable function is a single-threaded RenderScript function that you can call from your Java code with arbitrary arguments. These are often useful for initial setup or serial computations within a larger processing pipeline.
  • Some number of script globals. A script global is equivalent to a global variable in C. You can access script globals from Java code, and these are often used for parameter passing to RenderScript kernels.
  • Some number of compute kernels. A kernel is a parallel function that executes across every Element within an Allocation.

    A simple kernel may look like the following:

    uchar4 __attribute__((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;
    }

    In most respects, this is identical to a standard C function. The first notable feature is the __attribute__((kernel)) applied to the function prototype. This denotes that the function is a RenderScript kernel instead of an invokable function. The next feature is the in argument and its type. In a RenderScript kernel, this is a special argument that is automatically filled in based on the input Allocation passed to the kernel launch. By default, the kernel is run across an entire Allocation, with one execution of the kernel body per Element in the Allocation. The third notable feature is the return type of the kernel. The value returned from the kernel is automatically written to the appropriate location in the output Allocation. The RenderScript runtime checks to ensure that the Element types of the input and output Allocations match the kernel's prototype; if they do not match, an exception is thrown.

    A kernel may have an input Allocation, an output Allocation, or both. A kernel may not have more than one input or one output Allocation. If more than one input or output is required, those objects should be bound to rs_allocation script globals and accessed from a kernel or invokable function via rsGetElementAt_type() or rsSetElementAt_type().

    A kernel may access the coordinates of the current execution using the x, y, and z arguments. These arguments are optional, but the type of the coordinate arguments must be uint32_t.

  • An optional init() function. An init() function is a special type of invokable function that is run when the script is first instantiated. This allows for some computation to occur automatically at script creation.
  • Some number of static script globals and functions. A static script global is equivalent to a script global except that it cannot be set from Java code. A static function is a standard C function that can be called from any kernel or invokable function in the script but is not exposed to the Java API. If a script global or function does not need to be called from Java code, it is highly recommended that those be declared static.

Setting floating point precision

You can control the required level of floating point precision in a script. This is useful if full IEEE 754-2008 standard (used by default) is not required. The following pragmas can set a different level of floating point precision:

  • #pragma rs_fp_full (default if nothing is specified): For apps that require floating point precision as outlined by the IEEE 754-2008 standard.
  • #pragma rs_fp_relaxed - For apps that don’t require strict IEEE 754-2008 compliance and can tolerate less precision. This mode enables flush-to-zero for denorms and round-towards-zero.
  • #pragma rs_fp_imprecise - For apps that don’t have stringent precision requirements. This mode enables everything in rs_fp_relaxed along with the following:
    • Operations resulting in -0.0 can return +0.0 instead.
    • Operations on INF and NAN are undefined.

Most applications can use rs_fp_relaxed without any side effects. This may be very beneficial on some architectures due to additional optimizations only available with relaxed precision (such as SIMD CPU instructions).

Accessing RenderScript APIs

When developing an Android application that uses RenderScript, you can access its API in one of two ways:

We strongly recommend using the Support Library APIs for accessing RenderScript because they provide a wider range of device compatibility. Developers targeting specific versions of Android can use android.renderscript if necessary.

Using the RenderScript Support Library APIs

In order to use the Support Library RenderScript APIs, you must configure your development environment to be able to access them. The following Android SDK tools are required for using these APIs:

  • Android SDK Tools revision 22.2 or higher
  • Android SDK Build-tools revision 18.1.0 or higher

You can check and update the installed version of these tools in the Android SDK Manager.

Note: Use of Support Library RenderScript APIs is not currently supported with Android Studio or Gradle-based builds.

To use the Support Library RenderScript APIs in Eclipse:

  1. Make sure you have the required Android SDK version and Build Tools version installed.
  2. Open the project.properties file in the root folder of your application project.
  3. Add the following lines to the file:
    renderscript.target=18
    renderscript.support.mode=true
    sdk.buildtools=18.1.0
    
  4. In your application classes that use RenderScript, add an import for the Support Library classes:
    import android.support.v8.renderscript.*;
    

The project.properties settings listed above control specific behavior in the Android build process:

  • renderscript.target - Specifies the bytecode version to be generated. We recommend you set this value the highest available API level and set renderscript.support.mode to true. Valid values for this setting are any integer value from 11 to the most recently released API level. If your minimum SDK version specified in your application manifest is set to a higher value, this value is ignored and the target value is set to the minimum SDK version.
  • renderscript.support.mode - Specifies that the generated bytecode should fall back to a compatible version if the device it is running on does not support the target version.
  • sdk.buildtools - The version of the Android SDK build tools to use. This value should be set to 18.1.0 or higher. If this option is not specified, the highest installed build tools version is used. You should always set this value to ensure the consistency of builds across development machines with different configurations.

Using RenderScript from Java Code

Using RenderScript from Java code relies on the API classes located in the android.renderscript or the android.support.v8.renderscript package. Most applications follow the same basic usage patterns:

  1. Initialize a RenderScript context. The RenderScript context, created with create(Context), ensures that RenderScript can be used and provides an object to control the lifetime of all subsequent RenderScript objects. You should consider context creation to be a potentially long-running operation, since it may create resources on different pieces of hardware; it should not be in an application's critical path if at all possible. Typically, an application will have only a single RenderScript context at a time.
  2. Create at least one Allocation to be passed to a script. An Allocation is a RenderScript object that provides storage for a fixed amount of data. Kernels in scripts take Allocation objects as their input and output, and Allocation objects can be accessed in kernels using rsGetElementAt_type() and rsSetElementAt_type() when bound as script globals. Allocation objects allow arrays to be passed from Java code to RenderScript code and vice-versa. Allocation objects are typically created using createTyped(RenderScript, Type) or createFromBitmap(RenderScript, Bitmap).
  3. Create whatever scripts are necessary. There are two types of scripts available to you when using RenderScript:
    • ScriptC: These are the user-defined scripts as described in Writing a RenderScript Kernel above. Every script has a Java class reflected by the RenderScript compiler in order to make it easy to access the script from Java code; this class will have the name ScriptC_filename. For example, if the kernel above was located in invert.rs and a RenderScript context was already located in mRS, the Java code to instantiate the script would be:
      ScriptC_invert invert = new ScriptC_invert(mRenderScript);
    • ScriptIntrinsic: These are built-in RenderScript kernels for common operations, such as Gaussian blur, convolution, and image blending. For more information, see the subclasses of ScriptIntrinsic.
  4. Populate Allocations with data. Except for Allocations created with android.renderscript, an Allocation will be populated with empty data when it is first created. To populate an Allocation, use one of the copy methods in Allocation.
  5. Set any necessary script globals. Globals may be set using methods in the same ScriptC_filename class with methods named set_globalname. For example, in order to set an int named elements, use the Java method set_elements(int). RenderScript objects can also be set in kernels; for example, the rs_allocation variable named lookup can be set with the method set_lookup(Allocation).
  6. Launch the appropriate kernels. Methods to launch a given kernel will be reflected in the same ScriptC_filename class with methods named forEach_kernelname(). These launches are asynchronous, and launches will be serialized in the order in which they are launched. Depending on the arguments to the kernel, the method will take either one or two Allocations. By default, a kernel will execute over the entire input or output Allocation; to execute over a subset of that Allocation, pass an appropriate Script.LaunchOptions as the last argument to the forEach method.

    Invoked functions can be launched using the invoke_functionname methods reflected in the same ScriptC_filename class.

  7. Copy data out of Allocation objects. In order to access data from an Allocation from Java code, that data must be copied back to Java buffers using one of the copy methods in Allocation. These functions will synchronize with asynchronous kernel and function launches as necessary.
  8. Tear down the RenderScript context. The RenderScript context can be destroyed with destroy() or by allowing the RenderScript context object to be garbage collected. This will cause any further use of any object belonging to that context to throw an exception.