RenderScript Support for All

Dave Smith
Dave Smith
RenderScript Support for All

I really like RenderScript. I love what it represents—that a schmuck like me can write performance-critical code that is architecture independent. I was even more enthralled when script intrinsics were introduced in Android 4.2, exponentially adding to what I could accomplish while exponentially decreasing the code I needed to get there. However, like every Android developer, I felt tethered by the fact that I wouldn’t get to access it until we could safely increase our minimum platform version.

So, I was pretty excited when the Android team announced recently that RenderScript, the framework for writing high performance computation code without the NDK, is now unbundled from the framework and can be built into applications running on devices with Android 2.2 or later. Even more impressive is that the unbundled version allows some of those more recent enhancements, like script intrinsics, to also run on these older devices.

Currently, the integration of this framework into the build tools is a bit of a work in progress. There is no official support yet for using the RenderScript Support Package inside of Android Studio or Gradle. The integration with Eclipse also requires a little bit of additional work. The only workflow that functions "out of the box" at the moment is running command line builds with Ant.

...devices running Android 4.2 and earlier will always run their RenderScript applications on the CPU, while devices running Android 4.3 or later will run their RenderScript applications on whatever processors are available on that particular device.

This quote comes from the announcement blog post. Essentially, the Android team has created pre-compiled NDK libraries that will get bundled into your application’s APK to execute RenderScript functionality on devices where it is not natively supported. If that same code is running on Android 4.3 or later, the code will be executed using the native framework on the device instead; this includes the acceleration that comes from executing work on all possible CPU/GPU cores.

Basic Setup

Echoing the steps provided in the Android documentation, the first necessary step is to download at least v22.2 of the Platform Tools and ADT, plus v18.1 of the Build Tools from the SDK Manager.

From there, modify project.properties to indicate to the build tools the specific version to use and that RenderScript support should be enabled. Adding these triggers allows the build tools to know that they need to include the support library components, both Java (.jar/.dex) and native (.so) into your APK when it is being packaged.

renderscript.target=18
renderscript.support.mode=true
sdk.buildtools=18.1.0

UPDATE: The sdk.buildtools attribute is no longer necessary with PlatformTools and ADT v22.3 and later. However, if you are still having build issues you can include it with no harm, as long as it points to a valid tools version you have installed on your host machine.

SIDE NOTE: The pre-compiled libs they copy are located at <SDK HOME>/build-tools/18.1.0/renderscript/lib/packaged/, organized by ABI just as any NDK library would be. It is interesting to note that, at the moment, the armeabi or ARMv5 architecture is not included, only ARMv7, x86, and MIPS.

Next, make sure that you are importing the Java classes from the support package rather than the native frameworks:

import android.support.v8.renderscript.*;

At this point, you’ve done what you need to build RenderScript support properly using Ant on the command line. If you are a build server admin, you’re probably happy with this. If you’re a developer, read on.

Android Studio Setup

IMPORTANT: Let me reiterate that this is not for a Gradle project. I am talking here only about a project using the older build system that you just happen to be working on inside of Android Studio. RenderScript Support Mode is supported in the Android Gradle Plugin 0.7.0 and later without any special IDE configuration. For more information on how to set up RS Support in a Gradle project, have a look at the rsSupportMode Gradle Sample.

First we need to add a reference to the RenderScript support JAR, as before. A reminder if you skipped the last section that the path to the JAR is

<SDK HOME>/build-tools/18.1.0/renderscript/lib/renderscript-v8.jar

From the project structure screen, select the project Module, then the Dependencies tab. At the bottom, click the plus icon to add a new dependency on a JAR file.

Select the JAR file at the path described above to add it. You should now see it in the dependencies list. Make sure its scope is set to "Compile". You do not need to have this dependency exported in this case.

The new trick here is that the make-based builds in IntelliJ/Android Studio have never really parsed the project.properties file, and anything you wanted to include in your build (such as running ProGuard) had to be explicitly set in the IDE Project Properties. But, there is no location in the IDE to tell the build system about these new RenderScript flags we included. So, some additional hackery is necessary for now.

The last step is to set the native libs path of your module to point to the pre-compiled NDK directory of the new build tools. Select the "Android" facet underneath your project module, and select the "Structure" tab. Set the "Native libs directory" path to the following:

<SDK HOME>/build-tools/18.1.0/renderscript/lib/packaged

If you apply those changes, references to RenderScript support in your project should not resolve and the resulting project should build.

Basic Example

As a quick overview, I’ve whipped up an example application that uses RenderScript to filter image data. To date, this is probably the most common use case for RenderScript, and intrinsics make doing this work simple. What follows is a sample application that shows three images – one original and two others filtered using RenderScript.

First, the Activity layout...

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <ImageView
        android:id="@+id/image_normal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:scaleType="center"/>
    <ImageView
        android:id="@+id/image_blurred"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:scaleType="center"/>
    <ImageView
        android:id="@+id/image_colored"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:scaleType="center"/>
</LinearLayout>

Next, the Activity code...

//Make sure to use this import, among your others
import android.support.v8.renderscript.*;

public class MainActivity extends Activity {

    private ImageView mNormalImage, mBlurImage, mColorImage;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mNormalImage = (ImageView) findViewById(R.id.image_normal);
        mBlurImage = (ImageView) findViewById(R.id.image_blurred);
        mColorImage = (ImageView) findViewById(R.id.image_colored);

        //Start with an image from our APK resources
        Bitmap inBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.dog);
        Bitmap outBitmap = inBitmap.copy(inBitmap.getConfig(), true);
        Bitmap grayBitmap = inBitmap.copy(inBitmap.getConfig(), true);

        //Create the context and I/O allocations
        final RenderScript rs = RenderScript.create(this);
        final Allocation input = Allocation.createFromBitmap(rs, inBitmap,
                Allocation.MipmapControl.MIPMAP_NONE,
                Allocation.USAGE_SCRIPT);
        final Allocation output = Allocation.createTyped(rs, input.getType());

        //Blur the image
        final ScriptIntrinsicBlur script =
                ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
        script.setRadius(4f);
        script.setInput(input);
        script.forEach(output);
        output.copyTo(outBitmap);

        //Make the image greyscale
        final ScriptIntrinsicColorMatrix scriptColor =
                ScriptIntrinsicColorMatrix.create(rs, Element.U8_4(rs));
        scriptColor.setGreyscale();
        scriptColor.forEach(input, output);
        output.copyTo(grayBitmap);

        //Show the results
        mNormalImage.setImageBitmap(inBitmap);
        mBlurImage.setImageBitmap(outBitmap);
        mColorImage.setImageBitmap(grayBitmap);

        //We don't need RenderScript anymore
        rs.destroy();

    }
}

RenderScript works with this concept of an Allocation, which is just a memory mapped region to bring data in and out of the script. There are numerous convenience methods to create these from everyday objects, and here we are using one to create an Allocation for our Bitmap instances. The magic happens when forEach() is called, which applies everything in the script over the set of data inside the Allocation. You can see that all it takes to manipulate the image data is to create the appropriate script intrinsic instance and apply it to the image. In addition, you will find intrinsics for adding convolution matrix filters, which you can use to create effects like lighten, darken, or edge detection (just to name a few).

I have not provided the full example project code here, because it will be available in the upcoming 3rd Edition of Android Recipes.

As a final note, in both cases we are doing some hackery to the IDE configuration to get this support to build in the development environment. I fully expect that soon updates to the SDK Tools will render all this unnecessary, but, if you want to play with this stuff in the mean time, I hope you’ve found it helpful.