Script di rendering avanzato

Poiché le applicazioni che utilizzano RenderScript vengono comunque eseguite all'interno della VM Android, puoi accedere a tutte le API framework che conosci, ma puoi utilizzare RenderScript quando appropriato. Per facilitare questa interazione tra il framework e il runtime RenderScript, è presente anche un livello intermedio di codice per facilitare la comunicazione e la gestione della memoria tra i due livelli di codice. Questo documento analizza più nel dettaglio questi diversi livelli di codice e la modalità di condivisione della memoria tra la VM Android e il runtime di RenderScript.

Livello di runtime RenderScript

Il codice RenderScript viene compilato ed eseguito in un livello di runtime compatto e ben definito. Le API di runtime RenderScript offrono supporto per calcoli intensivi, portabili e scalabili automaticamente fino alla quantità di core disponibili su un processore.

Nota: è necessario garantire l'esecuzione delle funzioni C standard nell'NDK su una CPU, pertanto RenderScript non può accedere a queste librerie perché RenderScript è progettato per essere eseguito su diversi tipi di processori.

Definisci il codice RenderScript in file .rs e .rsh nella directory src/ del tuo progetto Android. Il codice viene compilato in bytecode intermedio dal compilatore llvm che viene eseguito come parte di una build Android. Quando la tua applicazione viene eseguita su un dispositivo, il bytecode viene quindi compilato (just-in-time) nel codice macchina da un altro compilatore llvm che risiede sul dispositivo. Il codice della macchina è ottimizzato per il dispositivo e memorizzato nella cache, quindi gli utilizzi successivi dell'applicazione abilitata per RenderScript non ricompilano il bytecode.

Alcune funzionalità principali delle librerie di runtime RenderScript includono:

  • Funzionalità per le richieste di allocazione della memoria
  • Un'ampia raccolta di funzioni matematiche con versioni sovraccaricate di tipo scalare e vettoriale di molte routine comuni. Sono disponibili operazioni come l'aggiunta, la moltiplicazione, il prodotto scalare e i prodotti incrociati, nonché le funzioni aritmetiche atomiche e di confronto.
  • Routine di conversione per vettori e tipi di dati primitivi, routine a matrici e routine di data e ora
  • Tipi di dati e strutture per supportare il sistema RenderScript, ad esempio i tipi Vector per la definizione di due, tre o quattro vettori.
  • Funzioni di logging

Per ulteriori informazioni sulle funzioni disponibili, consulta la documentazione di riferimento sull'API di runtime RenderScript.

Strato riflesso

Il livello riportato è un insieme di classi generate dagli strumenti di creazione di Android per consentire l'accesso al runtime RenderScript dal framework Android. Questo livello fornisce inoltre metodi e costruttori che consentono di allocare e lavorare con memoria per i puntatori definiti nel codice RenderScript. Il seguente elenco descrive i componenti principali interessati:

  • Ogni file .rs che crei viene generato in una classe denominata project_root/gen/package/name/ScriptC_renderscript_filename di tipo ScriptC. Questo file è la versione .java del file .rs, che puoi chiamare dal framework Android. Questa classe contiene i seguenti elementi riportati nel file .rs:
    • Funzioni non statiche
    • Variabili RenderScript globali non statiche. Per ogni variabile vengono generati metodi della funzione di accesso, quindi puoi leggere e scrivere le variabili RenderScript dal framework Android. Se una variabile globale viene inizializzata al livello di runtime RenderScript, questi valori vengono utilizzati per inizializzare i valori corrispondenti nel livello del framework Android. Se le variabili globali sono contrassegnate come const, non viene generato alcun metodo set. Leggi qui per ulteriori dettagli.

    • Puntatori globali
  • Un elemento struct si riflette nella propria classe denominata project_root/gen/package/name/ScriptField_struct_name, che si estende Script.FieldBase. Questa classe rappresenta un array di struct, che consente di allocare memoria per una o più istanze di struct.

Funzioni

Le funzioni si riflettono nella classe di script stessa, situata in project_root/gen/package/name/ScriptC_renderscript_filename. Ad esempio, se definisci la seguente funzione nel codice 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;
}

