Principes avancés de RenderScript

Les applications qui utilisent RenderScript s'exécutent encore dans la VM Android. Ainsi, vous avez accès à toutes vos API de framework habituelles, mais vous pouvez utiliser RenderScript si nécessaire. Pour faciliter cette interaction entre le framework et l'environnement d'exécution RenderScript, une couche de code intermédiaire est également présente. Son rôle est de simplifier la communication et la gestion de la mémoire entre les deux niveaux de code. Ce document décrit plus en détail ces différentes couches de code et explique comment la mémoire est partagée entre la VM Android et l'environnement d'exécution RenderScript.

Couche d'exécution RenderScript

Votre code RenderScript est compilé et exécuté dans une couche d'exécution compacte et bien définie. Les API d'exécution RenderScript sont compatibles avec des calculs intensifs, portables et évolutifs en fonction du nombre de cœurs disponibles sur un processeur.

Remarque : Les fonctions C standards du NDK doivent être exécutées sur un CPU. RenderScript ne peut donc pas accéder à ces bibliothèques, puisqu'il est conçu pour s'exécuter sur différents types de processeurs.

Vous définissez votre code RenderScript dans les fichiers .rs et .rsh, dans le répertoire src/ de votre projet Android. Le code est compilé sur un bytecode intermédiaire par le compilateur llvm qui s'exécute dans un build Android. Lorsque votre application s'exécute sur un appareil, le bytecode est ensuite compilé (juste à temps) en code machine par un autre compilateur llvm résidant sur l'appareil. Le code machine étant optimisé pour l'appareil et mis en cache, les utilisations ultérieures de l'application compatible avec RenderScript ne recompilent pas le bytecode.

Voici quelques-unes des principales fonctionnalités des bibliothèques d'exécution RenderScript :

  • Fonctionnalités de requêtes d'allocation de mémoire
  • Un grand ensemble de fonctions mathématiques, avec des versions surchargées de nombreuses routines courantes, sous la forme de scalaires et de vecteurs. Des opérations telles que l'ajout, la multiplication, le produit par point et le produit croisé sont disponibles, ainsi que des fonctions d'arithmétique atomique et de comparaison.
  • Routines de conversion pour les vecteurs et les types de données primitifs, les routines matricielles et les routines de date et heure
  • Types et structures de données compatibles avec le système RenderScript, tels que des types de vecteur permettant de définir des vecteurs deux, trois ou quatre.
  • Fonctions de journalisation

Pour en savoir plus sur les fonctions disponibles, consultez la documentation de référence de l'API d'exécution RenderScript.

Couche réfléchie

La couche réfléchie désigne un ensemble de classes générées par les outils de compilation Android pour permettre l'accès à l'environnement d'exécution RenderScript à partir du framework Android. Elle fournit par ailleurs des méthodes et des constructeurs qui vous permettent d'allouer et d'utiliser de la mémoire pour les pointeurs définis dans votre code RenderScript. Voici les principaux composants réfléchis :

  • Chaque fichier .rs que vous créez est généré dans une classe nommée project_root/gen/package/name/ScriptC_renderscript_filename de type ScriptC. Il s'agit de la version .java de votre fichier .rs, que vous pouvez appeler à partir du framework Android. Cette classe contient les éléments suivants réfléchis à partir du fichier .rs :
    • Fonctions non statiques
    • Variables RenderScript globales non statiques. Les méthodes d'accesseur sont générées pour chaque variable. Vous pouvez ainsi lire et écrire les variables RenderScript à partir du framework Android. Si une variable globale est initialisée au niveau de la couche d'exécution RenderScript, ces valeurs permettent d'initialiser les valeurs correspondantes dans la couche du framework Android. Si les variables globales sont marquées comme const, aucune méthode set n'est générée. Cliquez ici pour en savoir plus.

    • Pointeurs globaux
  • Un élément struct est réfléchi dans sa propre classe nommée project_root/gen/package/name/ScriptField_struct_name, qui étend Script.FieldBase. Cette classe représente un tableau de l'élément struct, qui vous permet d'allouer de la mémoire pour une ou plusieurs instances de ce même élément struct.

