For workloads where GPU compute is ideal, migrating RenderScript scripts to Vulkan compute gives your app more direct control over GPU hardware, potentially unlocking additional performance compared to other APIs.
A high-level overview follows to help you use Vulkan compute shaders to replace RenderScript scripts.
Vulkan Initialization
Instead of creating a RenderScript context object in Kotlin or Java, perform the following steps to create a Vulkan context using the NDK.
Create a Vulkan instance.
Choose a Vulkan physical device that supports a compute queue.
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.
Vulkan Allocations
A RenderScript Allocation can be migrated 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.
Conversion to Vulkan compute shaders
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 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:
- Create a shader module with the compiled SPIR-V shader.
- Create a descriptor set layout specifying the resource bindings (see Allocations for more details).
- Create a descriptor set from the descriptor set layout.
- Create a pipeline layout from the descriptor set layout.
- 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:
- Update the descriptor set with the Vulkan resources.
- Create a Vulkan command buffer, and record the following commands:
- Bind the pipeline and the descriptor set.
- Dispatch compute workgroups.
- Submit the command buffer to the compute queue.
- 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.