RenderScript avanzado

Debido a que las aplicaciones que utilizan RenderScript aún se ejecutan dentro de la VM de Android, tienes acceso a todas las API de framework que conoces, pero puedes usar RenderScript cuando corresponda. Para posibilitar esta interacción entre el framework y el entorno de ejecución de RenderScript, también hay una capa intermedia de código que facilita la comunicación y la administración de la memoria entre los dos niveles de código. En este documento, se proporcionan más detalles sobre las diferentes capas de código y sobre la manera en que se comparte la memoria entre la VM de Android y el entorno de ejecución de RenderScript.

Capa del entorno de ejecución de RenderScript

Tu código de RenderScript se compila y se ejecuta en una capa de entorno de ejecución compacta y bien definida. Las API de entorno de ejecución de RenderScript ofrecen compatibilidad para procesamiento intensivo que es portátil y escalable automáticamente a la cantidad de núcleos disponibles en un procesador.

Nota: Se debe garantizar que las funciones C estándar en el NDK se ejecuten en una CPU, por lo que RenderScript no puede acceder a esas bibliotecas, ya que está diseñado para ejecutarse en diferentes tipos de procesadores.

Define tu código de RenderScript en archivos .rs y .rsh en el directorio src/ de tu proyecto de Android. El compilador llvm que se ejecuta como parte de una compilación de Android compila el código en un código de bytes intermedio. Cuando la aplicación se ejecuta en un dispositivo, se compila el bytecode (justo a tiempo) en código máquina con otro compilador llvm que se encuentre en el dispositivo. El código máquina está optimizado para el dispositivo y también almacenado en caché, por lo que los usos posteriores de la aplicación habilitada para RenderScript no vuelven a compilar el código de bytes.

Algunas funciones clave de las bibliotecas de entorno de ejecución de RenderScript son las siguientes:

  • Funciones de solicitud de asignación de memoria
  • Un gran grupo de funciones matemáticas con versiones sobrecargadas de tipo escalar y vectorial de muchas rutinas comunes (están disponibles las operaciones como la suma, la multiplicación, el producto de puntos y el producto cruzado, así como las funciones de comparación y de aritmética atómica)
  • Rutinas de conversión para vectores y tipos de datos primitivos, rutinas matriciales y rutinas de fecha y hora
  • Estructuras y tipos de datos para admitir el sistema RenderScript, como los tipos de vectores para definir dos, tres o cuatro vectores
  • Funciones de acceso

Consulta la referencia de la API de entorno de ejecución de RenderScript para obtener más información sobre las funciones disponibles.

Capa reflejada

La capa reflejada es un conjunto de clases que generan las herramientas de compilación de Android para otorgar acceso al entorno de ejecución de RenderScript desde el framework de Android. Esta capa también proporciona métodos y constructores que permiten asignar y trabajar con memoria para punteros que se definen en el código de RenderScript. En la siguiente lista, se describen los componentes principales que se reflejan:

  • Cada archivo .rs que creas se genera en una clase llamada project_root/gen/package/name/ScriptC_renderscript_filename de tipo ScriptC. Este archivo es la versión .java de tu archivo .rs, al que puedes llamar desde el framework de Android. En esta clase, se incluyen los siguientes elementos reflejados del archivo .rs:
    • Funciones no estáticas
    • Variables RenderScript globales no estáticas. Los métodos de acceso se generan para cada variable, por lo que puedes leer y escribir las variables de RenderScript desde el framework de Android. Si se inicializa una variable global en la capa de entorno de ejecución de RenderScript, esos valores se utilizan para inicializar los valores correspondientes en la capa de framework de Android. Si las variables globales están marcadas como const, no se genera un método set. Busca más detalles aquí.

    • Punteros globales
  • Una struct se refleja en su propia clase denominada project_root/gen/package/name/ScriptField_struct_name, que extiende Script.FieldBase. Esta clase representa un arreglo de struct, que te permite asignar memoria para una o más instancias de esta struct

Funciones

Las funciones se reflejan en la propia clase de secuencia de comandos, ubicada en project_root/gen/package/name/ScriptC_renderscript_filename. Por ejemplo, si defines la siguiente función en el código de 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;
}