Fonctions

Les fonctions sont réfléchies dans la classe de script elle-même, située dans project_root/gen/package/name/ScriptC_renderscript_filename. Par exemple, si vous définissez la fonction suivante dans votre code RenderScript :

void touch(float x, float y, float pressure, int id) {
    if (id >= 10) {
        return;
    }

    touchPos[id].x = x;
    touchPos[id].y = y;
    touchPressure[id] = pressure;
}

Ce code Java est ensuite généré :

public void invoke_touch(float x, float y, float pressure, int id) {
    FieldPacker touch_fp = new FieldPacker(16);
    touch_fp.addF32(x);
    touch_fp.addF32(y);
    touch_fp.addF32(pressure);
    touch_fp.addI32(id);
    invoke(mExportFuncIdx_touch, touch_fp);
}

Les fonctions ne peuvent pas avoir de valeurs renvoyées, car le système RenderScript est conçu pour être asynchrone. Lorsque le code du framework Android appelle RenderScript, il est mis en file d'attente et est exécuté dès que possible. Cette restriction permet au système RenderScript de fonctionner sans interruptions constantes et augmente l'efficacité. Si les fonctions étaient autorisées à avoir des valeurs renvoyées, l'appel serait bloqué jusqu'à ce que la valeur soit renvoyée.

Si vous souhaitez que le code RenderScript renvoie une valeur au framework Android, utilisez la fonction rsSendToClient().

Variables

Les variables des types compatibles sont réfléchies dans la classe de script elle-même, située dans project_root/gen/package/name/ScriptC_renderscript_filename. Un ensemble de méthodes d'accesseur est généré pour chaque variable. Par exemple, si vous définissez la variable suivante dans votre code RenderScript :

uint32_t unsignedInteger = 1;

Ce code Java est ensuite généré :

private long mExportVar_unsignedInteger;
public void set_unsignedInteger(long v){
    mExportVar_unsignedInteger = v;
    setVar(mExportVarIdx_unsignedInteger, v);
}

public long get_unsignedInteger(){
    return mExportVar_unsignedInteger;
}
  

Structs

Les structs sont réfléchis dans leurs propres classes, situées dans <project_root>/gen/com/example/renderscript/ScriptField_struct_name. Cette classe représente un tableau de l'élément struct et vous permet d'allouer de la mémoire pour un nombre spécifié d'éléments struct. Par exemple, si vous définissez le struct suivant :

typedef struct Point {
    float2 position;
    float size;
} Point_t;

le code suivant est généré dans ScriptField_Point.java :

package com.example.android.rs.hellocompute;

import android.renderscript.*;
import android.content.res.Resources;

  /**
  * @hide
  */
public class ScriptField_Point extends android.renderscript.Script.FieldBase {

    static public class Item {
        public static final int sizeof = 12;

        Float2 position;
        float size;

        Item() {
            position = new Float2();
        }
    }

    private Item mItemArray[];
    private FieldPacker mIOBuffer;
    public static Element createElement(RenderScript rs) {
        Element.Builder eb = new Element.Builder(rs);
        eb.add(Element.F32_2(rs), "position");
        eb.add(Element.F32(rs), "size");
        return eb.create();
    }

    public  ScriptField_Point(RenderScript rs, int count) {
        mItemArray = null;
        mIOBuffer = null;
        mElement = createElement(rs);
        init(rs, count);
    }

    public  ScriptField_Point(RenderScript rs, int count, int usages) {
        mItemArray = null;
        mIOBuffer = null;
        mElement = createElement(rs);
        init(rs, count, usages);
    }

    private void copyToArray(Item i, int index) {
        if (mIOBuffer == null) mIOBuffer = new FieldPacker(Item.sizeof * getType().getX()/* count
        */);
        mIOBuffer.reset(index * Item.sizeof);
        mIOBuffer.addF32(i.position);
        mIOBuffer.addF32(i.size);
    }

