Erweitertes RenderScript

Anwendungen, die RenderScript nutzen, werden weiterhin innerhalb der Android-VM ausgeführt. Du hast also Zugriff auf alle Framework-APIs, mit denen du vertraut bist. Du kannst bei Bedarf jedoch RenderScript nutzen. Um diese Interaktion zwischen dem Framework und der RenderScript-Laufzeit zu erleichtern, ist auch eine Zwischenebene mit Code vorhanden, die die Kommunikation und Speicherverwaltung zwischen den beiden Codeebenen erleichtert. In diesem Dokument werden diese verschiedenen Codeebenen ausführlicher beschrieben. Außerdem wird erläutert, wie der Arbeitsspeicher zwischen der Android-VM und der RenderScript-Laufzeit aufgeteilt wird.

RenderScript-Laufzeitebene

Ihr RenderScript-Code wird in einer kompakten und klar definierten Laufzeitebene kompiliert und ausgeführt. Die RenderScript-Laufzeit-APIs unterstützen intensive Berechnungen, die portierbar und automatisch auf die Anzahl der auf einem Prozessor verfügbaren Kerne skaliert werden können.

Hinweis:Die Standard-C-Funktionen im NDK müssen garantiert auf einer CPU ausgeführt werden, damit RenderScript nicht auf diese Bibliotheken zugreifen kann, da RenderScript für verschiedene Prozessortypen entwickelt wurde.

Du definierst den RenderScript-Code in den Dateien .rs und .rsh im Verzeichnis src/ deines Android-Projekts. Der Code wird vom llvm-Compiler, der als Teil eines Android-Builds ausgeführt wird, in Zwischenbytecode kompiliert. Wenn Ihre Anwendung auf einem Gerät ausgeführt wird, wird der Bytecode dann (Just-in-Time) von einem anderen llvm-Compiler, der sich auf dem Gerät befindet, in Maschinencode kompiliert. Der Maschinencode ist für das Gerät optimiert und auch im Cache gespeichert, sodass bei späteren Verwendungen der RenderScript-fähigen Anwendung der Bytecode nicht neu kompiliert wird.

Zu den wichtigsten Funktionen der RenderScript-Laufzeitbibliotheken gehören:

  • Features von Anfragen zur Arbeitsspeicherzuweisung
  • Eine große Sammlung von mathematischen Funktionen mit sowohl skalaren als auch vektortypisierten, überlasteten Versionen vieler gängiger Routinen. Es sind Vorgänge wie Addition, Multiplikation, Punktprodukt und Kreuzprodukt sowie Funktionen für atomare Arithmetik und Vergleich verfügbar.
  • Konvertierungsroutinen für primitive Datentypen und Vektoren, Matrixroutinen sowie Datums- und Uhrzeitroutinen
  • Datentypen und -strukturen zur Unterstützung des RenderScript-Systems, z. B. Vektortypen zum Definieren von zwei, drei oder vier Vektoren
  • Logging-Funktionen

Weitere Informationen zu den verfügbaren Funktionen finden Sie in der Referenz zur RenderScript Runtime API.

Reflexionsschicht

Die reflektierte Ebene besteht aus einer Reihe von Klassen, die von den Android-Build-Tools generiert werden, um den Zugriff auf die RenderScript-Laufzeit aus dem Android-Framework zu ermöglichen. Diese Ebene bietet auch Methoden und Konstruktoren, mit denen Sie Arbeitsspeicher für Zeiger zuweisen und mit ihnen arbeiten können, die in Ihrem RenderScript-Code definiert sind. In der folgenden Liste werden die wichtigsten Komponenten beschrieben:

  • Jede von Ihnen erstellte .rs-Datei wird in einer Klasse namens project_root/gen/package/name/ScriptC_renderscript_filename vom Typ ScriptC generiert. Dies ist die .java-Version Ihrer .rs-Datei, die Sie aus dem Android-Framework aufrufen können. Diese Klasse enthält die folgenden Elemente aus der Datei .rs:
    • Nicht-statische Funktionen
    • Nicht statische, globale RenderScript-Variablen Für jede Variable werden Zugriffsmethoden generiert, sodass Sie die RenderScript-Variablen aus dem Android-Framework lesen und schreiben können. Wenn eine globale Variable auf der RenderScript-Laufzeitebene initialisiert wird, werden diese Werte verwendet, um die entsprechenden Werte in der Android-Framework-Ebene zu initialisieren. Wenn globale Variablen als const gekennzeichnet sind, wird keine set-Methode generiert. Weitere Informationen

    • Globale Cursor
  • Eine struct spiegelt sich in einer eigenen Klasse namens project_root/gen/package/name/ScriptField_struct_name wider, die Script.FieldBase erweitert. Diese Klasse stellt ein Array von struct dar, mit dem Sie Arbeitsspeicher für eine oder mehrere Instanzen dieser struct zuweisen können.