se genera el siguiente código Java:

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

Las funciones no pueden tener valores de resultado, porque el sistema RenderScript está diseñado para ser asíncrono. Cuando tu código del framework de Android llama a RenderScript, la llamada se pone en cola y se ejecuta cuando es posible. Esta restricción permite que el sistema RenderScript funcione sin interrupciones constantes y aumenta la eficiencia. Si se permitiera que las funciones tuvieran valores de resultado, la llamada se bloquearía hasta que se mostrara el valor.

Si deseas que el código de RenderScript envíe un valor al framework de Android, usa la función rsSendToClient().

Variables

Las variables de los tipos admitidos se reflejan en la propia clase de secuencia de comandos, ubicada en project_root/gen/package/name/ScriptC_renderscript_filename. Para cada variable, se genera un conjunto de métodos de acceso. Por ejemplo, si defines la siguiente variable en el código de RenderScript:

uint32_t unsignedInteger = 1;

se genera el siguiente código Java:

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

Las structs se reflejan en sus propias clases, ubicadas en <project_root>/gen/com/example/renderscript/ScriptField_struct_name. Esta clase representa un arreglo de las struct y te permite asignar memoria para un número especificado de struct. Por ejemplo, si defines la siguiente struct:

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

se genera el siguiente código en 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 */);
    }
}

El código generado se te proporciona como una ventaja para asignar memoria a structs solicitadas por el entorno de ejecución de RenderScript e interactuar con structs en la memoria. Cada clase de struct define los siguientes métodos y constructores:

  • Constructores sobrecargados que te permiten asignar memoria. El constructor ScriptField_struct_name(RenderScript rs, int count) te permite definir la cantidad de estructuras a las que deseas asignar memoria con el parámetro count. El constructor ScriptField_struct_name(RenderScript rs, int count, int usages) define un parámetro adicional, usages, que te permite especificar el espacio de memoria de esta asignación de memoria. Hay cuatro posibilidades de espacio de memoria:
    • USAGE_SCRIPT: asigna en el espacio de memoria de la secuencia de comandos. Este es el espacio de memoria predeterminado si no especificas un espacio de memoria.
    • USAGE_GRAPHICS_TEXTURE asigna en el espacio de memoria de textura de la GPU.
    • USAGE_GRAPHICS_VERTEX asigna en el espacio de memoria de vértice de la GPU.
    • USAGE_GRAPHICS_CONSTANTS asigna en el espacio de memoria de constantes de la GPU que utilizan los diversos objetos del programa.

    Puedes especificar varios espacios de memoria mediante el operador de bits OR. De esa manera, se le notifica al entorno de ejecución de RenderScript que vas a acceder a los datos en los espacios de memoria especificados. En el siguiente ejemplo, se asigna memoria a un tipo de datos personalizado en los espacios de memoria de vértice y de secuencia de comandos:

    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);
    
  • Una clase anidada estática, Item, te permite crear una instancia de struct en forma de objeto. Esta clase anidada es útil si prefieres trabajar con struct en tu código de Android. Cuando hayas terminado de manipular el objeto, podrás enviar el objeto a la memoria asignada llamando a set(Item i, int index, boolean copyNow) y estableciendo Item en la posición deseada en el arreglo. El entorno de ejecución de RenderScript automáticamente tiene acceso a la memoria recién escrita.
  • Métodos de acceso para obtener y establecer los valores de cada campo en una struct. Cada uno de estos métodos de acceso tiene un parámetro index para especificar struct en el arreglo en el que deseas leer o escribir. Cada método set también tiene un parámetro copyNow que especifica si se debe sincronizar inmediatamente esta memoria con el entorno de ejecución de RenderScript. Para sincronizar cualquier memoria que no se haya sincronizado, llama al copyAll().
  • El método createElement() crea una descripción de la struct en la memoria. Esta descripción se utiliza para asignar memoria que consta de uno o más elementos.
  • resize() funciona de manera muy similar a realloc() en C, lo que te permite expandir la memoria asignada previamente y mantener los valores actuales que se crearon antes.
  • copyAll() sincroniza la memoria que se estableció en el nivel de framework con el entorno de ejecución de RenderScript. Cuando llamas a un método set de acceso en un miembro, hay un parámetro booleano copyNow opcional que puedes especificar. Si especificas true, se sincroniza la memoria cuando llamas al método. Si especificas falso, puedes llamar una vez a copyAll(), que sincroniza la memoria para todas las propiedades que no están sincronizadas aún.