viene generato il seguente codice 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);
}

Le funzioni non possono avere valori restituiti perché il sistema RenderScript è progettato per essere asincrono. Quando il codice del framework Android chiama in RenderScript, la chiamata viene messa in coda e, quando possibile, viene eseguita. Questa limitazione consente al sistema RenderScript di funzionare senza interruzioni costanti, aumentando l'efficienza. Se alle funzioni fosse consentito restituire valori, la chiamata verrebbe bloccata fino a quando il valore non viene restituito.

Se vuoi che il codice RenderScript invii un valore al framework Android, utilizza la funzione rsSendToClient().

Variabili

Le variabili dei tipi supportati si riflettono nella classe di script stessa, situata in project_root/gen/package/name/ScriptC_renderscript_filename. Per ogni variabile viene generato un insieme di metodi della funzione di accesso. Ad esempio, se definisci la seguente variabile nel codice RenderScript:

uint32_t unsignedInteger = 1;

viene generato il seguente codice 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;
}
  

Strutture

Gli strut sono riportati nelle rispettive classi, situate in <project_root>/gen/com/example/renderscript/ScriptField_struct_name. Questa classe rappresenta un array di struct e consente di allocare memoria per un numero specificato di struct. Ad esempio, se definisci il seguente struct:

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

viene generato il seguente codice in 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 */);
    }
}

Il codice generato ti viene fornito per allocare memoria per gli struct richiesti dal runtime RenderScript e per interagire con gli struct in memoria. Ogni classe di struct definisce i seguenti metodi e costruttori:

  • Costruttori sovraccaricati che consentono di allocare memoria. Il costruttore ScriptField_struct_name(RenderScript rs, int count) consente di definire il numero di strutture per cui vuoi allocare memoria con il parametro count. Il costruttore ScriptField_struct_name(RenderScript rs, int count, int usages) definisce un parametro aggiuntivo, usages, che consente di specificare lo spazio di memoria per questa allocazione di memoria. Esistono quattro possibilità di spazio di memoria:
    • USAGE_SCRIPT: viene assegnato nello spazio di memoria dello script. Questo è lo spazio di memoria predefinito se non ne specifichi uno.
    • USAGE_GRAPHICS_TEXTURE: viene assegnato nello spazio di memoria della trama della GPU.
    • USAGE_GRAPHICS_VERTEX: viene allocato nello spazio di memoria totale della GPU.
    • USAGE_GRAPHICS_CONSTANTS: viene allocato nello spazio di memoria costante della GPU utilizzata dai vari oggetti di programma.

    Puoi specificare più spazi di memoria utilizzando l'operatore OR a livello di bit. In questo modo comunicherai al runtime RenderScript che intendi accedere ai dati negli spazi di memoria specificati. L'esempio seguente alloca memoria per un tipo di dati personalizzato sia negli spazi di script che di vertice di memoria:

    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 classe nidificata statica, Item, consente di creare un'istanza di struct sotto forma di oggetto. Questa classe nidificata è utile se ha più senso lavorare con struct nel codice Android. Una volta completata la manipolazione dell'oggetto, puoi eseguire il push dell'oggetto nella memoria allocata chiamando set(Item i, int index, boolean copyNow) e impostando Item nella posizione desiderata nell'array. Il runtime RenderScript ha automaticamente accesso alla memoria appena scritta.
  • Metodi della funzione di accesso per recuperare e impostare i valori di ogni campo in uno struct. Ciascuno di questi metodi della funzione di accesso ha un parametro index per specificare il valore struct nell'array in cui vuoi leggere o scrivere. Ogni metodo setter ha anche un parametro copyNow che specifica se sincronizzare immediatamente questa memoria con il runtime RenderScript. Per sincronizzare i ricordi che non sono stati sincronizzati, chiama copyAll().
  • Il metodo createElement() crea una descrizione dello struct in memoria. Questa descrizione viene utilizzata per allocare la memoria composta da uno o più elementi.
  • resize() funziona in modo molto simile a realloc() in C, consentendoti di espandere la memoria allocata in precedenza, mantenendo i valori attuali creati in precedenza.
  • copyAll() sincronizza la memoria impostata a livello di framework sul runtime di RenderScript. Quando chiami un metodo della funzione di accesso set su un membro, puoi specificare un parametro booleano copyNow facoltativo. Se specifichi true, la memoria viene sincronizzata quando chiami il metodo. Se specifichi false, puoi chiamare copyAll() una volta e sincronizzare la memoria per tutte le proprietà non ancora sincronizzate.