    public void set(Item i, int index, boolean copyNow) {
        if (mItemArray == null) mItemArray = new Item[getType().getX() /* count */];
        mItemArray[index] = i;
        if (copyNow)  {
            copyToArray(i, index);
            mAllocation.setFromFieldPacker(index, mIOBuffer);
        }
    }

    public Item get(int index) {
        if (mItemArray == null) return null;
        return mItemArray[index];
    }

    public void set_position(int index, Float2 v, boolean copyNow) {
        if (mIOBuffer == null) mIOBuffer = new FieldPacker(Item.sizeof * getType().getX()/* count */);
        if (mItemArray == null) mItemArray = new Item[getType().getX() /* count */];
        if (mItemArray[index] == null) mItemArray[index] = new Item();
        mItemArray[index].position = v;
        if (copyNow) {
            mIOBuffer.reset(index * Item.sizeof);
            mIOBuffer.addF32(v);
            FieldPacker fp = new FieldPacker(8);
            fp.addF32(v);
            mAllocation.setFromFieldPacker(index, 0, fp);
        }
    }

    public void set_size(int index, float v, boolean copyNow) {
        if (mIOBuffer == null) mIOBuffer = new FieldPacker(Item.sizeof * getType().getX()/* count */);
        if (mItemArray == null) mItemArray = new Item[getType().getX() /* count */];
        if (mItemArray[index] == null) mItemArray[index] = new Item();
        mItemArray[index].size = v;
        if (copyNow)  {
            mIOBuffer.reset(index * Item.sizeof + 8);
            mIOBuffer.addF32(v);
            FieldPacker fp = new FieldPacker(4);
            fp.addF32(v);
            mAllocation.setFromFieldPacker(index, 1, fp);
        }
    }

    public Float2 get_position(int index) {
        if (mItemArray == null) return null;
        return mItemArray[index].position;
    }

    public float get_size(int index) {
        if (mItemArray == null) return 0;
        return mItemArray[index].size;
    }

    public void copyAll() {
        for (int ct = 0; ct < mItemArray.length; ct++) copyToArray(mItemArray[ct], ct);
        mAllocation.setFromFieldPacker(0, mIOBuffer);
    }

    public void resize(int newSize) {
        if (mItemArray != null)  {
            int oldSize = mItemArray.length;
            int copySize = Math.min(oldSize, newSize);
            if (newSize == oldSize) return;
            Item ni[] = new Item[newSize];
            System.arraycopy(mItemArray, 0, ni, 0, copySize);
            mItemArray = ni;
        }
        mAllocation.resize(newSize);
        if (mIOBuffer != null) mIOBuffer = new FieldPacker(Item.sizeof * getType().getX()/* count */);
    }
}