Punteros

Los punteros globales se reflejan en la propia clase de secuencia de comandos, ubicada en project_root/gen/package/name/ScriptC_renderscript_filename. Puedes declarar punteros a struct o cualquiera de los tipos de RenderScript admitidos, pero una struct no puede incluir punteros ni arreglos anidados. Por ejemplo, si defines los siguientes punteros para una struct y para int32_t

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

Point_t *touchPoints;
int32_t *intPointer;

se genera el siguiente código Java:

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

Se generan un método get y un método especial llamado bind_pointer_name (en lugar de un método set()). El método bind_pointer_name te permite vincular la memoria asignada en la VM de Android al entorno de ejecución de RenderScript (no puedes asignar memoria en tu archivo .rs). Para obtener más información, consulta Cómo trabajar con memoria asignada.

API de asignación de memoria

Las aplicaciones que usan RenderScript igual se ejecutan en la VM de Android. Sin embargo, el código real de RenderScript se ejecuta de forma nativa y requiere acceso a la memoria asignada en la VM de Android. Para lograr esto, debes conectar la memoria que está asignada en la VM al entorno de ejecución de RenderScript. Este proceso, denominado vinculación, permite que el entorno de ejecución de RenderScript funcione sin problemas con la memoria que solicita, pero que no puede asignar explícitamente. El resultado final es en esencia el mismo que si hubieras llamado a malloc en C. El beneficio adicional es que la VM de Android puede llevar a cabo la recolección de elementos no utilizados y compartir la memoria con la capa de entorno de ejecución de RenderScript. La vinculación solo es necesaria para la memoria asignada de forma dinámica. La memoria asignada de manera estática se crea automáticamente para el código de RenderScript en el momento de la compilación. Consulta la figura 1 para obtener más información sobre cómo se realiza la asignación de memoria.

Para admitir este sistema de asignación de memoria, hay un conjunto de API que permiten que la VM de Android asigne memoria y proporcione una funcionalidad similar a una llamada malloc. Estas clases básicamente describen cómo se debe asignar la memoria y también llevan a cabo la asignación. Para comprender mejor cómo funcionan esas clases, es útil analizarlas en relación con una llamada malloc simple que puede ser así:

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

La llamada malloc se puede dividir en dos partes: el tamaño de la memoria que se asigna (sizeof(int)) junto con la cantidad de unidades de esa memoria que se deben asignar (10). El framework de Android proporciona clases para esas dos partes, así como una clase para representar el malloc.

La clase Element representa la parte (sizeof(int)) de la llamada malloc y encapsula una celda de una asignación de memoria, como un valor flotante único o una struct. La clase Type encapsula el Element y la cantidad de elementos para asignar (10 en nuestro ejemplo). Puedes entender un Type como un arreglo de Element. La clase Allocation realiza la asignación de memoria real en función de un Type determinado y representa la memoria asignada real.

En la mayoría de las situaciones, no es necesario llamar a estas API de asignación de memoria directamente. Las clases de capa reflejada generan código para usar estas API automáticamente, y todo lo que necesitas hacer para asignar memoria es llamar a un constructor que esté declarado en una de las clases de capa reflejada y vincular la memoria resultante Allocation a RenderScript. Hay algunas situaciones en las que querrías usar esas clases directamente para asignar memoria por tu cuenta, como para cargar un mapa de bits desde un recurso o asignar memoria para punteros a tipos primitivos. En la sección Cómo asignar y vincular la memoria a RenderScript, puedes consultar cómo hacerlo. En la siguiente tabla, se describen con más detalle las tres clases de administración de memoria:

Tipo Object de Android Descripción
Element

