RenderScript avançado

Como os apps que usam o RenderScript ainda são executados na VM do Android, você tem acesso a todas as APIs de framework que já conhece, mas pode utilizar o RenderScript quando necessário. Para facilitar essa interação entre o framework e o ambiente de execução do RenderScript, uma camada intermediária de código também está presente para facilitar a comunicação e o gerenciamento de memória entre os dois níveis de código. Este documento aborda mais detalhes sobre essas diferentes camadas de código, além de como a memória é compartilhada entre a VM do Android e o ambiente de execução do RenderScript.

Camada do ambiente de execução do RenderScript

O código do RenderScript é compilado e executado em uma camada do ambiente de execução compacta e bem definida. As APIs do ambiente de execução do RenderScript oferecem suporte para computação intensiva que é portátil e pode ser escalonada automaticamente de acordo com a quantidade de núcleos disponíveis em um processador.

Observação: as funções C padrão no NDK precisam ser executadas em uma CPU. Portanto, o RenderScript não pode acessar essas bibliotecas, porque o RenderScript foi desenvolvido para ser executado em diferentes tipos de processador.

Você define o código do RenderScript nos arquivos .rs e .rsh do diretório src/ do seu projeto Android. O código é compilado para o bytecode intermediário pelo compilador llvm que é executado como parte de um build do Android. Quando seu app é executado em um dispositivo, o bytecode é compilado (just-in-time) para o código de máquina por outro compilador llvm que reside no dispositivo. O código de máquina é otimizado para o dispositivo e também armazenado em cache, de modo que os usos subsequentes do app compatível com RenderScript não recompilam o bytecode.

Alguns dos principais recursos das bibliotecas de ambiente de execução do RenderScript incluem:

  • recursos de solicitação de alocação de memória;
  • uma grande coleção de funções matemáticas com versões sobrecarregadas de variáveis escalares e vetoriais de muitas rotinas comuns; operações como adição, multiplicação, produto escalar e produto vetorial estão disponíveis, assim como funções aritméticas e de comparação atômicas;
  • rotinas de conversão para tipos e vetores de dados primitivos, rotinas de matriz e rotinas de data e hora;
  • tipos de dados e estruturas compatíveis com o sistema RenderScript, por exemplo, tipos vetoriais, para definir dois, três ou quatro vetores;
  • funções de registro.

Consulte a referência da API do ambiente de execução do RenderScript para saber mais sobre as funções disponíveis.

Camada refletida

A camada refletida é um conjunto de classes que as ferramentas de criação do Android geram para permitir acesso ao ambiente de execução do RenderScript a partir do framework do Android. Essa camada também fornece métodos e construtores que permitem alocar e trabalhar com memória para ponteiros que são definidos no código do RenderScript. A lista a seguir descreve os principais componentes que são refletidos.

  • Todo arquivo .rs que você cria é gerado em uma classe chamada project_root/gen/package/name/ScriptC_renderscript_filename do tipo ScriptC. Esse arquivo é a versão .java do seu arquivo .rs, que você pode chamar a partir do framework do Android. Essa classe contém os seguintes itens refletidos do arquivo .rs:
    • Funções não estáticas
    • Variáveis globais não estáticas do RenderScript. Os métodos do acessador são gerados para cada variável, de modo que você pode ler e gravar variáveis do RenderScript a partir do framework do Android. Se uma variável global for inicializada na camada do ambiente de execução do RenderScript, esses valores serão usados para inicializar os valores correspondentes na camada de framework do Android. Se as variáveis globais estiverem marcadas como const, um método set não será gerado. Veja aqui para saber mais detalhes.

    • Ponteiros globais
  • Um struct é refletido na própria classe chamada project_root/gen/package/name/ScriptField_struct_name, que estende Script.FieldBase. Essa classe representa uma matriz de struct, que permite alocar memória para uma ou mais instâncias desse struct.

Funções

As funções são refletidas na própria classe de script, localizada em project_root/gen/package/name/ScriptC_renderscript_filename. Por exemplo, se você definir a seguinte função no código do 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;
}