Le code généré est fourni afin de vous permettre d'allouer de la mémoire pour les structs demandés par l'environnement d'exécution RenderScript et pour interagir avec les éléments struct en mémoire. La classe de chaque élément struct définit les méthodes et constructeurs suivants :

  • Constructeurs surchargés permettant d'allouer de la mémoire. Le constructeur ScriptField_struct_name(RenderScript rs, int count) vous permet de définir le nombre de structures auxquelles vous souhaitez allouer de la mémoire à l'aide du paramètre count. Le constructeur ScriptField_struct_name(RenderScript rs, int count, int usages) définit un paramètre supplémentaire, usages, qui sert à spécifier l'espace mémoire de cette allocation de mémoire. Il existe quatre possibilités d'espace mémoire :
    • USAGE_SCRIPT : alloue de la mémoire au sein de l'espace mémoire du script. Il s'agit du paramètre par défaut.
    • USAGE_GRAPHICS_TEXTURE : alloue de la mémoire au sein de l'espace mémoire de texture du GPU.
    • USAGE_GRAPHICS_VERTEX : alloue de la mémoire au sein de l'espace mémoire vertex du GPU.
    • USAGE_GRAPHICS_CONSTANTS : alloue de la mémoire au sein de l'espace mémoire constant du GPU utilisé par les différents objets du programme.

    Vous pouvez spécifier plusieurs espaces mémoire à l'aide de l'opérateur OR bit à bit. Cela permet d'informer l'environnement d'exécution RenderScript que vous souhaitez accéder aux données des espaces mémoire spécifiés. L'exemple suivant alloue de la mémoire pour un type de données personnalisé à la fois dans l'espace mémoire des données de script et dans celui des données vertex :

    Kotlin

    val touchPoints: ScriptField_Point = ScriptField_Point(
            myRenderScript,
            2,
            Allocation.USAGE_SCRIPT or Allocation.USAGE_GRAPHICS_VERTEX
    )
    

    Java

    ScriptField_Point touchPoints = new ScriptField_Point(myRenderScript, 2,
            Allocation.USAGE_SCRIPT | Allocation.USAGE_GRAPHICS_VERTEX);
    
  • Une classe imbriquée statique, Item, vous permet de créer une instance d'élément struct sous la forme d'un objet. Cette classe imbriquée est utile lorsque vous avez intérêt à utiliser struct dans votre code Android. Une fois que vous avez terminé de manipuler l'objet, vous pouvez le placer dans la mémoire allouée en appelant set(Item i, int index, boolean copyNow) et en définissant l'élément Item sur la position souhaitée dans le tableau. L'environnement d'exécution RenderScript a automatiquement accès à la nouvelle mémoire écrite.
  • Méthodes d'accesseur pour obtenir et définir les valeurs de chaque champ dans un struct. Chacune de ces méthodes d'accesseur comporte un paramètre index pour spécifier l'élément struct dans le tableau dans lequel vous souhaitez lire ou écrire des données. Chaque méthode setter possède également un paramètre copyNow qui spécifie si la mémoire doit être synchronisée immédiatement ou non avec l'environnement d'exécution RenderScript. Pour synchroniser toute mémoire qui n'a pas été synchronisée, appelez copyAll().
  • La méthode createElement() crée une description du struct en mémoire. Cette description permet d'allouer de la mémoire composée d'un ou de plusieurs éléments.
  • resize() fonctionne comme une méthode realloc() en C. Il vous permet d'étendre la mémoire précédemment allouée tout en conservant les valeurs actuelles, qui ont été préalablement créées.
  • copyAll() synchronise la mémoire définie au niveau du framework avec l'environnement d'exécution RenderScript. Lorsque vous appelez une méthode d'accesseur définie sur un membre, vous pouvez spécifier un paramètre booléen copyNow facultatif. Spécifier true synchronise la mémoire lorsque vous appelez la méthode. En spécifiant "false", vous pouvez appeler copyAll() une fois pour synchroniser la mémoire de toutes les propriétés qui ne sont pas encore synchronisées.

Pointeurs

Les pointeurs globaux sont réfléchis dans la classe de script elle-même, située dans project_root/gen/package/name/ScriptC_renderscript_filename. Vous pouvez déclarer des pointeurs vers un struct ou l'un des types RenderScript compatibles, mais un struct ne peut pas contenir de pointeurs ni de tableaux imbriqués. Par exemple, si vous définissez les pointeurs suivants vers un élément struct et un élément int32_t :

typedef struct Point {
    float2 position;
    float size;
} Point_t;

Point_t *touchPoints;
int32_t *intPointer;

alors, le code Java suivant est généré :

private ScriptField_Point mExportVar_touchPoints;
public void bind_touchPoints(ScriptField_Point v) {
    mExportVar_touchPoints = v;
    if (v == null) bindAllocation(null, mExportVarIdx_touchPoints);
    else bindAllocation(v.getAllocation(), mExportVarIdx_touchPoints);
}

public ScriptField_Point get_touchPoints() {
    return mExportVar_touchPoints;
}

