Effectuer une migration depuis RenderScript

Les API RenderScript sont obsolètes à partir d'Android 12. Les fabricants d'appareils et de composants ont déjà cessé de proposer la prise en charge de l'accélération matérielle. De plus, la compatibilité avec RenderScript devrait être complètement supprimée dans une prochaine version.

Les performances C/C++ peuvent être adaptées à de nombreux cas d'utilisation. Si vous dépendiez uniquement de RenderScript pour les fonctionnalités intrinsèques, vous pouvez les remplacer par le kit de remplacement des fonctionnalités intrinsèques de RenderScript, qui est plus facile à utiliser et qui multiplie par deux les performances.

Pour profiter pleinement de l'accélération du GPU, nous vous recommandons de migrer vos scripts vers Vulkan. Les autres options d'accélération incluent la migration de vos scripts vers OpenGL, l'utilisation d'opérations d'image basées sur Canvas et l'utilisation du langage AGSL (Android Graphics Language).

Suite à l'abandon de RenderScript sur la plate-forme Android, la prise en charge de RenderScript sera supprimée dans le plug-in Android Gradle. À partir de la version 7.2 du plug-in Android Gradle, les API RenderScript sont obsolètes. Elles continuent de fonctionner, mais déclenchent des avertissements. Les futures versions d'AGP ne prendront plus en charge RenderScript. Ce guide explique comment effectuer la migration depuis RenderScript.

Migrer depuis les fonctionnalités intrinsèques

Même si les fonctionnalités intrinsèques de RenderScript continuent à fonctionner après l'abandon de RenderScript, il est possible qu'elles ne puissent s'exécuter que sur le processeur et non plus sur le GPU.

Pour certaines de ces opérations, des options plus efficaces sont désormais intégrées à la plate-forme ou aux bibliothèques Jetpack.

Opérations d'image accélérées intégrées

La plate-forme Android prend en charge les opérations de traitement d'image accélérées qui peuvent être appliquées aux images, indépendamment des fonctionnalités intrinsèques de RenderScript. Exemples :

  • Mélange
  • Floutage
  • Matrice de couleurs
  • Redimensionner

Floutage d'image sous Android 12 ou version ultérieure dans une vue

RenderEffect avec la prise en charge du floutage a été ajouté à Android 12 (niveau d'API 31). Il vous permet de flouter un RenderNode. RenderNode est une construction de la liste d'affichage utilisée par Android pour accélérer les graphismes de la plate-forme.

Android fournit un raccourci pour appliquer un effet au RenderNode associé à une View. Pour flouter une View, appelez View.setRenderEffect() :

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

Floutage d'image sous Android 12 ou version ultérieure affiché dans un bitmap

Si vous souhaitez que l'image floutée soit affichée dans unBitmap, le framework prend en charge le rendu accéléré avec un HardwareRenderer appuyé par un HardwareBuffer. Le code suivant crée le HardwareRenderer, le RenderNode et le RenderEffect pour le floutage :

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)

L'application de l'effet implique l'utilisation du RecordingCanvas interne pour le RenderNode. Le code suivant enregistre le dessin, crée la requête de rendu, puis attend qu'elle soit terminée :

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

L'image affichée se trouve dans un HardwareBuffer associé à l'ImageReader. Le code suivant acquiert l'Image et renvoie un Bitmap qui encapsule son 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")

Le code suivant effectue un nettoyage après l'affichage de l'image. Notez que l'ImageReader, le RenderNode, le RenderEffect et le HardwareRenderer peuvent être utilisés pour traiter plusieurs images.

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

AGSL pour le traitement d'image

Le langage AGSL (Android Graphics Shading Language) est utilisé par Android 13 et versions ultérieures pour définir le comportement des objets RuntimeShader programmables. AGSL partage une grande partie de sa syntaxe avec les nuanceurs de fragment GLSL, mais fonctionne dans le système de rendu graphique Android pour personnaliser la peinture dans Canvas et filtrer le contenu View. Cela permet d'ajouter un traitement d'image personnalisé lors des opérations de dessin ou d'utiliser directement un RenderNode pour afficher une image dans un canevas Bitmap. L'exemple suivant montre comment appliquer un nuanceur personnalisé pour remplacer l'effet de floutage d'image.

Commencez par créer un RuntimeShader, en l'instanciant avec le code du nuanceur AGSL. Ce nuanceur permet d'appliquer une matrice de couleurs pour la rotation des teintes :

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