o seguinte código Java será gerado:

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

As funções não podem ter valores de retorno porque o sistema RenderScript é projetado para ser assíncrono. Quando o código do framework do Android é chamado para o RenderScript, a chamada é colocada na fila e executada quando possível. Essa restrição permite que o sistema RenderScript funcione sem interrupção constante e aumenta a eficiência. Se as funções pudessem ter valores de retorno, a chamada seria bloqueada até que o valor fosse retornado.

Se você quiser que o código do RenderScript envie um valor de volta ao framework do Android, use a função rsSendToClient().

Variáveis

As variáveis de tipos compatíveis são refletidas na própria classe de script, localizada em project_root/gen/package/name/ScriptC_renderscript_filename. Um conjunto de métodos do acessador é gerado para cada variável. Por exemplo, se você definir a seguinte variável no código do RenderScript:

uint32_t unsignedInteger = 1;

o seguinte código Java será gerado:

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

public long get_unsignedInteger(){
    return mExportVar_unsignedInteger;
}
  

Estruturas

As estruturas são refletidas em suas próprias classes, localizadas em <project_root>/gen/com/example/renderscript/ScriptField_struct_name. Essa classe representa uma matriz de struct e permite que você aloque memória para um número especificado de structs. Por exemplo, se você definir a seguinte estrutura:

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

o seguinte código será gerado em 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 */);
    }
}

O código gerado é fornecido como uma conveniência para alocar memória para estruturas solicitadas pelo ambiente de execução do RenderScript e para interagir com structs na memória. Cada classe de struct define os seguintes métodos e construtores:

  • Construtores sobrecarregados que permitem alocar memória. O construtor ScriptField_struct_name(RenderScript rs, int count) permite que você defina o número de estruturas para as quais quer alocar memória com o parâmetro count. O construtor ScriptField_struct_name(RenderScript rs, int count, int usages) define um parâmetro extra, usages, que permite especificar o espaço de memória dessa alocação de memória. Há quatro possibilidades de espaço de memória:
    • USAGE_SCRIPT: aloca no espaço de memória do script. Este será o espaço de memória padrão se você não especificar um espaço de memória.
    • USAGE_GRAPHICS_TEXTURE: aloca no espaço de memória da textura da GPU.
    • USAGE_GRAPHICS_VERTEX: aloca no espaço de memória de vértice da GPU.
    • USAGE_GRAPHICS_CONSTANTS: aloca no espaço de memória das constantes da GPU usado pelos vários objetos do programa.

    Você pode especificar vários espaços de memória usando o operador OR bit a bit. Isso notifica o ambiente de execução do RenderScript que você pretende acessar os dados nos espaços de memória especificados. O exemplo a seguir aloca memória para um tipo de dados personalizado nos espaços de memória de script e vértice:

    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);
    
  • Uma classe aninhada estática, Item, permite que você crie uma instância de struct, na forma de um objeto. Essa classe aninhada é útil se faz mais sentido trabalhar com struct no código do Android. Quando terminar de manipular o objeto, você poderá enviar o objeto para a memória alocada chamando set(Item i, int index, boolean copyNow) e definindo o Item como a posição desejada na matriz. O ambiente de execução do RenderScript tem acesso automático à memória recém-criada.
  • Métodos do acessador para ver e definir os valores de cada campo em uma estrutura. Cada um desses métodos do acessador tem um parâmetro index para especificar o struct na matriz que você quer ler ou gravar. Cada método setter também tem um parâmetro copyNow que especifica se a memória precisa ser sincronizada imediatamente com o ambiente de execução do RenderScript. Para sincronizar qualquer memória que não tenha sido sincronizada, chame copyAll().
  • O método createElement() cria uma descrição da estrutura na memória. Essa descrição é usada para alocar memória que consiste em um ou mais elementos.
  • resize() funciona como um realloc() em C, o que permite expandir a memória alocada anteriormente, mantendo os valores atuais que foram criados antes.
  • copyAll() sincroniza a memória que foi definida no nível do framework com o ambiente de execução do RenderScript. Quando você chama um método do acessador definido em um membro, há um parâmetro booleano copyNow opcional que pode ser especificado. Especificar true sincroniza a memória quando você chama o método. Se você especificar "falso", poderá chamar copyAll() uma vez e sincronizará a memória para todas as propriedades que ainda não estiverem sincronizadas.