Puntatori

I puntatori globali si riflettono nella classe di script stessa, situata in project_root/gen/package/name/ScriptC_renderscript_filename. Puoi dichiarare i puntatori a un struct o a qualsiasi tipo di RenderScript supportato, ma un elemento struct non può contenere puntatori o array nidificati. Ad esempio, se definisci i seguenti puntatori a struct e int32_t

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

Point_t *touchPoints;
int32_t *intPointer;

viene generato il seguente codice 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;
}
  

Vengono generati un metodo get e un metodo speciale denominato bind_pointer_name (anziché un metodo set()). Il metodo bind_pointer_name ti consente di associare la memoria allocata nella VM Android al runtime RenderScript (non puoi allocare la memoria nel file .rs). Per ulteriori informazioni, consulta Utilizzo della memoria allocata.

API di allocazione della memoria

Le applicazioni che utilizzano RenderScript vengono comunque eseguite nella VM Android. Il codice RenderScript effettivo, tuttavia, viene eseguito in modo nativo e richiede l'accesso alla memoria allocata nella VM Android. A questo scopo, devi collegare la memoria allocata nella VM al runtime RenderScript. Questo processo, chiamato associazione, consente al runtime RenderScript di funzionare senza problemi con la memoria che richiede, ma che non può allocare esplicitamente. Il risultato finale è essenzialmente lo stesso che avresti chiamato malloc in C. Il vantaggio aggiuntivo è che la VM Android può eseguire garbage collection e condividere memoria con il livello di runtime RenderScript. L'associazione è necessaria solo per la memoria allocata dinamicamente. La memoria allocata in modo statico viene creata automaticamente per il codice RenderScript al momento della compilazione. Consulta la Figura 1 per ulteriori informazioni su come avviene l'allocazione della memoria.

Per supportare questo sistema di allocazione della memoria, esistono un insieme di API che consentono alla VM Android di allocare memoria e offrono funzionalità simili a una chiamata malloc. Queste classi descrivono essenzialmente come dovrebbe essere allocata la memoria ed eseguono anche l'allocazione. Per capire meglio come funzionano queste classi, è utile considerarle in relazione a una semplice chiamata malloc che può avere il seguente aspetto:

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

La chiamata malloc può essere suddivisa in due parti: la dimensione della memoria allocata (sizeof(int)) e il numero di unità di quella memoria che devono essere allocate (10). Il framework Android fornisce classi per queste due parti, nonché una classe per rappresentare la stessa malloc.

La classe Element rappresenta la parte (sizeof(int)) della chiamata malloc e incapsula una cella di allocazione della memoria, come un singolo valore in virgola mobile o uno struct. La classe Type incapsula Element e la quantità di elementi da allocare (10 nel nostro esempio). Puoi considerare Type come un array di Element. La classe Allocation esegue l'allocazione effettiva della memoria in base a un determinato Type e rappresenta la memoria effettivamente allocata.

Nella maggior parte dei casi, non è necessario chiamare direttamente queste API di allocazione della memoria. Le classi di livello riflesse generano il codice per l'utilizzo automatico di queste API. Per allocare la memoria, è sufficiente chiamare un costruttore dichiarato in una delle classi di livello riflesse e associare la memoria risultante Allocation a RenderScript. In alcune situazioni potresti voler utilizzare queste classi direttamente per allocare la memoria in modo autonomo, ad esempio caricando una bitmap da una risorsa o quando vuoi allocare la memoria per i puntatori ai tipi primitivi. Puoi vedere come eseguire questa operazione nella sezione Allocazione e associazione della memoria a RenderScript. La tabella seguente descrive in maggiore dettaglio le tre classi di gestione della memoria:

