Migrate from RenderScript

RenderScript APIs are deprecated starting in Android 12. They will continue to function for a while, but device and component manufacturers have already stopped providing hardware acceleration support, and RenderScript support will be removed entirely in a future release.

Many users will find C/C++ performance adequate for their uses, and if you were only reliant on RenderScript for intrinsics, you can replace those uses with the RenderScript Intrinsics Replacement Toolkit, which is easier to use and comes with a potential 2x performance improvement!

If you actually need to take full advantage of GPU acceleration, we recommend migrating your scripts to Vulkan.

Following the deprecation of RenderScript in the Android platform, we are also removing support for RenderScript in the Android Gradle plugin. Starting with Android Gradle plugin 7.2, the RenderScript APIs are deprecated. They will continue to function, but will invoke warnings, and will be completely removed in future versions of AGP. This guide explains how to migrate from RenderScript.

Migrate from intrinsics

Although the RenderScript intrinsics functions will continue to function after RenderScript deprecation, they may execute only on the CPU rather than the GPU.

If your application uses intrinsics, you can use the standalone replacement library; our tests indicate it's faster than using the existing RenderScript CPU implementation.

The toolkit includes the following functions:

  • Blend
  • Blur
  • Color matrix
  • Convolve
  • Histogram and histogramDot
  • Lookup table (LUT) and LUT 3D
  • Resize
  • YUV to RGB

For full details and limitations, see the toolkit's README.md and Toolkit.kt. files.

Perform the following steps to download, add, and use the library:

  1. Download the project from GitHub.

  2. Locate and build the renderscript-toolkit module.

  3. Add the library to your Android Studio project by modifying the app's build.gradle file.

  4. Invoke the appropriate method of the toolkit.

Example: migrate from the ScriptIntrinsicBlur function

To replace the ScriptIntrinsicBlur function:

  • To blur a bitmap, call Toolkit.blur.

    var blurredBitmap = Toolkit.blur(myBitmap, radius)
    
  • If you want to blur an image represented by an array of bytes, specify the width, height, and the number of bytes per pixel.

    val outArray = Toolkit.blur(inputArray, bytesPerPixel, width, height, radius)
    

When targeting Android 12 (API level 31) and higher, consider using the RenderEffect class instead of Toolkit.blur().

Migrate from scripts

If your use case cannot be solved with the RenderScript Intrinsics Replacement Toolkit (which offers a potential 2x performance improvement over RenderScript), we recommend migrating RenderScript scripts to the cross-platform Vulkan API. You may find this unnecessary because on most devices your scripts are already running on the CPU instead of the GPU anyway: C/C++ may be faster than RenderScript or Vulkan compute for some use cases, and may be fast enough even if Vulkan or RenderScript can do better.

To better understand how to migrate functionality, review the sample app. The sample demonstrates how to both blur a bitmap and do a color-matrix conversion in RenderScript, and has equivalent code in Vulkan.

If your application needs to support a range of releases, use RenderScript for devices running Android 6 (API level 23) and lower, and Vulkan for Android 7 (API level 24) and higher. If your minSdkVersion is 24 or higher, you do not need to use RenderScript; Vulkan can be used on every device you support.

Vulkan doesn’t provide Kotlin or Java APIs, so there is no direct mapping from RenderScript to Vulkan. You'll need to write your Vulkan code using the NDK and create JNI functions to access this code from Kotlin or Java.

The following sections cover aspects of migrating from RenderScript. The sample app implements almost all of these considerations. To better understand them, compare the RenderScript and Vulkan equivalent code.

Initialization

Instead of creating a RenderScript context object in Kotlin or Java, perform the following steps to create a Vulkan context with the NDK.

  1. Create a Vulkan instance.

  2. Choose a Vulkan physical device that supports a compute queue.

  3. Create a Vulkan logical device, and get the compute queue.

Optionally, you can set up the Vulkan validation layers on Android to speed up your Vulkan application development.

The sample app demonstrates how to initialize the Vulkan context in VulkanContext.h. To learn more, see the Initialization and Devices and Queues sections of the Vulkan specification.

Allocations

You can migrate a RenderScript Allocation to a Vulkan storage image or a Vulkan storage buffer. For better performance with read-only images, use a sampled image with fetch operations, either as a combined image sampler, or with distinct sampler and sampled image bindings.

The Vulkan resources are allocated within Vulkan. To avoid memory copying overhead when interacting with other Android components, consider using the VK_ANDROID_external_memory_android_hardware_buffer extension to import an Android AHardwareBuffer into Vulkan. This extension is available on all Android devices supporting Vulkan 1.1. For more information, see FEATURE_VULKAN_HARDWARE_VERSION.

The sample app demonstrates how to create Vulkan resources in VulkanResources.h. To learn more, see the resource creation and resource descriptors sections of the Vulkan specification.

Scripts

Your RenderScript scripts must be converted to Vulkan compute shaders. You may also need to adapt your code depending on the use of RenderScript globals.

Write a Vulkan compute shader

A Vulkan compute shader is commonly written in OpenGL Shading Language (GLSL) and then compiled to the Standard Portable Intermediate Representation-V (SPIR-V) format.

For detailed information and instructions for integrating shaders into your app, see Vulkan shader compilers on Android.

Adaptation of script globals

Based on the characteristics of the script globals, we recommend using specialization constants, push constants, or uniform buffer objects for globals that are not modified within the shader:

  • Specialization constants: Recommended for script globals that are mostly consistent across kernel invocations. Changing the value of specialization constants will need to recreate the compute pipeline.
  • Push constants: Recommended for frequently-changed script globals of sizes smaller than maxPushConstantsSize (guaranteed minimum: 128 bytes).
  • Uniform buffer: Recommended for frequently-changed script globals of sizes larger than the push constant limit.

For globals that are changed within the shader, you can use either the Vulkan storage image or the Vulkan storage buffer.

Computations

You'll need to create a Vulkan compute pipeline in order to have the GPU execute your compute shader.

Create a Vulkan compute pipeline

The ComputePipeline.h file in the sample app demonstrates how to create the Vulkan compute pipeline.

To use a compiled SPIR-V shader in Vulkan, construct a Vulkan compute pipeline as follows:

  1. Create a shader module with the compiled SPIR-V shader.
  2. Create a descriptor set layout specifying the resource bindings (see Allocations for more details).
  3. Create a descriptor set from the descriptor set layout.
  4. Create a pipeline layout from the descriptor set layout.
  5. Create a compute pipeline with the shader module and pipeline layout.

For more information, see the Compute Pipelines section in the Vulkan specification.

Start a computation

To start the computation with a compute pipeline:

  1. Update the descriptor set with the Vulkan resources.
  2. Create a Vulkan command buffer, and record the following commands:
    1. Bind the pipeline and the descriptor set.
    2. Dispatch compute workgroups.
  3. Submit the command buffer to the compute queue.
  4. Wait on the queue, or optionally return a sync fence.

To chain multiple kernels together (for example, to migrate codes using ScriptGroup), record them in a single command buffer and synchronize with memory barriers.

The sample app demonstrates two compute tasks:

  • HUE rotation: A simple compute task with a single compute shader. See ImageProcessor::rotateHue for the code sample.
  • Blur: A more complex compute task that sequentially executes two compute shaders. See ImageProcessor::blur for the code sample.

To learn more about command buffers or memory barriers, refer to the sections in the Vulkan specification called Command Buffers and Memory Barriers.