Ponteiros

Ponteiros globais são refletidos na própria classe de script, localizada em project_root/gen/package/name/ScriptC_renderscript_filename. Você pode declarar ponteiros para um struct ou qualquer um dos tipos de RenderScript compatíveis, mas um struct não pode conter ponteiros ou matrizes aninhadas. Por exemplo, se você definir os seguintes ponteiros para struct e int32_t,

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

Point_t *touchPoints;
int32_t *intPointer;

o seguinte código Java será gerado:

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

Um método get e um método especial chamado bind_pointer_name (em vez de um método set()) são gerados. O método bind_pointer_name permite que você vincule a memória alocada na VM do Android ao ambiente de execução do RenderScript (não é possível alocar memória no arquivo .rs). Para saber mais, consulte Como trabalhar com memória alocada.

APIs de alocação de memória

Apps que usam o RenderScript ainda são executados na VM do Android. No entanto, o código do RenderScript real é executado nativamente e precisa de acesso à memória alocada na VM do Android. Para fazer isso, anexe a memória alocada na VM ao ambiente de execução do RenderScript. Esse processo, chamado de "vinculação", permite que o ambiente de execução do RenderScript funcione perfeitamente com a memória solicitada, mas não pode alocar explicitamente. O resultado final é basicamente o mesmo que se você tivesse chamado malloc em C. O benefício adicional é que a VM do Android pode realizar a coleta de lixo e também compartilhar a memória com a camada de ambiente de execução do RenderScript. A vinculação só é necessária para a memória alocada dinamicamente. A memória alocada estaticamente é criada automaticamente para o código do RenderScript no momento da compilação. Veja a Figura 1 para saber mais sobre como ocorre a alocação de memória.

Para oferecer compatibilidade com esse sistema de alocação de memória, um conjunto de APIs permite que a VM do Android aloque memória e ofereça funcionalidade semelhante a uma chamada de malloc. Basicamente, essas classes descrevem como a memória precisa ser alocada e também realizam a alocação. Para entender melhor como essas classes funcionam, é útil pensar nelas em relação a uma chamada de malloc simples que pode ter esta aparência:

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

A chamada de malloc pode ser dividida em duas partes: o tamanho da memória alocada (sizeof(int)) e o total de unidades da memória que precisam ser alocadas (10). O framework do Android fornece classes para essas duas partes, bem como uma classe para representar malloc.

A classe Element representa a parte (sizeof(int)) da chamada de malloc e encapsula uma célula de uma alocação de memória, por exemplo, um único valor flutuante ou uma estrutura. A classe Type encapsula a classe Element e a quantidade de elementos a serem alocados (10 no nosso exemplo). Você pode pensar em Type como uma matriz de Elements. A classe Allocation realiza a alocação real de memória com base em um determinado Type e representa a memória real alocada.

Na maioria das situações, você não precisa chamar essas APIs de alocação de memória diretamente. As classes de camada refletidas geram código para usar essas APIs automaticamente. Tudo o que você precisa fazer para alocar memória é chamar um construtor declarado em uma das classes de camada refletidas e vincular a memória resultante Allocation ao RenderScript. Há algumas situações em que convém usar essas classes diretamente para alocar memória por conta própria, por exemplo, para carregar um bitmap de um recurso ou quando você quer alocar memória para ponteiros de tipos primitivos. Veja como fazer isso na seção Como alocar e vincular memória ao RenderScript. A tabela a seguir descreve as três classes de gerenciamento de memória em mais detalhes:

Tipo de objeto Android Descrição
Element

Um elemento descreve uma célula de uma alocação de memória e pode ter duas formas: básica ou complexa.

