Cómo migrar desde RenderScript

Las APIs de RenderScript dejaron de estar disponibles a partir de Android 12. Los fabricantes de dispositivos y componentes ya no brindan compatibilidad con la aceleración de hardware, y la compatibilidad con RenderScript se eliminará por completo en una versión futura.

El rendimiento de C/C++ puede ser adecuado para muchos casos de uso y, si solo dependías de RenderScript para funciones intrínsecas, puedes reemplazar esos usos con el kit de herramientas de reemplazo de funciones intrínsecas de RenderScript, que es más fácil de usar y podría duplicar el rendimiento.

Si necesitas aprovechar al máximo la aceleración de GPU, te recomendamos que migres tus secuencias de comandos a Vulkan. Otras opciones aceleradas incluyen migrar tus secuencias de comandos a OpenGL, a través de Operaciones de imagen basadas en Canvas, o bien usar Android Graphics Shading Language (AGSL).

Después de que RenderScript deje de estar disponible en la plataforma de Android, se quitará la compatibilidad con RenderScript en el complemento de Android para Gradle. A partir del complemento de Android para Gradle 7.2, las APIs de RenderScript dejaron de estar disponibles. Seguirán funcionando, pero invocarán advertencias. Las versiones futuras de AGP ya no incluirán compatibilidad con Renderscript. En esta guía, se explica cómo migrar desde RenderScript.

Cómo migrar desde funciones intrínsecas

Aunque las funciones intrínsecas de RenderScript seguirán funcionando después de que RenderScript deje de estar disponible, es posible que solo se ejecuten en la CPU y no en la GPU.

Para algunas de estas operaciones, hay opciones más eficientes ahora integradas en la plataforma o en las bibliotecas de Jetpack.

Operaciones de imágenes aceleradas integradas

La plataforma de Android admite operaciones de procesamiento acelerado de imágenes que se pueden aplicar a imágenes, independientemente de las funciones intrínsecas de RenderScript. Los siguientes son algunos ejemplos:

  • Mezcla
  • Desenfoque
  • Matriz de color
  • Cambiar tamaño

Desenfoque de imágenes en Android 12 y versiones posteriores en un elemento View

Se agregó RenderEffect con compatibilidad para desenfoque en Android 12, nivel de API 31, lo que te permite desenfocar un RenderNode. El objeto RenderNode es una construcción de la lista de visualización que Android usa para ayudar a acelerar los gráficos de la plataforma.

Android proporciona un acceso directo para aplicar un efecto a la RenderNode asociada con una View. Para desenfocar un objeto View, llama a View.setRenderEffect() de la siguiente manera:

val blurRenderEffect = RenderEffect.createBlurEffect(radius, radius,
        Shader.TileMode.MIRROR
    )
view.setRenderEffect(blurRenderEffect)

Desenfoque de imágenes en Android 12 y versiones posteriores renderizadas en un mapa de bits

Si necesitas que la imagen desenfocada se renderice en unBitmap, el framework es compatible con el procesamiento acelerado con un HardwareRenderer respaldado por un HardwareBuffer. Con el siguiente código, se crean los objetos HardwareRenderer, RenderNode y RenderEffect para el desenfoque:

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)

El uso del efecto implica usar el RecordingCanvas interno para el RenderNode. Con el siguiente código, se registra el dibujo, se crea la solicitud de renderización y, luego, se espera a que finalice la solicitud:

val renderCanvas = it.renderNode.beginRecording()
renderCanvas.drawBitmap(it.bitmap, 0f, 0f, null)
renderNode.endRecording()
hardwareRenderer.createRenderRequest()
    .setWaitForPresent(true)
    .syncAndDraw()

La imagen renderizada está en un HardwareBuffer asociado con ImageReader. El siguiente código obtiene el Image y muestra un Bitmap que une su HardwareBuffer.

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

La siguiente limpieza de código se realiza después de procesar la imagen. Ten en cuenta que ImageReader, RenderNode, RenderEffect y HardwareRenderer se pueden usar para procesar varias imágenes.

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

AGSL para el procesamiento de imágenes

El Android Graphics Shading Language (AGSL) se usa en Android 13 y versiones posteriores para definir el comportamiento de los objetos RuntimeShader programables. AGSL comparte gran parte de su sintaxis con sombreadores de fragmentos GLSL, pero funciona dentro del sistema de renderización de gráficos de Android para personalizar la pintura en Canvas y filtrar el contenido View. Se puede usar para agregar procesamiento de imágenes personalizadas durante las operaciones de dibujo o usar un RenderNode directamente para renderizar una imagen en un lienzo Bitmap. En el siguiente ejemplo, se muestra cómo aplicar un sombreador personalizado para reemplazar el efecto de desenfoque de la imagen.

