从 RenderScript 迁移

从 Android 12 开始,RenderScript API 已被废弃。设备和组件制造商已停止提供硬件加速支持,预计将在未来的版本中完全取消对 RenderScript 的支持。

C/C++ 性能可能足以满足许多用例的需要,如果您仅将 RenderScript 用于内建函数,可使用 RenderScript 内建函数替换工具包来替代。后者更易于使用,并且有可能将性能提高 2 倍!

如果您需要充分利用 GPU 加速功能,我们建议您将脚本迁移到 Vulkan,其他加速选项包括将脚本迁移到 OpenGL,方法是使用基于画布的图片操作,或利用 Android 图形着色语言 (AGSL)

在 Android 平台中废弃 RenderScript 后,将在 Android Gradle 插件中取消对 RenderScript 的支持。从 Android Gradle 插件 7.2 开始,废弃了 RenderScript API。它们会继续正常运行,但会产生警告。未来的 AGP 版本将不再支持 RenderScript。本指南介绍了如何从 RenderScript 迁移。

从内建函数迁移

尽管 RenderScript 内建函数在 RenderScript 废弃后会继续正常运行,但它们只能在 CPU(而非 GPU)上执行。

对于其中一些操作,平台或 Jetpack 库中现在内置了更高效的选项。

内置加速图片操作

Android 平台支持可应用于图片的加速图片操作,不依赖于 RenderScript 内建函数。例如:

  • 混合函数 (blend)
  • 模糊处理函数 (blur)
  • 颜色矩阵函数 (colorMatrix)
  • 大小调整函数 (resize)

将 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 进行加速渲染。以下代码会为模糊处理创建 HardwareRendererRenderNodeRenderEffect

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 并返回封装其 HardwareBufferBitmap

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")

以下代码会在渲染图片后进行清理。请注意,ImageReaderRenderNodeRenderEffectHardwareRenderer 可用于处理多张图片。

hardwareBuffer.close()
image.close()
imageReader.close()
renderNode.discardDisplayList()
hardwareRenderer.destroy()

用于图片处理的 AGSL

Android 13 及更高版本使用 Android 图形着色语言 (AGSL) 来定义可编程 RuntimeShader 对象的行为。AGSL 与 GLSL fragment 着色器共享大部分语法,但可在 Android 图形渲染系统中运行,用于自定义 Canvas 中的绘制和过滤 View 内容。这可用于在绘制操作期间添加自定义图片处理,或者直接使用 RenderNode 将图片渲染到 Bitmap 画布中。以下示例演示了如何应用自定义着色器来替换图片模糊处理效果。

首先创建一个 RuntimeShader,并使用 AGSL 着色器代码将其实例化。该着色器用于应用颜色矩阵进行 HUE 旋转:

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;
    }
""")

该着色器可以应用于 RenderNode,就像应用于任何其他 RenderEffect 一样。以下示例演示了如何在 hueShader 中设置 uniform:

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,并返回封装其 HardwareBufferBitmap

使用 CameraX 从平面 YUV 转换为 RGB

Jetpack 的 CameraX 中的 ImageAnalysis 用例支持将平面 YUV 转换为 RGB 以用于图片处理。

CameraX 使用入门 Codelab 和 Android 相机示例代码库中提供了关于如何使用 ImageAnalysis 的资源。

RenderScript 内建函数替换工具包

如果您的应用使用了内建函数,您就可以使用独立的替换库;我们的测试表明,其速度比使用现有 RenderScript CPU 实现更快。

该工具包包含以下函数:

  • 混合函数 (blend)
  • 模糊处理函数 (blur)
  • 颜色矩阵函数 (colorMatrix)
  • 卷积函数 (convolve)
  • 直方图函数 (histogram) 和 histogramDot
  • 对照表函数 (lut) 和 lut3d
  • 大小调整函数 (resize)
  • 将 YUV 转换为 RGB 的函数 (yuvToRgb)

如需了解完整详情和限制条件,请参阅该工具包的 README.mdToolkit.kt 文件。

如需下载、添加和使用该库,请执行以下步骤:

  1. 从 GitHub 下载相应项目

  2. 找到并构建 renderscript-toolkit module

  3. 通过修改应用的 build.gradle 文件,将该库添加到您的 Android Studio 项目中。

  4. 调用该工具包的相应方法。

示例:从 ScriptIntrinsicBlur 函数迁移

如需替换 ScriptIntrinsicBlur 函数,请执行以下操作:

  • 如需对位图进行模糊处理,请调用 Toolkit.blur

    var blurredBitmap = Toolkit.blur(myBitmap, radius)
    
  • 如果您需要对由字节数组表示的图片进行模糊处理,请指定宽度、高度和每像素字节数。

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

从脚本迁移

如果您的用例无法通过以下资源得以解决,请考虑从脚本迁移:

您的用例也可以受益于 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;Vulkan 或 GLES 3.1 可用于需要支持 GPU 计算的任何设备。

Android 为 GLES API 提供 SDK 绑定,因此使用 OpenGL ES 时无需使用 NDK。

Vulkan 不提供 SDK 绑定,因此不存在从 RenderScript 到 Vulkan 的直接映射;您可以使用 NDK 编写 Vulkan 代码并创建 JNI 函数,以便通过 Kotlin 或 Java 访问此代码。

以下几页介绍了从 RenderScript 迁移的各个方面。示例应用几乎涵盖了上述所有考虑因素。为了更好地理解这些因素,请对比以下 RenderScript 和 Vulkan 等效代码。