Um elemento básico contém um único componente de dados de qualquer tipo de dados válido do RenderScript. Exemplos de tipos de dados de elementos básicos incluem um valor float único, um vetor float4 ou uma única cor RGB-565.

Elementos complexos contêm uma lista de elementos básicos e são criados a partir de structs que você declara no código do RenderScript. Por exemplo, uma alocação pode conter vários structs organizados em ordem na memória. Cada estrutura é considerada como um elemento próprio, em vez de cada tipo de dados dentro dessa estrutura.

Type

Um tipo é um modelo de alocação de memória e consiste em um elemento e uma ou mais dimensões. Ele descreve o layout da memória (basicamente uma matriz de Elements), mas não aloca a memória para os dados descritos.

Um tipo consiste em cinco dimensões: X, Y, Z, LOD (nível de detalhe) e faces (de um mapa de cubos). Você pode definir as dimensões X, Y, Z como qualquer valor inteiro positivo dentro das restrições da memória disponível. Uma alocação de dimensão única tem uma dimensão X maior que zero, enquanto as dimensões Y e Z são zero para indicar que não estão presentes. Por exemplo, uma alocação de x=10, y=1 é considerada bidimensional, e x=10, y=0 é considerada unidimensional. As dimensões LOD e Faces são booleanas para indicar que estão presentes ou não.

Allocation

Uma alocação fornece a memória para apps com base em uma descrição da memória que é representada por um Type. A memória alocada pode existir em muitos espaços de memória ao mesmo tempo. Se a memória for modificada em um espaço, você precisará sincronizar explicitamente a memória, para que ela seja atualizada em todos os outros espaços em que existir.

Os dados de alocação são enviados de duas maneiras principais: tipo marcado e desmarcado. Para matrizes simples, há funções de copyFrom() que pegam uma matriz do sistema Android e a copiam para o armazenamento de memória da camada nativa. As variantes desmarcadas permitem que o sistema Android copie em matrizes de estruturas, porque ele não é compatível com estruturas. Por exemplo, se houver uma alocação que seja uma matriz de n valores flutuantes, os dados contidos em uma matriz float[n] ou uma matriz byte[n*4] poderão ser copiados.

Como trabalhar com memória

As variáveis globais não estáticas que você declara no RenderScript são alocadas na memória no momento da compilação. Você pode trabalhar com essas variáveis diretamente no código do RenderScript sem ter que alocar memória para elas no nível do framework do Android. A camada do framework do Android também tem acesso a essas variáveis com os métodos do acessador fornecidos que são gerados nas classes de camada refletidas. Se essas variáveis forem inicializadas na camada de ambiente de execução do RenderScript, esses valores serão usados para inicializar os valores correspondentes na camada do framework do Android. Se as variáveis globais forem marcadas como constantes, um set não será gerado. Veja mais detalhes aqui.

Observação: se você estiver usando algumas estruturas do RenderScript que contenham ponteiros, como rs_program_fragment e rs_allocation, primeiro será necessário acessar um objeto da classe de framework do Android correspondente e, depois, chamar o método set para essa estrutura para vincular a memória ao ambiente de execução do RenderScript. Não é possível manipular diretamente essas estruturas na camada de ambiente de execução do RenderScript. Essa restrição não é aplicável a estruturas definidas pelo usuário que contêm ponteiros, porque elas não podem ser exportadas para uma classe de camada refletida em primeiro lugar. Um erro de compilador será gerado se você tentar declarar uma estrutura global não estática que contém um ponteiro.

O RenderScript também é compatível com ponteiros, mas você precisa alocar explicitamente a memória no código do framework do Android. Quando você declara um ponteiro global no arquivo .rs, aloca memória usando a classe de camada refletida apropriada e vincula essa memória à camada do RenderScript nativa. Você pode interagir com essa memória na camada do framework do Android, bem como na camada do RenderScript, o que oferece a flexibilidade de modificar variáveis na camada mais apropriada.

Como alocar e vincular memória dinâmica ao RenderScript