Funktionen

Funktionen werden in der Skriptklasse selbst in project_root/gen/package/name/ScriptC_renderscript_filename wiedergegeben. Angenommen, Sie definieren die folgende Funktion in Ihrem RenderScript-Code:

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

wird der folgende Java-Code generiert:

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

Funktionen können keine Rückgabewerte haben, da das RenderScript-System asynchron ausgelegt ist. Wenn Ihr Android-Framework-Code RenderScript aufruft, wird der Aufruf in die Warteschlange gestellt und nach Möglichkeit ausgeführt. Durch diese Einschränkung kann das RenderScript-System ohne ständige Unterbrechung funktionieren und die Effizienz steigern. Wenn für Funktionen Rückgabewerte zulässig wären, wird der Aufruf blockiert, bis der Wert zurückgegeben wurde.

Wenn Sie möchten, dass der RenderScript-Code einen Wert an das Android-Framework zurücksendet, verwenden Sie die Funktion rsSendToClient().

Variablen

Variablen unterstützter Typen werden in der Skriptklasse selbst in project_root/gen/package/name/ScriptC_renderscript_filename wiedergegeben. Für jede Variable wird eine Reihe von Zugriffsmethoden generiert. Angenommen, Sie definieren die folgende Variable in Ihrem RenderScript-Code:

uint32_t unsignedInteger = 1;

wird der folgende Java-Code generiert:

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

public long get_unsignedInteger(){
    return mExportVar_unsignedInteger;
}
  

Strukturen

Structs werden in eigenen Klassen in <project_root>/gen/com/example/renderscript/ScriptField_struct_name widergespiegelt. Diese Klasse stellt ein Array von struct dar und ermöglicht Ihnen, Arbeitsspeicher für eine bestimmte Anzahl von structs zuzuweisen. Angenommen, Sie definieren die folgende Struktur:

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

wird der folgende Code in ScriptField_Point.java generiert:

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

Der generierte Code wird Ihnen zur Verfügung gestellt, damit Sie Arbeitsspeicher für von der RenderScript-Laufzeit angeforderte Strukturen zuweisen und mit structs im Arbeitsspeicher interagieren können. Die Klasse jeder struct definiert die folgenden Methoden und Konstruktoren:

  • Überladene Konstruktoren, mit denen Sie Arbeitsspeicher zuweisen können. Mit dem Konstruktor ScriptField_struct_name(RenderScript rs, int count) können Sie mit dem Parameter count die Anzahl der Strukturen definieren, denen Sie Arbeitsspeicher zuweisen möchten. Der ScriptField_struct_name(RenderScript rs, int count, int usages)-Konstruktor definiert den zusätzlichen Parameter usages, mit dem Sie den Arbeitsspeicherbereich dieser Arbeitsspeicherzuweisung angeben können. Es gibt vier mögliche Speicherkapazitäten:

    Sie können mehrere Arbeitsspeicherbereiche mithilfe des bitweisen OR-Operators angeben. Dadurch wird die RenderScript-Laufzeit darüber informiert, dass Sie auf die Daten in den angegebenen Arbeitsspeicherbereichen zugreifen möchten. Im folgenden Beispiel wird Arbeitsspeicher für einen benutzerdefinierten Datentyp sowohl im Skript als auch in den Scheitelpunkt-Arbeitsspeicherbereichen zugewiesen:

    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);
    
  • Mit der statischen, verschachtelten Klasse Item können Sie eine Instanz von struct in Form eines Objekts erstellen. Diese verschachtelte Klasse ist nützlich, wenn die Arbeit mit struct in Ihrem Android-Code sinnvoller ist. Wenn Sie mit der Bearbeitung des Objekts fertig sind, können Sie das Objekt in den zugewiesenen Speicher verschieben. Rufen Sie dazu set(Item i, int index, boolean copyNow) auf und setzen Sie Item auf die gewünschte Position im Array. Die RenderScript-Laufzeit hat automatisch Zugriff auf den neu geschriebenen Arbeitsspeicher.
  • Zugriffsmethoden zum Abrufen und Festlegen der Werte der einzelnen Felder in einer Struktur. Jede dieser Zugriffsmethoden hat einen index-Parameter, um die struct in dem Array anzugeben, in das Sie lesen oder schreiben möchten. Jede Setter-Methode hat auch einen copyNow-Parameter, der angibt, ob dieser Arbeitsspeicher sofort mit der RenderScript-Laufzeit synchronisiert werden soll. Um eine Erinnerung zu synchronisieren, die nicht synchronisiert wurde, ruf copyAll() auf.
  • Mit der Methode createElement() wird eine Beschreibung der Struktur im Speicher erstellt. Diese Beschreibung wird verwendet, um Arbeitsspeicher zuzuweisen, der aus einem oder mehreren Elementen besteht.
  • resize() funktioniert ähnlich wie ein realloc() in C. Sie können also zuvor zugewiesenen Arbeitsspeicher erweitern und die aktuellen Werte beibehalten, die zuvor erstellt wurden.
  • copyAll() synchronisiert den auf Framework-Ebene festgelegten Arbeitsspeicher mit der RenderScript-Laufzeit. Wenn Sie eine Zugriffsmethode für ein Element aufrufen, können Sie den optionalen booleschen Parameter copyNow angeben. Wenn Sie true angeben, wird der Arbeitsspeicher beim Aufrufen der Methode synchronisiert. Wenn Sie „false“ angeben, können Sie copyAll() einmal aufrufen. Dadurch wird der Arbeitsspeicher für alle Attribute synchronisiert, die noch nicht synchronisiert sind.