Tipo di oggetto Android Descrizione
Element

Un elemento descrive una cella di un'allocazione della memoria e può avere due forme: base o complessa.

Un elemento di base contiene un singolo componente di dati di qualsiasi tipo di dati RenderScript valido. Esempi di tipi di dati di elementi di base includono un singolo valore float, un vettore float4 o un singolo colore RGB-565.

Gli elementi complessi contengono un elenco di elementi di base e vengono creati dagli elementi struct dichiarati nel codice RenderScript. Ad esempio, un'allocazione può contenere più struct disposti in ordine in memoria. Ogni struct viene considerato come un elemento a sé, anziché come ogni tipo di dati all'interno dello struct.

Type

Un tipo è un modello di allocazione della memoria ed è costituito da un elemento e da una o più dimensioni. Descrive il layout della memoria (sostanzialmente un array di Element), ma non alloca la memoria per i dati che descrive.

Un tipo è costituito da cinque dimensioni: X, Y, Z, LOD (livello di dettaglio) e Volti (di una mappa cubica). Puoi impostare le dimensioni X, Y e Z su qualsiasi valore intero positivo entro i limiti di memoria disponibile. L'allocazione di una singola dimensione ha una dimensione X maggiore di zero, mentre le dimensioni Y e Z sono zero per indicare che non è presente. Ad esempio, un'allocazione di x=10, y=1 è bidimensionale, mentre x=10 e y=0 è monodimensionale. Le dimensioni LOD e Volti sono booleani per indicare che sono presenti o meno.

Allocation

Un'allocazione fornisce la memoria per le applicazioni in base a una descrizione della memoria rappresentata da un Type. La memoria allocata può trovarsi contemporaneamente in molti spazi di memoria. Se la memoria viene modificata in uno spazio, devi sincronizzarla esplicitamente, in modo che venga aggiornata in tutti gli altri spazi in cui esiste.

I dati di allocazione vengono caricati in uno dei due seguenti modi principali: tipo selezionato e tipo deselezionato. Per gli array semplici esistono funzioni copyFrom() che acquisiscono un array dal sistema Android e lo copiano nell'archivio di memoria del livello nativo. Le varianti deselezionate consentono al sistema Android di copiare array di strutture perché non supporta le strutture. Ad esempio, se esiste un'allocazione che è un array di n valori in virgola mobile, è possibile copiare i dati contenuti in un array con virgola mobile[n] o in un array byte[n*4].

Utilizzo della memoria

Le variabili globali non statiche dichiarate in RenderScript sono memoria allocata al momento della compilazione. Puoi lavorare con queste variabili direttamente nel codice RenderScript senza dover allocare memoria a livello di framework Android. Anche il livello del framework Android ha accesso a queste variabili con i metodi della funzione di accesso forniti che vengono generati nelle classi dei livelli riflesse. Se queste variabili vengono inizializzate al livello di runtime RenderScript, tali valori vengono utilizzati per inizializzare i valori corrispondenti nel livello del framework Android. Se le variabili globali sono contrassegnate come const, non viene generato alcun metodo set. Leggi qui per ulteriori dettagli.

Nota: se utilizzi determinate strutture RenderScript che contengono puntatori, come rs_program_fragment e rs_allocation, devi prima ottenere un oggetto della classe di framework Android corrispondente, quindi chiamare il metodo set per tale struttura per associare la memoria al runtime di RenderScript. Non puoi manipolare direttamente queste strutture al livello di runtime di RenderScript. Questa restrizione non si applica alle strutture definite dall'utente che contengono puntatori, perché non possono essere prima esportate in una classe di livello riflessa. Se tenti di dichiarare uno struct globale non statico che contiene un puntatore, viene generato un errore del compilatore.

RenderScript supporta anche i puntatori, ma devi allocare esplicitamente la memoria nel codice del framework Android. Quando dichiari un puntatore globale nel file .rs, devi allocare la memoria tramite la classe del livello riflessa appropriata e associare quella memoria al livello RenderScript nativo. Puoi interagire con questa memoria dal livello del framework Android e dal livello RenderScript, che offre la flessibilità di modificare le variabili nel livello più appropriato.