private Allocation mExportVar_intPointer;
public void bind_intPointer(Allocation v) {
    mExportVar_intPointer = v;
    if (v == null) bindAllocation(null, mExportVarIdx_intPointer);
    else bindAllocation(v, mExportVarIdx_intPointer);
}

public Allocation get_intPointer() {
    return mExportVar_intPointer;
}
  

Une méthode get et une méthode spéciale nommée bind_pointer_name (au lieu d'une méthode set()) sont générées. La méthode bind_pointer_name vous permet de lier la mémoire allouée dans la VM Android à l'environnement d'exécution RenderScript (vous ne pouvez pas allouer de mémoire dans votre fichier .rs). Pour en savoir plus, consultez la section Utiliser la mémoire allouée.

API d'allocation de mémoire

Les applications qui utilisent RenderScript s'exécutent toujours dans la VM Android. Toutefois, le code RenderScript réel s'exécute de manière native et a besoin d'accéder à la mémoire allouée dans la VM Android. Pour ce faire, vous devez associer la mémoire allouée dans la VM à l'environnement d'exécution RenderScript. Ce processus, appelé "liaison", permet à l'environnement d'exécution RenderScript de fonctionner de manière transparente avec la mémoire qu'il demande, mais ne peut pas allouer de mémoire explicitement. Le résultat final est essentiellement le même que si vous aviez appelé malloc en C. L'avantage est que la VM Android peut récupérer et partager de la mémoire avec la couche d'exécution RenderScript. La liaison n'est nécessaire que pour la mémoire allouée de manière dynamique. La mémoire allouée de manière statique est automatiquement créée pour votre code RenderScript au moment de la compilation. Pour en savoir plus sur le fonctionnement de l'allocation de mémoire, consultez la Figure 1.

Pour assurer la compatibilité avec ce système d'allocation de mémoire, il existe un ensemble d'API permettant à la VM Android d'allouer de la mémoire et de fournir des fonctionnalités semblables à celles d'un appel malloc. Ces classes décrivent essentiellement comment la mémoire doit être allouée et effectuent l'allocation. Pour mieux en comprendre le fonctionnement, il est utile de considérer ces classes au regard d'un simple appel malloc, qui peut se présenter comme suit :

array = (int *)malloc(sizeof(int)*10);

L'appel malloc peut être divisé en deux parties : la taille de la mémoire allouée (sizeof(int)), ainsi que le nombre d'unités de cette mémoire à allouer (10). Le framework Android fournit des classes pour ces deux parties, ainsi qu'une classe pour représenter malloc lui-même.

La classe Element représente la partie sizeof(int) de l'appel malloc et encapsule une cellule d'une allocation de mémoire, telle qu'une seule valeur flottante ou un struct. La classe Type encapsule la classe Element ainsi que la quantité d'éléments à allouer (10 dans notre exemple). Vous pouvez considérer un élément Type comme un tableau de valeurs Element. La classe Allocation effectue l'allocation de mémoire réelle en fonction d'un élément Type donné et représente la mémoire réellement allouée.

Dans la plupart des cas, vous n'avez pas besoin d'appeler ces API d'allocation de mémoire directement. Les classes de la couche réfléchie génèrent du code pour utiliser ces API automatiquement. Tout ce dont vous avez besoin pour allouer de la mémoire est d'appeler un constructeur déclaré dans l'une des classes de la couche réfléchie, puis de lier la classe Allocation de mémoire obtenue au RenderScript. Dans certains cas, vous pouvez utiliser ces classes directement pour allouer de la mémoire vous-même, par exemple pour charger un bitmap à partir d'une ressource ou pour allouer de la mémoire pour des pointeurs à des types primitifs. Pour savoir comment procéder, consultez la section Attribuer et lier la mémoire au RenderScript. Le tableau suivant décrit plus en détail les trois classes de gestion de la mémoire :

Type d'objet Android Description
Element

Un élément décrit une cellule d'allocation de mémoire et peut avoir deux formes : basique ou complexe.

Un élément de base contient un seul composant de données de n'importe quel type de données RenderScript valide. Les types de données d'éléments de base incluent une valeur float unique, un vecteur float4 ou une seule couleur RVB-565.

Les éléments complexes contiennent une liste d'éléments basiques et sont créés à partir des éléments struct que vous déclarez dans votre code RenderScript. Par exemple, une allocation peut contenir plusieurs éléments struct organisés dans la mémoire. Chaque struct est considéré comme son propre élément, et non selon chaque type de données qu'il contient.

Type

Un type est un modèle d'allocation de mémoire qui se compose d'un élément et d'une ou plusieurs dimensions. Il décrit la mise en page de la mémoire (autrement dit, un tableau de classes Element), mais n'alloue pas la mémoire pour les données qu'il décrit.

Il comprend cinq dimensions : X, Y, Z, le niveau de détail (LOD) et les faces (d'une carte cubique). Vous pouvez définir les dimensions X, Y et Z sur n'importe quelle valeur entière positive dans les limites de la mémoire disponible. La dimension X d'une allocation de dimension unique est supérieure à zéro, tandis que les dimensions Y et Z sont égales à zéro pour indiquer leur absence. Par exemple, une allocation de x = 10, y = 1 est considérée comme bidimensionnelle, et x = 10, y = 0 est considérée comme unidimensionnelle. Les dimensions LOD et Faces sont des valeurs booléennes indiquant si le modèle est présent ou non.

Allocation

Une allocation fournit la mémoire aux applications en fonction d'une description représentée par un élément Type. La mémoire allouée peut exister simultanément dans de nombreux espaces mémoire. Si la mémoire est modifiée dans un espace, vous devez la synchroniser explicitement, de sorte que les changements se répercutent dans tous les autres espaces dans lesquels elle existe.

Les données d'allocation sont importées de deux manières principales : en cochant ou en décochant les cases "Type". Pour les tableaux simples, il existe des fonctions copyFrom() qui récupèrent un tableau du système Android et le copient dans le magasin de mémoire de la couche native. Les variantes décochées permettent au système Android de copier sur des tableaux de structures, car il n'est pas compatible avec les structures. Par exemple, s'il existe une allocation qui correspond à un tableau de n floats, les données contenues dans un tableau float[n] ou un tableau byte[n*4] peuvent être copiées.

Utiliser la mémoire

Les variables globales et non statiques que vous déclarez dans votre RenderScript sont allouées au moment de la compilation. Vous pouvez utiliser ces variables directement dans votre code RenderScript sans leur allouer de mémoire au niveau du framework Android. La couche du framework Android a également accès à ces variables avec les méthodes d'accesseur fournies qui sont générées dans les classes de la couche réfléchie. Si ces variables sont initialisées au niveau de la couche d'exécution RenderScript, ces valeurs permettent d'initialiser les valeurs correspondantes dans la couche du framework Android. Si les variables globales sont marquées comme const, aucune méthode set n'est générée. Cliquez ici pour en savoir plus.

Remarque : Si vous utilisez certaines structures RenderScript contenant des pointeurs, tels que rs_program_fragment et rs_allocation, vous devez obtenir un objet de la classe de framework Android correspondante. Appelez ensuite la méthode set pour cette structure afin de lier la mémoire à l'environnement d'exécution RenderScript. Vous ne pouvez pas manipuler directement ces structures au niveau de la couche d'exécution RenderScript. Cette restriction ne s'applique pas aux structures définies par l'utilisateur qui contiennent des pointeurs, car elles ne peuvent pas être exportées vers une classe de la couche réfléchie. Une erreur de compilateur est générée si vous essayez de déclarer un struct global non statique contenant un pointeur.

RenderScript est également compatible avec les pointeurs, mais vous devez allouer explicitement la mémoire dans le code de votre framework Android. Lorsque vous déclarez un pointeur global dans votre fichier .rs, vous allouez de la mémoire via la classe de couche réfléchie appropriée et liez cette mémoire à la couche native RenderScript. Vous pouvez interagir avec cette mémoire à partir de la couche du framework Android et de la couche RenderScript, ce qui vous permet de modifier les variables dans la couche la plus appropriée.

Allouer et lier de la mémoire dynamique à RenderScript

Pour allouer de la mémoire dynamique, la méthode la plus courante consiste à appeler le constructeur d'une classe Script.FieldBase. Une autre solution consiste à créer une classe Allocation manuellement, ce qui est nécessaire pour des éléments tels que les pointeurs de type primitif. Pour plus de simplicité, mieux vaut utiliser dans la mesure du possible un constructeur de classe Script.FieldBase. Après avoir obtenu une allocation de mémoire, appelez la méthode bind réfléchie du pointeur pour lier la mémoire allouée à l'environnement d'exécution RenderScript.

L'exemple ci-dessous alloue de la mémoire à la fois pour un pointeur de type primitif intPointer et pour un pointeur vers un struct touchPoints. Elle associe également la mémoire au script RenderScript :

Kotlin

private lateinit var myRenderScript: RenderScript
private lateinit var script: ScriptC_example
private lateinit var resources: Resources

public fun init(rs: RenderScript, res: Resources) {
    myRenderScript = rs
    resources = res

    // allocate memory for the struct pointer, calling the constructor
    val touchPoints = ScriptField_Point(myRenderScript, 2)

    // Create an element manually and allocate memory for the int pointer
    val intPointer: Allocation = Allocation.createSized(myRenderScript, Element.I32(myRenderScript), 2)

    // create an instance of the RenderScript, pointing it to the bytecode resource
    script = ScriptC_point(myRenderScript/*, resources, R.raw.example*/)

    // bind the struct and int pointers to the RenderScript
    script.bind_touchPoints(touchPoints)
    script.bind_intPointer(intPointer)

   ...
}

Java

private RenderScript myRenderScript;
private ScriptC_example script;
private Resources resources;

public void init(RenderScript rs, Resources res) {
    myRenderScript = rs;
    resources = res;

    // allocate memory for the struct pointer, calling the constructor
    ScriptField_Point touchPoints = new ScriptField_Point(myRenderScript, 2);

    // Create an element manually and allocate memory for the int pointer
    intPointer = Allocation.createSized(myRenderScript, Element.I32(myRenderScript), 2);

    // create an instance of the RenderScript, pointing it to the bytecode resource
    script = new ScriptC_example(myRenderScript, resources, R.raw.example);

    // bind the struct and int pointers to the RenderScript
    script.bind_touchPoints(touchPoints);
    script.bind_intPointer(intPointer);

   ...
}

Lire et écrire dans la mémoire

Vous pouvez lire et écrire dans la mémoire allouée de manière statique et dynamique, à la fois au niveau de l'environnement d'exécution RenderScript et de la couche du framework Android.

Pour la mémoire allouée de manière statique, la communication de ne peut s'effectuer que de manière unidirectionnelle au niveau de l'environnement d'exécution RenderScript. Lorsque le code RenderScript modifie la valeur d'une variable, celle-ci n'est pas transmise à la couche du framework Android pour des raisons d'efficacité. La dernière valeur définie à partir du framework Android est toujours renvoyée lors d'un appel à une méthode get. Toutefois, lorsque le code du framework Android modifie une variable, cette modification peut être communiquée automatiquement à l'environnement d'exécution RenderScript ou synchronisée ultérieurement. Si vous devez envoyer des données depuis l'environnement d'exécution RenderScript vers la couche du framework Android, vous pouvez utiliser la fonction rsSendToClient() pour contourner cette limitation.

Lorsque vous travaillez avec de la mémoire allouée de manière dynamique, toutes les modifications de la couche d'exécution RenderScript sont répercutées sur la couche du framework Android si vous avez modifié l'allocation de mémoire à l'aide du pointeur associé. La modification d'un objet au niveau de la couche du framework Android propage immédiatement cette modification à la couche d'exécution RenderScript.

Lire et écrire des variables globales

La lecture et l'écriture de variables globales sont un processus simple. Vous pouvez utiliser les méthodes d'accesseur au niveau du framework Android ou les définir directement dans le code RenderScript. N'oubliez pas que les modifications que vous apportez dans votre code RenderScript ne sont pas répercutées sur la couche du framework Android (cliquez ici pour en savoir plus).

Prenons l'exemple du struct suivant, déclaré dans un fichier nommé rsfile.rs :

typedef struct Point {
    int x;
    int y;
} Point_t;

Point_t point;

Vous pouvez attribuer des valeurs au struct comme ceci directement dans rsfile.rs. Ces valeurs ne sont pas propagées au niveau du framework Android :

point.x = 1;
point.y = 1;

Vous pouvez attribuer des valeurs au struct au niveau de la couche du framework Android, comme ceci. Ces valeurs sont propagées au niveau de l'environnement d'exécution RenderScript de manière asynchrone :

Kotlin

val script: ScriptC_rsfile = ...

...

script._point = ScriptField_Point.Item().apply {
    x = 1
    y = 1
}

Java

ScriptC_rsfile script;

...

Item i = new ScriptField_Point.Item();
i.x = 1;
i.y = 1;
script.set_point(i);

Vous pouvez lire les valeurs de votre code RenderScript comme suit :

rsDebug("Printing out a Point", point.x, point.y);

Vous pouvez lire les valeurs de la couche du framework Android à l'aide du code suivant. N'oubliez pas que ce code ne renvoie une valeur que si elle a été définie au niveau du framework Android. Vous obtiendrez une exception de pointeur si vous ne définissez la valeur qu'au niveau de l'environnement d'exécution RenderScript :

Kotlin

Log.i("TAGNAME", "Printing out a Point: ${mScript._point.x} ${mScript._point.y}")
println("${point.x} ${point.y}")

Java

Log.i("TAGNAME", "Printing out a Point: " + script.get_point().x + " " + script.get_point().y);
System.out.println(point.get_x() + " " + point.get_y());

Lire et écrire des pointeurs globaux

En supposant que la mémoire ait été allouée au niveau du framework Android et liée à l'environnement d'exécution RenderScript, vous pouvez lire et écrire la mémoire à partir du framework Android à l'aide des méthodes get et set pour ce pointeur. Dans la couche de l'environnement d'exécution RenderScript, vous pouvez lire et écrire dans la mémoire avec des pointeurs de façon normale. Les modifications sont alors propagées dans la couche du framework Android, ce qui n'est pas le cas avec la mémoire allouée de manière statique.

Par exemple, en utilisant le pointeur suivant pour un élément struct dans un fichier nommé rsfile.rs :

typedef struct Point {
    int x;
    int y;
} Point_t;

Point_t *point;

En supposant que vous ayez déjà alloué de la mémoire au niveau de la couche du framework Android, vous pouvez accéder normalement aux valeurs du struct. Toutes les modifications que vous apportez au struct via sa variable de pointeur sont automatiquement disponibles pour la couche du framework Android :

Kotlin

point[index].apply {
    x = 1
    y = 1
}

Java

point[index].x = 1;
point[index].y = 1;

Vous pouvez également lire et écrire des valeurs dans le pointeur au niveau de la couche du framework Android :

Kotlin

val i = ScriptField_Point.Item().apply {
    x = 100
    y = 100
}
val p = ScriptField_Point(rs, 1).apply {
    set(i, 0, true)
}
script.bind_point(p)

p.get_x(0)            //read x and y from index 0
p.get_y(0)

Java

ScriptField_Point p = new ScriptField_Point(rs, 1);
Item i = new ScriptField_Point.Item();
i.x=100;
i.y = 100;
p.set(i, 0, true);
script.bind_point(p);

p.get_x(0);            //read x and y from index 0
p.get_y(0);

Lorsque la mémoire est déjà liée, vous n'avez pas besoin de la réassocier à l'environnement d'exécution RenderScript chaque fois que vous modifiez une valeur.