Zeiger

Globale Zeiger werden in der Skriptklasse selbst in project_root/gen/package/name/ScriptC_renderscript_filename wiedergegeben. Sie können Zeiger auf ein struct oder einen der unterstützten RenderScript-Typen deklarieren, aber ein struct darf keine Zeiger oder verschachtelten Arrays enthalten. Wenn Sie beispielsweise die folgenden Zeiger auf struct und int32_t definieren

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

Point_t *touchPoints;
int32_t *intPointer;

wird der folgende Java-Code generiert:

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

Eine get-Methode und eine spezielle Methode namens bind_pointer_name (anstelle einer set()-Methode) werden generiert. Mit der Methode bind_pointer_name können Sie den in der Android-VM zugewiesenen Arbeitsspeicher an die RenderScript-Laufzeit binden. Sie können in der Datei .rs keinen Arbeitsspeicher zuweisen. Weitere Informationen finden Sie unter Mit zugewiesenem Arbeitsspeicher arbeiten.

APIs zur Arbeitsspeicherzuweisung

Anwendungen, die RenderScript verwenden, werden weiterhin auf der Android-VM ausgeführt. Der eigentliche RenderScript-Code wird jedoch nativ ausgeführt und benötigt Zugriff auf den in der Android-VM zugewiesenen Speicher. Dazu müssen Sie den in der VM zugewiesenen Arbeitsspeicher an die RenderScript-Laufzeit anhängen. Dieser Prozess, der als Bindung bezeichnet wird, ermöglicht der RenderScript-Laufzeit, nahtlos mit Arbeitsspeicher zu arbeiten, den sie anfordert, aber nicht explizit zuweisen kann. Das Endergebnis ist im Wesentlichen dasselbe wie bei malloc in C. Der zusätzliche Vorteil besteht darin, dass die Android-VM die automatische Speicherbereinigung durchführen und Arbeitsspeicher mit der RenderScript-Laufzeitebene teilen kann. Eine Bindung ist nur für dynamisch zugewiesenen Arbeitsspeicher erforderlich. Statisch zugewiesener Speicher wird bei der Kompilierung automatisch für Ihren RenderScript-Code erstellt. Weitere Informationen zur Arbeitsspeicherzuweisung finden Sie in Abbildung 1.

Um dieses Arbeitsspeicherzuweisungssystem zu unterstützen, gibt es eine Reihe von APIs, mit denen die Android-VM Arbeitsspeicher zuweisen kann und die ähnliche Funktionen wie ein malloc-Aufruf bieten. Diese Klassen beschreiben im Wesentlichen, wie Speicher zugewiesen werden soll, und führen auch die Zuweisung durch. Zum besseren Verständnis der Funktionsweise dieser Klassen empfiehlt es sich, sie in Bezug auf einen einfachen malloc-Aufruf zu betrachten, der so aussehen kann:

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