Le nuanceur peut être appliqué à un RenderNode, comme n'importe quel autre RenderEffect. L'exemple suivant montre comment définir les uniformes dans 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)

Pour obtenir le Bitmap, la même technique est utilisée que dans l'exemple de floutage d'image précédent.

  • Le RecordingCanvas interne pour le RenderNode applique le nuanceur.
  • L'Image est acquise et renvoie un Bitmap qui encapsule son HardwareBuffer.

Convertir le format YUV planaire en RVB avec CameraX

La conversion du format YUV planaire en RVB pour le traitement d'image est prise en charge dans le cadre du cas d'utilisation ImageAnalysis dans CameraX de Jetpack.

Des ressources sur l'utilisation d'ImageAnalysis sont disponibles dans l'atelier de programmation Premiers pas avec CameraX et dans le dépôt d'exemples d'appareils photo Android.

Kit de remplacement des fonctionnalités intrinsèques de Renderscript

Si votre application utilise des fonctionnalités intrinsèques, vous pouvez utiliser la bibliothèque de remplacement autonome. Cette option sera, d'après nos tests, plus rapide que d'utiliser la mise en œuvre existante du processeur RenderScript.

Ce kit comprend les fonctions suivantes :

  • Mélange
  • Floutage
  • Matrice de couleurs
  • Convoluer
  • Histogramme et histogramDot
  • Tableau de conversion (LUT) et LUT 3D
  • Redimensionner
  • YUV vers RVB

Pour en savoir plus et connaître les limites, consultez les fichiers README.md et Toolkit.kt du kit.

Pour télécharger, ajouter et utiliser la bibliothèque, procédez comme suit :

  1. Télécharger le projet depuis GitHub.

  2. Localisez et créez le renderscript-toolkit module.

  3. Ajoutez la bibliothèque à votre projet Android Studio en modifiant le fichier build.gradle de l'application.

  4. Appelez la méthode appropriée dans le kit.

Exemple : Migrer depuis la fonction ScriptIntrinsicBlur

Pour remplacer la fonction ScriptIntrinsicBlur :

  • Pour flouter un bitmap, appelez Toolkit.blur.

    var blurredBitmap = Toolkit.blur(myBitmap, radius)
    
  • Si vous souhaitez flouter une image représentée par un tableau d'octets, spécifiez la largeur, la hauteur et le nombre d'octets par pixel.

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

Migrer depuis les scripts

Si votre cas d'utilisation ne peut pas être résolu avec :

Et si votre cas d'utilisation peut bénéficier de l'accélération du GPU, Android prend en charge les calculs GPU sur les API multiplates-formes Vulkan et OpenGL ES (GLES). Cela peut s'avérer inutile, car sur la plupart des appareils, vos scripts s'exécutent déjà sur le processeur plutôt que sur le GPU : C/C++ peut être plus rapide que les calculs RenderScript, GLES ou Vulkan dans certains cas d'utilisation (ou du moins assez rapide pour votre cas d'utilisation).

Pour mieux comprendre comment effectuer la migration, consultez l'application exemple. L'exemple montre comment flouter un bitmap et effectuer une conversion de la matrice de couleurs dans RenderScript, et présente un code équivalent dans Vulkan et OpenGL.

Si votre application doit prendre en charge plusieurs versions, utilisez RenderScript pour les appareils équipés d'Android 6 (niveau d'API 23) ou version antérieure, et Vulkan ou GLES sur les appareils compatibles équipés d'Android 7 (niveau d'API 24) ou version ultérieure. Si vous utilisez minSdkVersion 24 ou ultérieure, vous n'aurez peut-être pas besoin d'utiliser RenderScript. Vulkan ou GLES 3.1 peuvent être utilisés partout où vous avez besoin de capacités de calcul GPU.

Android fournit des liaisons SDK pour les API GLES. Il n'est donc pas nécessaire d'utiliser le NDK lorsque vous travaillez dans OpenGL ES.

Vulkan ne fournit pas de liaisons SDK. Il n'y a donc pas de mappage direct de RenderScript vers Vulkan. Vous écrivez le code Vulkan à l'aide du NDK et créez des fonctions JNI pour accéder à ce code depuis Kotlin ou Java.

Les pages suivantes traitent des aspects de la migration depuis RenderScript. L'application exemple met en œuvre pratiquement toutes ces considérations. Pour mieux les comprendre, comparez le code RenderScript et le code équivalent Vulkan.