Primero, crea un RuntimeShader y créale una instancia con el código del sombreador AGSL. Este sombreador se usa para aplicar una matriz de colores para la rotación de matiz:

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

El sombreador se puede aplicar a un RenderNode, como a cualquier otro RenderEffect. En el siguiente ejemplo, se muestra la configuración de uniformes en hueShader:

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)

Para obtener el Bitmap, se usa la misma técnica que en la muestra anterior de desenfoque de imágenes.

  • El objeto RecordingCanvas interno del objeto RenderNode aplica el sombreador.
  • Se adquiere Image y se muestra un Bitmap que une su HardwareBuffer.

Cómo convertir de YUV plano a RGB con CameraX

Se admite la conversión de YUV plana a RGB para su uso en el procesamiento de imágenes como parte del caso de uso de ImageAnalysis dentro de CameraX de Jetpack.

Hay recursos sobre el uso de ImageAnalysis como parte del codelab Cómo comenzar a usar CameraX y en el repositorio de muestras de la cámara de Android.

Kit de herramientas de reemplazo de funciones intrínsecas de Renderscript

Si tu aplicación utiliza estas funciones, puedes usar la biblioteca de reemplazo independiente. Nuestras pruebas indican que esto es más rápido que usar la implementación de CPU de RenderScript existente.

El kit de herramientas incluye las siguientes funciones:

  • Mezcla
  • Desenfoque
  • Matriz de color
  • Convolución
  • Histograma e histogramDot
  • Tabla de consulta (LUT) y LUT 3D
  • Cambiar tamaño
  • YUV a RGB

Si deseas conocer información detallada y las limitaciones, consulta los archivos README.md y Toolkit.kt del kit de herramientas.

Realiza los siguientes pasos para descargar, agregar y usar la biblioteca:

  1. Descarga el proyecto desde GitHub.

  2. Ubica y compila el renderscript-toolkit module.

  3. Para agregar la biblioteca a tu proyecto de Android Studio, modifica el archivo build.gradle de la app.

  4. Invoca el método adecuado del kit de herramientas.

Ejemplo: Cómo migrar de la función ScriptIntrinsicBlur

Para reemplazar la función ScriptIntrinsicBlur, sigue estos pasos:

  • Desenfoca el mapa de bits llamando a Toolkit.blur.

    var blurredBitmap = Toolkit.blur(myBitmap, radius)
    
  • Si deseas desenfocar una imagen representada por un array de bytes, especifica el ancho, el alto y la cantidad de bytes por píxel.

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

Cómo migrar desde secuencias de comandos

Si no puedes resolver tu caso de uso con lo siguiente:

Además, tu caso de uso puede beneficiarse de la aceleración de GPU, ya que Android admite procesamiento de GPU en las APIs multiplataforma de Vulkan y OpenGL ES (GLES). Es posible que esto no sea necesario porque, en la mayoría de los dispositivos, tus secuencias de comandos ya se ejecutan en la CPU en lugar de la GPU: C/C++ puede ser más rápido que el procesamiento de RenderScript, GLES o Vulkan para algunos casos de uso (o al menos lo suficientemente rápido para tu caso de uso).

Si deseas comprender mejor cómo migrar la funcionalidad, revisa la app de ejemplo, que muestra cómo desenfocar un mapa de bits y realizar una conversión de matriz de color en RenderScript, y tiene código equivalente en Vulkan y OpenGL.

Si tu aplicación necesita ser compatible con una variedad de versiones, usa RenderScript para dispositivos con Android 6 (nivel de API 23) y versiones anteriores, y Vulkan o GLES en dispositivos compatibles con Android 7 (nivel de API 24) y versiones posteriores. Si tu minSdkVersion tiene un nivel de API 24 o superior, es posible que no necesites usar RenderScript. Se pueden usar Vulkan o GLES 3.1 en cualquier lugar en el que necesites compatibilidad con el procesamiento de GPU.

Android proporciona vinculaciones de SDK para las APIs de GLES, por lo que no es necesario usar el NDK cuando se trabaja en OpenGL ES.

Vulkan no proporciona vinculaciones de SDK, por lo que no hay una asignación directa de RenderScript a Vulkan. Escribes el código de Vulkan con el NDK y creas funciones de JNI para acceder a este código desde Kotlin o Java.

En las siguientes secciones, se describen los aspectos de la migración de RenderScript. En la app de ejemplo, se implementan casi todas estas consideraciones. Para entenderlas mejor, compara el código equivalente de RenderScript y Vulkan.