Der malloc-Aufruf kann in zwei Teile unterteilt werden: die Größe des zugewiesenen Speichers (sizeof(int)) und die Anzahl der zuzuweisenden Einheiten dieses Speichers (10). Das Android-Framework bietet Klassen für diese beiden Teile sowie eine Klasse, um malloc selbst darzustellen.

Die Klasse Element stellt den Teil (sizeof(int)) des malloc-Aufrufs dar und kapselt eine Zelle einer Arbeitsspeicherzuweisung, z. B. einen einzelnen Gleitkommawert oder eine Struktur. Die Klasse Type kapselt den Element und die Anzahl der zuzuweisenden Elemente (in unserem Beispiel 10). Sie können sich ein Type als ein Array von Element-Werten vorstellen. Die Klasse Allocation führt die tatsächliche Arbeitsspeicherzuweisung anhand einer bestimmten Type durch und stellt den tatsächlich zugewiesenen Arbeitsspeicher dar.

In den meisten Fällen müssen Sie diese APIs zur Arbeitsspeicherzuweisung nicht direkt aufrufen. Die Klassen der reflektierten Schichten generieren Code zur Nutzung dieser APIs. Sie müssen lediglich einen Konstruktor aufrufen, der in einer der reflektierten Schichtenklassen deklariert ist, und dann den resultierenden Arbeitsspeicher-Allocation an das RenderScript binden. In einigen Situationen empfiehlt es sich, diese Klassen direkt zu verwenden, um Speicher selbst zuzuweisen, z. B. wenn Sie eine Bitmap aus einer Ressource laden oder Arbeitsspeicher für Zeiger auf primitive Typen zuweisen möchten. Weitere Informationen hierzu finden Sie im Abschnitt Arbeitsspeicher an RenderScript zuweisen und an RenderScript binden. In der folgenden Tabelle werden die drei Klassen zur Arbeitsspeicherverwaltung ausführlicher beschrieben:

Android-Objekttyp Beschreibung
Element

Ein Element beschreibt eine Zelle einer Speicherzuweisung und kann zwei Formen haben: einfach oder komplex.

Ein Basiselement enthält eine einzelne Datenkomponente eines beliebigen gültigen RenderScript-Datentyps. Beispiele für grundlegende Elementdatentypen sind ein einzelner float-Wert, ein float4-Vektor oder eine einzelne RGB-565-Farbe.

Komplexe Elemente enthalten eine Liste von Grundelementen und werden aus structs erstellt, die Sie in Ihrem RenderScript-Code deklarieren. Beispielsweise kann eine Zuweisung mehrere structs enthalten, die der Reihe nach im Arbeitsspeicher angeordnet sind. Jede Struktur wird als eigenes Element betrachtet und nicht als jeder Datentyp innerhalb dieser Struktur.

Type

Ein Typ ist eine Vorlage für die Arbeitsspeicherzuweisung und besteht aus einem Element und einer oder mehreren Dimensionen. Sie beschreibt das Layout des Arbeitsspeichers (im Grunde ein Array aus Elements), weist jedoch den Speicher für die beschriebenen Daten nicht zu.

Ein Typ besteht aus fünf Dimensionen: X, Y, Z, LOD (Detailebene) und Flächen (einer Kubuskarte). Sie können die Dimensionen X, Y und Z innerhalb der Einschränkungen des verfügbaren Arbeitsspeichers auf einen beliebigen positiven ganzzahligen Wert festlegen. Eine Zuweisung mit einer einzelnen Dimension hat eine X-Dimension größer als null, während die Dimensionen Y und Z Null sind, um anzuzeigen, dass sie nicht vorhanden ist. Eine Zuordnung von x=10, y=1 wird z. B. als zweidimensional und x=10 und y=0 als eindimensional betrachtet. Die Dimensionen „LOD“ und „Flächen“ sind boolesche Werte, um anzuzeigen, ob es vorhanden oder nicht vorhanden ist.

Allocation

