从 Android 12 开始,RenderScript API 已被弃用。设备和组件制造商已停止提供硬件加速支持,预计在未来版本中将完全移除对 RenderScript 的支持。
对于许多用例,C/C++ 的性能可能已足够。如果您仅依赖 RenderScript 的内部函数 (intrinsics),可以使用RenderScript 内部函数替换工具包来替代它们,该工具包更易于使用,并可能带来 2 倍的性能提升!
如果您需要充分利用 GPU 加速,我们建议将脚本迁移到 Vulkan。其他加速选项包括将脚本迁移到 OpenGL,使用基于 Canvas 的图像操作,或利用Android Graphics Shading Language (AGSL)。
在 Android 平台中弃用 RenderScript 后,Android Gradle 插件也正在移除对 RenderScript 的支持。从 Android Gradle 插件 7.2 开始,RenderScript API 已被弃用。它们仍然可以使用,但会触发警告。未来的 AGP 版本将不再包含 RenderScript 支持。本指南将解释如何从 RenderScript 迁移。
从内部函数迁移
尽管 RenderScript 内部函数在 RenderScript 弃用后仍然可用,但它们可能只在 CPU 上执行,而不是在 GPU 上执行。
对于其中一些操作,平台或 Jetpack 库中现在内置了更高效的选项。
内置加速图像操作
Android 平台支持可应用于图像的加速图像处理操作,独立于 RenderScript 内部函数。示例包括:
- 混合
- 模糊
- 颜色矩阵
- 调整大小
在 Android 12 及更高版本上将图像模糊效果应用于 View
Android 12(API 级别 31)中添加了支持模糊的RenderEffect
,允许您模糊 RenderNode
。RenderNode
是 Android 用于加速平台图形的显示列表结构的一部分。
Android 提供了一种将效果应用于与 View
关联的 RenderNode
的快捷方式。要模糊 View
,请调用 View.setRenderEffect()
val blurRenderEffect = RenderEffect.createBlurEffect(radius, radius,
Shader.TileMode.MIRROR
)
view.setRenderEffect(blurRenderEffect)
在 Android 12 及更高版本上将图像模糊效果渲染到 Bitmap
如果需要将模糊后的图像渲染到 Bitmap
中,框架支持使用以 HardwareBuffer
为后端的 HardwareRenderer
进行加速渲染。以下代码创建用于模糊的 HardwareRenderer
、RenderNode
和 RenderEffect
val imageReader = ImageReader.newInstance(
bitmap.width, bitmap.height,
PixelFormat.RGBA_8888, numberOfOutputImages,
HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE or HardwareBuffer.USAGE_GPU_COLOR_OUTPUT
)
val renderNode = RenderNode("BlurEffect")
val hardwareRenderer = HardwareRenderer()
hardwareRenderer.setSurface(imageReader.surface)
hardwareRenderer.setContentRoot(renderNode)
renderNode.setPosition(0, 0, imageReader.width, imageReader.height)
val blurRenderEffect = RenderEffect.createBlurEffect(
radius, radius,
Shader.TileMode.MIRROR
)
renderNode.setRenderEffect(blurRenderEffect)
应用效果需要使用 RenderNode
内部的 RecordingCanvas
。以下代码记录绘制内容,创建渲染请求,然后等待请求完成
val renderCanvas = it.renderNode.beginRecording()
renderCanvas.drawBitmap(it.bitmap, 0f, 0f, null)
renderNode.endRecording()
hardwareRenderer.createRenderRequest()
.setWaitForPresent(true)
.syncAndDraw()
渲染后的图像位于与 ImageReader
相关联的 HardwareBuffer
中。以下代码获取 Image
并返回一个包装其 HardwareBuffer
的 Bitmap
。
val image = imageReader.acquireNextImage() ?: throw RuntimeException("No Image")
val hardwareBuffer = image.hardwareBuffer ?: throw RuntimeException("No HardwareBuffer")
val bitmap = Bitmap.wrapHardwareBuffer(hardwareBuffer, null)
?: throw RuntimeException("Create Bitmap Failed")
以下代码在渲染图像后执行清理。请注意,ImageReader
、RenderNode
、RenderEffect
和 HardwareRenderer
可用于处理多个图像。
hardwareBuffer.close()
image.close()
imageReader.close()
renderNode.discardDisplayList()
hardwareRenderer.destroy()
AGSL 用于图像处理
Android 13 及更高版本使用Android Graphics Shading Language (AGSL) 来定义可编程的 RuntimeShader
对象的行为。AGSL 与 GLSL 片元着色器共享大部分语法,但在 Android 图形渲染系统中工作,用于自定义 Canvas
内的绘制以及过滤 View
内容。这可用于在绘制操作期间添加自定义图像处理,或通过直接使用 RenderNode
将图像渲染到 Bitmap
canvas 中。以下示例演示了如何应用自定义着色器来替换图像模糊效果。
首先创建一个 RuntimeShader
,并使用 AGSL 着色器代码实例化它。该着色器用于应用颜色矩阵以进行色相旋转
val hueShader = RuntimeShader("""
uniform float2 iResolution; // Viewport resolution (pixels)
uniform float2 iImageResolution; // iImage1 resolution (pixels)
uniform float iRadian; // radian to rotate things around
uniform shader iImage1; // An input image
half4 main(float2 fragCoord) {
float cosR = cos(iRadian);
float sinR = sin(iRadian);
mat4 hueRotation =
mat4 (
0.299 + 0.701 * cosR + 0.168 * sinR, //0
0.587 - 0.587 * cosR + 0.330 * sinR, //1
0.114 - 0.114 * cosR - 0.497 * sinR, //2
0.0, //3
0.299 - 0.299 * cosR - 0.328 * sinR, //4
0.587 + 0.413 * cosR + 0.035 * sinR, //5
0.114 - 0.114 * cosR + 0.292 * sinR, //6
0.0, //7
0.299 - 0.300 * cosR + 1.25 * sinR, //8
0.587 - 0.588 * cosR - 1.05 * sinR, //9
0.114 + 0.886 * cosR - 0.203 * sinR, //10
0.0, //11
0.0, 0.0, 0.0, 1.0 ); //12,13,14,15
float2 scale = iImageResolution.xy / iResolution.xy;
return iImage1.eval(fragCoord * scale)*hueRotation;
}
""")
该着色器可以像其他任何 RenderEffect
一样应用于 RenderNode
。以下示例演示了如何在 hueShader 中设置 uniforms
hueShader.setFloatUniform("iImageResolution", bitmap.width.toFloat(),
bitmap.height.toFloat())
hueShader.setFloatUniform("iResolution", bitmap.width.toFloat(),
bitmap.height.toFloat())
hueShader.setFloatUniform("iRadian", radian)
hueShader.setInputShader( "iImage1", BitmapShader(bitmap, Shader.TileMode.MIRROR,
Shader.TileMode.MIRROR))
val colorFilterEffect = RenderEffect.createShaderEffect(it.hueShader)
renderNode.setRenderEffect(colorFilterEffect)
要获取 Bitmap
,使用的方法与前面的图像模糊示例相同。
RenderNode
内部的RecordingCanvas
应用着色器。- 获取
Image
,返回一个包装其HardwareBuffer
的Bitmap
。
使用 CameraX 将平面 YUV 转换为 RGB
Jetpack CameraX 中的 ImageAnalysis 用例支持将 平面 YUV 转换为 RGB 用于图像处理。
有关使用 ImageAnalysis
的资源,可参考 CameraX 入门 codelab 以及 Android 相机 示例 代码库。
Renderscript 内部函数替换工具包
如果您的应用使用内部函数,您可以使用独立的替换库;我们的测试表明它比使用现有的 RenderScript CPU 实现更快。
该工具包包含以下函数:
- 混合
- 模糊
- 颜色矩阵
- 卷积
- 直方图和 histogramDot
- 查找表 (LUT) 和 LUT 3D
- 调整大小
- YUV 到 RGB
有关完整详情和限制,请参阅工具包的 README.md
和 Toolkit.kt
文件。
执行以下步骤以下载、添加和使用该库:
从 GitHub 下载项目。
找到并构建
renderscript-toolkit module
。通过修改应用的
build.gradle
文件将该库添加到您的 Android Studio 项目中。调用工具包中的相应方法。
示例:从 ScriptIntrinsicBlur 函数迁移
要替换 ScriptIntrinsicBlur
函数
要模糊位图,请调用
Toolkit.blur
。var blurredBitmap = Toolkit.blur(myBitmap, radius)
如果您想模糊由字节数组表示的图像,请指定宽度、高度以及每像素的字节数。
val outArray = Toolkit.blur(inputArray, bytesPerPixel, width, height, radius)
从脚本迁移
如果您的用例无法通过以下方式解决:
- RenderScript 内部函数替换工具包
- Android 平台中的新 API,例如
RenderEffect
和AGSL
- Android Jetpack 库 API,例如
CameraX
并且您的用例可以受益于 GPU 加速,Android 支持跨平台 Vulkan 和 OpenGL ES (GLES) API 上的 GPU 计算。您可能会发现这并非必要,因为在大多数设备上,您的脚本已经在 CPU 上运行,而不是在 GPU 上:对于某些用例,C/C++ 可能比 RenderScript、GLES 或 Vulkan 计算更快。(或者至少对于您的用例来说足够快)
为了更好地了解如何迁移,请查看示例应用。该示例演示了如何在 RenderScript 中模糊位图和进行颜色矩阵转换,并提供了等效的 Vulkan 和 OpenGL 代码。
如果您的应用需要支持一系列版本,则对于运行 Android 6(API 级别 23)及更低版本的设备,请使用 RenderScript;对于支持 Android 7(API 级别 24)及更高版本的设备,请使用 Vulkan 或 GLES。如果您的 minSdkVersion
是 24 或更高,您可能不需要使用 RenderScript;只要需要 GPU 计算支持,就可以使用 Vulkan 或 GLES 3.1。
Android 为 GLES API 提供了 SDK 绑定,因此在使用 OpenGL ES 时无需使用 NDK。
Vulkan 不提供 SDK 绑定,因此 RenderScript 到 Vulkan 没有直接的映射;您需要使用 NDK 编写 Vulkan 代码,并创建 JNI 函数以便从 Kotlin 或 Java 访问这些代码。
以下页面涵盖了从 RenderScript 迁移的各个方面。示例应用实现了几乎所有这些注意事项。为了更好地理解它们,请比较 RenderScript 和 Vulkan 的等效代码。