Un elemento describe una celda de una asignación de memoria y puede ser de dos formas: básico o complejo.

Un elemento básico incluye un único componente de datos de cualquier tipo de datos de RenderScript válido. Algunos ejemplos de tipos de datos de elementos básicos son un único valor float, un vector float4 o un solo color RGB-565.

Los elementos complejos tienen una lista de elementos básicos y se crean a partir de structs que declaras en el código de RenderScript. Por ejemplo, una asignación puede contener varias structs ordenadas en la memoria. Cada struct se considera como su propio elemento, en lugar de cada tipo de datos dentro de esa struct.

Type

Un tipo es una plantilla de asignación de memoria y tiene un elemento y una o más dimensiones. Describe el diseño de la memoria (básicamente un arreglo de Elements), pero no asigna la memoria para los datos que describe.

Un tipo consta de cinco dimensiones: X, Y, Z, LOD (nivel de detalle) y caras (de un mapa de cubo). Puedes establecer las dimensiones X, Y y Z en cualquier valor entero positivo dentro de las restricciones de la memoria disponible. Una asignación de una sola dimensión tiene una dimensión X mayor que cero, mientras que las dimensiones Y y Z son cero para indicar que no están presentes. Por ejemplo, una asignación de x=10, y=1 se considera bidimensional y x=10, y=0 se considera unidimensional. Las dimensiones LOD y caras son booleanos que indican si están o no presentes.

Allocation

Una asignación proporciona la memoria para las aplicaciones según una descripción de la memoria que se representa con un Type. La memoria asignada puede existir en muchos espacios de memoria al mismo tiempo. Si la memoria se modifica en un espacio, debes sincronizar explícitamente la memoria para que se actualice en todos los demás espacios en los que existe.

Los datos de asignación se cargan de una de dos formas principales: tipo marcado y tipo desmarcado. Para los arreglos simples, hay funciones copyFrom() que toman un arreglo del sistema Android y lo copian en el almacén de memoria de la capa nativa. Las variantes desmarcadas permiten que el sistema Android copie sobre arreglos de estructuras porque no admite estructuras. Por ejemplo, si hay una asignación que es un arreglo de n números de punto flotante, se pueden copiar los datos contenidos en un arreglo float[n] o en un arreglo byte[n*4].

Cómo trabajar con memoria

Las variables globales no estáticas que declaras en tu RenderScript tienen memoria asignada en el momento de la compilación. Puedes trabajar con estas variables directamente en tu código de RenderScript sin tener que asignarles memoria en el nivel del framework de Android. La capa del framework de Android también tiene acceso a estas variables con los métodos de acceso proporcionados que se generan en las clases de capa reflejada. Si estas variables se inicializan en la capa de entorno de ejecución de RenderScript, esos valores se utilizan para inicializar los valores correspondientes en la capa del framework de Android. Si las variables globales se marcan como const, no se genera un método set. Busca más detalles aquí.

Nota: Si utilizas ciertas estructuras de RenderScript que incluyen punteros, como rs_program_fragment y rs_allocation, primero debes obtener un objeto de la clase del framework de Android correspondiente y, luego, llamar al método set para que esa estructura vincule la memoria al entorno de ejecución de RenderScript. No puedes manipular directamente estas estructuras en la capa de entorno de ejecución de RenderScript. Esta restricción no se aplica a las estructuras definidas por el usuario que contienen punteros, porque no pueden exportarse a una clase de capa reflejada en primer lugar. Se genera un error de compilador si intentas declarar una struct global no estática que contiene un puntero.

RenderScript también admite punteros, pero debes asignar explícitamente la memoria en el código del framework de Android. Cuando declaras un puntero global en tu archivo .rs, asignas memoria por medio de la clase de capa reflejada apropiada y vinculas esa memoria a la capa RenderScript nativa. Puedes interactuar con esta memoria desde la capa del framework de Android, así como desde la capa de RenderScript, que te ofrece la flexibilidad para modificar las variables en la capa más adecuada.

Cómo asignar y vincular memoria dinámica al RenderScript