Eine Zuweisung stellt den Arbeitsspeicher für Anwendungen anhand einer Beschreibung des Arbeitsspeichers bereit, die durch eine Type dargestellt wird. Zugewiesener Arbeitsspeicher kann gleichzeitig in vielen Arbeitsspeicherbereichen vorhanden sein. Wenn Arbeitsspeicher in einem Bereich geändert wird, müssen Sie den Arbeitsspeicher explizit synchronisieren, damit er in allen anderen Bereichen, in denen er vorhanden ist, aktualisiert wird.

Es gibt zwei Möglichkeiten, Zuordnungsdaten hochzuladen: Typ aktiviert oder Typ deaktiviert. Für einfache Arrays gibt es copyFrom()-Funktionen, die ein Array aus dem Android-System in den Arbeitsspeicherspeicher der nativen Ebene kopieren. Mit den deaktivierten Varianten kann das Android-System Arrays von Strukturen kopieren, da es keine Strukturen unterstützt. Wenn es beispielsweise eine Zuweisung gibt, die aus einem Array aus n Gleitkommazahlen besteht, können die in einem Gleitkommazahl[n]-Array oder einem byte[n*4]-Array enthaltenen Daten kopiert werden.

Mit Memory arbeiten

Nicht-statische, globale Variablen, die Sie in Ihrem RenderScript deklarieren, werden bei der Kompilierung zugewiesen. Sie können mit diesen Variablen direkt im RenderScript-Code arbeiten, ohne ihnen auf Android-Framework-Ebene Arbeitsspeicher zuweisen zu müssen. Über die bereitgestellten Zugriffsmethoden, die in den Klassen der reflektierten Ebene generiert werden, hat auch die Framework-Ebene von Android Zugriff auf diese Variablen. Wenn diese Variablen auf der RenderScript-Laufzeitebene initialisiert werden, werden diese Werte verwendet, um die entsprechenden Werte in der Android-Framework-Ebene zu initialisieren. Wenn globale Variablen als const gekennzeichnet sind, wird keine set-Methode generiert. Weitere Informationen

Hinweis:Wenn Sie bestimmte RenderScript-Strukturen verwenden, die Zeiger enthalten, z. B. rs_program_fragment und rs_allocation, müssen Sie zuerst ein Objekt der entsprechenden Android-Framework-Klasse abrufen und dann die Methode set für diese Struktur aufrufen, um den Arbeitsspeicher an die RenderScript-Laufzeit zu binden. Sie können diese Strukturen nicht direkt auf der RenderScript-Laufzeitebene bearbeiten. Diese Einschränkung gilt nicht für benutzerdefinierte Strukturen, die Zeiger enthalten, da sie von vornherein nicht in eine Klasse von reflektierten Ebenen exportiert werden können. Wenn Sie versuchen, eine nicht-statische, globale Struktur zu deklarieren, die einen Zeiger enthält, wird ein Compiler-Fehler generiert.

RenderScript unterstützt auch Zeiger, aber Sie müssen den Speicher in Ihrem Android-Framework-Code explizit zuweisen. Wenn Sie in Ihrer .rs-Datei einen globalen Zeiger deklarieren, weisen Sie Arbeitsspeicher über die entsprechende Klasse der reflektierten Schicht zu und binden ihn an die native RenderScript-Ebene. Sie können mit diesem Arbeitsspeicher auf der Android-Framework-Ebene und der RenderScript-Ebene interagieren. So haben Sie die Flexibilität, Variablen in der jeweils passenden Ebene zu ändern.

Dynamischen Speicher zuweisen und an RenderScript binden

Zum Zuweisen von dynamischem Arbeitsspeicher müssen Sie den Konstruktor einer Script.FieldBase-Klasse aufrufen. Das ist die gängigste Methode. Alternativ können Sie ein Allocation manuell erstellen, was z. B. für Zeiger von primitiven Typen erforderlich ist. Der Einfachheit halber sollten Sie nach Möglichkeit einen Script.FieldBase-Klassenkonstruktor verwenden. Nachdem Sie eine Arbeitsspeicherzuweisung erhalten haben, rufen Sie die reflektierte bind-Methode des Zeigers auf, um den zugewiesenen Arbeitsspeicher an die RenderScript-Laufzeit zu binden.

Im folgenden Beispiel wird Arbeitsspeicher sowohl für einen primitiven Zeiger (intPointer) als auch für einen Zeiger auf eine Struktur (touchPoints) zugewiesen. Außerdem wird der Arbeitsspeicher an das RenderScript gebunden:

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

   ...
}

