Migrate from RenderScript

RenderScript APIs are deprecated starting in Android 12. They will continue to function, but we expect that device and component manufacturers will stop providing hardware acceleration support over time. To take full advantage of GPU acceleration, we recommend migrating your scripts to Vulkan or using our replacement toolkit to migrate away from intrinsics.

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

To take full advantage of GPU acceleration, we recommend migrating RenderScript scripts to the cross-platform Vulkan API. Although your scripts will continue to run even if you don't migrate, they may execute on the CPU instead of the GPU depending on the availability of drivers.

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 9 (API level 28) and lower, and Vulkan for Android 10 (API level 29) and higher.

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.