Para alocar memória dinâmica, você precisa chamar o construtor de uma classe Script.FieldBase, que é a maneira mais comum. Uma alternativa é criar um Allocation manualmente, o que é necessário, por exemplo, para ponteiros de tipo primitivo. Para simplificar, use um construtor de classe Script.FieldBase sempre que estiver disponível. Depois de fazer uma alocação de memória, chame o método refletido bind do ponteiro para vincular a memória alocada ao ambiente de execução do RenderScript.

O exemplo abaixo aloca memória para um ponteiro de tipo primitivo, intPointer, e um ponteiro para uma estrutura, touchPoints. Ele também vincula a memória ao 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);

   ...
}

Como ler e gravar na memória

Você pode ler e gravar em memória alocada estática e dinamicamente no ambiente de execução do RenderScript e na camada do framework do Android.

A memória alocada estaticamente é fornecida com uma restrição de comunicação unidirecional no nível do ambiente de execução do RenderScript. Quando o código do RenderScript altera o valor de uma variável, a alteração não é transmitida de volta para a camada do framework do Android para melhorar a eficiência. O último valor definido no framework do Android é sempre retornado durante uma chamada para um método get. No entanto, quando o código do framework do Android modifica uma variável, essa alteração pode ser transmitida ao ambiente de execução do RenderScript automaticamente ou sincronizada posteriormente. Se você precisar enviar dados do ambiente de execução do RenderScript para a camada do framework do Android, poderá usar a função rsSendToClient() para superar essa limitação.

Ao trabalhar com memória alocada dinamicamente, as alterações na camada de ambiente de execução do RenderScript serão propagadas de volta para a camada do framework do Android se você tiver modificado a alocação de memória usando o ponteiro associado. Modificar um objeto na camada do framework do Android propaga imediatamente essa alteração para a camada de ambiente de execução do RenderScript.

Como ler e gravar em variáveis globais

Ler e gravar em variáveis globais é um processo simples. Você pode usar os métodos do acessador no nível do framework do Android ou defini-los diretamente no código do RenderScript. Lembre-se de que as alterações feitas no código do RenderScript não são propagadas de volta para a camada do framework do Android. Saiba mais detalhes aqui.

Por exemplo, dado o seguinte struct declarado em um arquivo chamado rsfile.rs:

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

Point_t point;

Você pode atribuir valores a um struct como este diretamente em rsfile.rs. Esses valores não são propagados de volta para o nível do framework do Android:

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

Você pode atribuir valores ao struct na camada do framework do Android da seguinte forma. Esses valores são propagados de volta para o nível do ambiente de execução do RenderScript de forma assí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);

Você pode ler os valores no código do RenderScript da seguinte maneira:

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

Você pode ler os valores na camada do framework do Android com o código a seguir. Lembre-se de que esse código só retornará um valor se um tiver sido definido no nível do framework do Android. Você verá uma exceção de ponteiro nulo se definir apenas o valor no nível do ambiente de execução do 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());

Como ler e gravar ponteiros globais

Supondo que a memória tenha sido alocada no nível do framework do Android e vinculada ao ambiente de execução do RenderScript, você pode ler e gravar no nível do framework do Android usando os métodos get e set para esse ponteiro. Na camada de ambiente de execução do RenderScript, você pode ler e gravar na memória com ponteiros normalmente, e as alterações são propagadas de volta para a camada do framework do Android, diferente do que acontece com a memória alocada estaticamente.

Por exemplo, considerando o seguinte ponteiro para um struct em um arquivo chamado rsfile.rs:

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

Point_t *point;

Supondo que você já tenha alocado memória na camada do framework do Android, pode acessar valores em struct como de costume. Todas as alterações feitas na estrutura pela variável de ponteiro estão automaticamente disponíveis para a camada do framework do Android:

Kotlin

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

Java

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

Também é possível ler e gravar valores no ponteiro da camada do framework do 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);

Depois que a memória já estiver vinculada, você não precisará vincular novamente a memória ao ambiente de execução do RenderScript sempre que fizer uma alteração em um valor.