Lesen und in den Speicher schreiben

Sie können sowohl in der RenderScript-Laufzeit als auch auf der Android-Framework-Ebene in statisch und dynamisch zugewiesenen Speicher lesen und schreiben.

Bei statisch zugewiesenem Arbeitsspeicher gilt eine Einschränkung der unidirektionalen Kommunikation auf RenderScript-Laufzeitebene. Wenn der RenderScript-Code den Wert einer Variablen ändert, wird er aus Effizienzgründen nicht zurück an die Android-Framework-Ebene gesendet. Der letzte Wert, der vom Android-Framework festgelegt wird, wird bei einem Aufruf der Methode get immer zurückgegeben. Wenn jedoch der Android-Framework-Code eine Variable ändert, kann diese Änderung automatisch an die RenderScript-Laufzeit gesendet oder später synchronisiert werden. Wenn Sie Daten von der RenderScript-Laufzeit an die Android-Framework-Ebene senden müssen, können Sie diese Einschränkung mit der Funktion rsSendToClient() umgehen.

Wenn Sie mit dynamisch zugewiesenem Arbeitsspeicher arbeiten, werden alle Änderungen auf der RenderScript-Laufzeitebene zurück an die Android-Framework-Ebene weitergegeben, wenn Sie die Arbeitsspeicherzuweisung mit dem zugehörigen Zeiger geändert haben. Wenn Sie ein Objekt auf der Framework-Ebene von Android ändern, wird die Änderung sofort wieder auf die RenderScript-Laufzeitebene übertragen.

Globale Variablen lesen und schreiben

Das Lesen und Schreiben in globale Variablen ist ein unkomplizierter Prozess. Du kannst die Zugriffsmethoden auf der Ebene des Android-Frameworks verwenden oder direkt im RenderScript-Code festlegen. Änderungen, die Sie im RenderScript-Code vornehmen, werden nicht an die Framework-Ebene von Android zurückgegeben. Weitere Informationen finden Sie hier.

Angenommen, die folgende Struktur ist in einer Datei namens rsfile.rs deklariert:

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

Point_t point;

Sie können der Struktur direkt in rsfile.rs Werte zuweisen. Diese Werte werden nicht zurück auf die Android-Framework-Ebene weitergegeben:

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

Sie können der Struktur auf der Android-Framework-Ebene Werte zuweisen. Diese Werte werden asynchron wieder an die RenderScript-Laufzeitebene zurückgegeben:

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

So können Sie die Werte in Ihrem RenderScript-Code lesen:

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

Mit dem folgenden Code können Sie die Werte in der Android-Framework-Ebene lesen. Beachten Sie, dass dieser Code nur dann einen Wert zurückgibt, wenn einer auf Android-Framework-Ebene festgelegt wurde. Wenn Sie den Wert nur auf der RenderScript-Laufzeitebene festlegen, erhalten Sie eine Nullzeiger-Ausnahme:

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

Globale Hinweise lesen und schreiben

Wenn der Arbeitsspeicher auf Android-Framework-Ebene zugewiesen und an die RenderScript-Laufzeit gebunden wurde, können Sie Arbeitsspeicher von der Android-Framework-Ebene aus lesen und schreiben, indem Sie die Methoden get und set für diesen Zeiger verwenden. In der RenderScript-Laufzeitebene können Sie wie gewohnt mit Zeigern in den Arbeitsspeicher lesen und schreiben. Die Änderungen werden im Gegensatz zu statisch zugewiesenem Arbeitsspeicher zurück an die Android-Framework-Ebene weitergegeben.

Mit dem folgenden Zeiger auf einen struct in einer Datei namens rsfile.rs wird beispielsweise Folgendes angegeben:

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

Point_t *point;

Wenn Sie bereits Arbeitsspeicher auf der Android-Framework-Ebene zugewiesen haben, können Sie wie gewohnt auf Werte in struct zugreifen. Alle Änderungen, die Sie über die Zeigervariable an der Struktur vornehmen, sind automatisch für die Android-Framework-Ebene verfügbar:

Kotlin

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

Java

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

Sie können Werte auch auf der Android-Framework-Ebene lesen und in den Zeiger schreiben:

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

Wenn der Arbeitsspeicher bereits gebunden ist, müssen Sie ihn nicht bei jeder Änderung an einem Wert wieder an die RenderScript-Laufzeit binden.