Allocazione e associazione della memoria dinamica a RenderScript

Per allocare memoria dinamica, devi chiamare il costruttore di una classe Script.FieldBase, che è il metodo più comune. Un'alternativa è quella di creare manualmente una Allocation, necessaria per elementi come i puntatori di tipo primitivo. Per semplicità, utilizza un costruttore della classe Script.FieldBase, se disponibile. Dopo aver ottenuto un'allocazione della memoria, chiama il metodo bind riflesso del puntatore per associare la memoria allocata al runtime RenderScript.

L'esempio seguente alloca memoria sia per un puntatore di tipo primitivo, intPointer, sia per un puntatore a uno struct, touchPoints. Inoltre, associa la memoria al file 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);

   ...
}

Lettura e scrittura a memoria

Puoi leggere e scrivere nella memoria allocata in modo statico e dinamico sia a livello di runtime di RenderScript sia a livello del framework Android.

La memoria allocata in modo statico è dotata di una limitazione di comunicazione unidirezionale a livello di runtime di RenderScript. Quando il codice RenderScript modifica il valore di una variabile, questo non viene comunicato al livello del framework Android per motivi di efficienza. L'ultimo valore impostato dal framework Android viene sempre restituito durante una chiamata a un metodo get. Tuttavia, quando il codice del framework Android modifica una variabile, questa modifica può essere comunicata automaticamente al runtime RenderScript o sincronizzata in un secondo momento. Se devi inviare dati dal runtime RenderScript al livello del framework Android, puoi utilizzare la funzione rsSendToClient() per superare questo limite.

Quando si lavora con memoria allocata dinamicamente, eventuali modifiche al livello di runtime RenderScript vengono propagate al livello del framework Android se hai modificato l'allocazione della memoria utilizzando il puntatore associato. La modifica di un oggetto a livello del framework Android propaga immediatamente la modifica al livello di runtime RenderScript.

Lettura e scrittura di variabili globali

Leggere e scrivere a variabili globali è un processo semplice. Puoi utilizzare i metodi della funzione di accesso a livello di framework Android o impostarli direttamente nel codice RenderScript. Tieni presente che eventuali modifiche apportate al codice RenderScript non vengono propagate al livello del framework Android (ulteriori dettagli qui).

Ad esempio, dato il seguente struct dichiarato in un file denominato rsfile.rs:

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

Point_t point;

Puoi assegnare valori allo struct in questo modo direttamente in rsfile.rs. Questi valori non vengono propagati a livello di framework Android:

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

Puoi assegnare valori allo struct a livello del framework Android in questo modo. Questi valori vengono propagati al livello di runtime RenderScript in modo asincrono:

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

Puoi leggere i valori nel codice RenderScript come segue:

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

Puoi leggere i valori nel livello del framework Android con il codice seguente. Tieni presente che questo codice restituisce un valore solo se ne è stato impostato uno a livello di framework Android. Si otterrà un'eccezione di puntatore nullo se imposti il valore solo al livello di runtime di 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());

Lettura e scrittura di puntatori globali

Supponendo che la memoria sia stata allocata a livello di framework Android e associata al runtime RenderScript, puoi leggere e scrivere memoria a livello di framework Android utilizzando i metodi get e set per quel puntatore. Nel livello di runtime RenderScript puoi leggere e scrivere in memoria con puntatori normalmente e le modifiche vengono propagate al livello del framework Android, a differenza della memoria allocata in modo statico.

Ad esempio, dato il seguente puntatore a un struct in un file denominato rsfile.rs:

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

Point_t *point;

Supponendo che la memoria sia già allocata al livello del framework Android, puoi accedere ai valori in struct come di consueto. Qualsiasi modifica apportata allo struct tramite la relativa variabile di puntatore è automaticamente disponibile per il livello del framework Android:

Kotlin

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

Java

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

Puoi leggere e scrivere valori sul puntatore anche a livello del 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);

Una volta che la memoria è già stata associata, non è necessario riassociarla al runtime RenderScript ogni volta che apporti una modifica a un valore.