Para asignar memoria dinámica, debes llamar al constructor de una clase Script.FieldBase; esa es la forma más común. Una alternativa es crear una Allocation manualmente, lo cual es necesario para los punteros de tipo primitivo, entre otros elementos. Debes usar un constructor de clase Script.FieldBase siempre que esté disponible a fin de simplificar el proceso. Después de obtener una asignación de memoria, llama al método bind reflejado del puntero para vincular la memoria asignada al entorno de ejecución de RenderScript.

En el ejemplo incluido a continuación, se asigna memoria a un puntero de tipo primitivo, intPointer, y un puntero a una struct, touchPoints. También se vincula la memoria al 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);

   ...
}

Cómo leer y escribir en la memoria

Puedes leer y escribir en la memoria asignada de manera estática y dinámica tanto en el entorno de ejecución de RenderScript como en la capa del framework de Android.

La memoria asignada estáticamente viene con una restricción de comunicación unidireccional en el nivel del entorno de ejecución de RenderScript. Cuando el código de RenderScript cambia el valor de una variable, no se lo comunica a la capa del framework de Android por motivos de eficiencia. El último valor que se establece desde el framework de Android siempre se muestra durante una llamada a un método get. Sin embargo, cuando el código de framework de Android modifica una variable, ese cambio puede comunicarse al entorno de ejecución de RenderScript automáticamente o sincronizarse en otro momento. Si necesitas enviar datos desde el entorno de ejecución de RenderScript a la capa del framework de Android, puedes usar la función rsSendToClient() para superar esta limitación.

Cuando trabajas con memoria asignada dinámicamente, cualquier cambio en la capa de entorno de ejecución de RenderScript se propaga a la capa del framework de Android si modificaste la asignación de memoria con su puntero asociado. La modificación de un objeto en la capa del framework de Android propaga de inmediato ese cambio a la capa del entorno de ejecución de RenderScript.

Cómo leer y escribir en variables globales

Leer y escribir en variables globales es un proceso sencillo. Puedes usar los métodos de acceso en el nivel del framework de Android o configurarlos directamente en el código de RenderScript. Ten en cuenta que los cambios que realices en el código de RenderScript no se propagarán a la capa del framework de Android (obtén más información aquí).

Por ejemplo, dada la siguiente struct declarada en un archivo llamado rsfile.rs:

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

Point_t point;

Puedes asignar valores a la struct de esa forma directamente en rsfile.rs. Estos valores no se propagan al nivel del framework de Android:

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

Puedes asignar valores a la struct en la capa del framework de Android de esta manera. Estos valores se propagan nuevamente al nivel de entorno de ejecución de RenderScript de forma asíncrona:

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

Puedes leer los valores en tu código de RenderScript de la siguiente manera:

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

Puedes leer los valores en la capa del framework de Android con el siguiente código. Ten en cuenta que este código solo muestra un valor si se configuró en el nivel del framework de Android. Obtendrás una excepción de puntero nulo si solo estableces el valor en el nivel del entorno de ejecución de 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());

Cómo leer y escribir punteros globales

Si suponemos que se asignó memoria en el nivel del framework de Android y se vinculó al entorno de ejecución de RenderScript, puedes leer y escribir memoria desde el nivel del framework de Android utilizando los métodos get y set para ese puntero. En la capa del entorno de ejecución de RenderScript, puedes leer y escribir en la memoria con punteros de forma normal y los cambios se propagan a la capa del framework de Android, a diferencia de lo que ocurre con la memoria asignada estáticamente.

Por ejemplo, dado el siguiente puntero a una struct en un archivo llamado rsfile.rs:

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

Point_t *point;

Si ya asignaste memoria en la capa del framework de Android, puedes acceder a los valores en struct de manera normal. Cualquier cambio que realices en la struct a través de su variable de puntero estará automáticamente disponible para la capa del framework de Android:

Kotlin

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

Java

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

También puedes leer y escribir valores en el puntero de la capa del framework de 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);

Una vez que la memoria esté vinculada, no tendrás que volver a vincularla al entorno de ejecución de RenderScript cada vez que realices un cambio en un valor.