Zaawansowany skrypt renderowania

Aplikacje, które wykorzystują RenderScript, nadal działają w maszynie wirtualnej z Androidem, dlatego masz dostęp do wszystkich znanych Ci interfejsów API platformy, ale w razie potrzeby możesz używać RenderScript. Aby ułatwić tę interakcję między platformą a środowiskiem wykonawczym RenderScript, dostępna jest też pośrednia warstwa kodu, która ułatwia komunikację i zarządzanie pamięcią między dwoma poziomami kodu. W tym dokumencie szczegółowo opisujemy różne warstwy kodu oraz sposób współdzielenia pamięci między maszyną wirtualną z Androidem a środowiskiem wykonawczym RenderScript.

Warstwa środowiska wykonawczego RenderScript

Kod RenderScript jest skompilowany i wykonywany w kompaktowej i dobrze zdefiniowanej warstwie środowiska wykonawczego. Interfejsy RenderScript API obsługują zaawansowane obliczenia, które można przenosić i automatycznie skalować do liczby rdzeni dostępnych w procesorze.

Uwaga: trzeba zagwarantować uruchamianie standardowych funkcji C w pakiecie NDK na procesorze, więc RenderScript nie ma dostępu do tych bibliotek, ponieważ RenderScript jest przeznaczony do uruchamiania na różnych typach procesorów.

Kod RenderScript definiuje się w plikach .rs, a pliki .rsh w katalogu src/ projektu Androida. Kod jest skompilowany do pośredniego kodu bajtowego przez kompilator llvm, który działa w ramach kompilacji Androida. Gdy aplikacja działa na urządzeniu, kod bajtowy jest następnie kompilowany (w sam raz) pod kod maszynowy przez inny kompilator llvm znajdujący się na tym urządzeniu. Kod maszyny jest optymalizowany pod kątem urządzenia i zapisywany w pamięci podręcznej, więc późniejsze zastosowania aplikacji z obsługą RenderScriptu nie rekompilują kodu bajtowego.

Kluczowe funkcje bibliotek środowiska wykonawczego RenderScript to:

  • Funkcje żądań alokacji pamięci
  • Duża kolekcja funkcji matematycznych z przeciążonymi wersjami wielu popularnych rutyn zarówno w formacie skalarnym, jak i wektorowym. Dostępne są takie działania jak dodawanie, mnożenie, iloczyn skalarny, iloczyn wektorowy, a także funkcje atomowej arytmetyki i porównania.
  • Rutyny konwersji dla podstawowych typów danych i wektorów, procedur macierzy oraz rutyn uwzględniających datę i godzinę
  • Typy danych i struktury obsługujące system RenderScript, np. typy wektorów do definiowania wektorów w formacie 2, 3 lub 4 wektorów.
  • Funkcje logowania

Więcej informacji o dostępnych funkcjach znajdziesz w dokumentacji interfejsu API środowiska wykonawczego RenderScript.

Warstwa odbicia

Odzwierciedlona warstwa to zestaw klas generowanych przez narzędzia do kompilacji Androida, które umożliwiają dostęp do środowiska wykonawczego RenderScriptu z platformy Androida. W tej warstwie znajdują się również metody i konstrutory, które umożliwiają przydzielanie pamięci i pracę z nią na potrzeby wskaźników zdefiniowanych w kodzie RenderScript. Poniższa lista zawiera opis głównych komponentów, które mają być uwzględniane:

  • Każdy utworzony przez Ciebie plik .rs jest generowany w klasie o nazwie project_root/gen/package/name/ScriptC_renderscript_filename typu ScriptC. To jest wersja .java pliku .rs, którą możesz wywołać z platformy Androida. Ta klasa zawiera następujące elementy odzwierciedlone w pliku .rs:
    • Funkcje niestatyczne
    • Niestatyczne, globalne zmienne RenderScript. Metody dostępu są generowane dla każdej zmiennej, więc możesz odczytywać i zapisywać zmienne RenderScript, korzystając z platformy Androida. Jeśli zmienna globalna zostanie zainicjowana w warstwie środowiska wykonawczego RenderScript, wartości te zostaną użyte do zainicjowania odpowiednich wartości w warstwie platformy Androida. Jeśli zmienne globalne są oznaczone jako const, metoda set nie jest generowana. Więcej informacji znajdziesz tutaj.

    • Wskaźniki globalne
  • Obiekt struct jest odzwierciedlany we własnej klasie o nazwie project_root/gen/package/name/ScriptField_struct_name, która jest rozszerzeniem Script.FieldBase. Ta klasa reprezentuje tablicę obiektu struct, która pozwala przydzielić pamięć na 1 lub więcej instancji obiektu struct.

Funkcje

Funkcje są odzwierciedlane w klasie skryptu w lokalizacji project_root/gen/package/name/ScriptC_renderscript_filename. Jeśli na przykład zdefiniujesz tę funkcję w kodzie 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;
}

zostanie wygenerowany następujący kod 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);
}

Funkcje nie mogą mieć wartości zwracanych, ponieważ system RenderScript jest zaprojektowany w sposób asynchroniczny. Gdy kod platformy Androida wywołuje RenderScript, wywołanie jest umieszczane w kolejce i wykonywane, gdy jest to możliwe. To ograniczenie pozwala systemowi RenderScript funkcjonować bez ciągłych przerw i zwiększa wydajność. Gdyby funkcje mogły mieć zwracane wartości, wywołanie blokuje je, dopóki wartość nie zostanie zwrócona.

Jeśli chcesz, by kod RenderScript zwracał wartość do platformy Androida, użyj funkcji rsSendToClient().

Zmienne

Zmienne obsługiwanych typów są odzwierciedlane w klasie skryptu znajdującej się w elemencie project_root/gen/package/name/ScriptC_renderscript_filename. Dla każdej zmiennej generowany jest zestaw metod akcesorów. Jeśli np. zdefiniujesz tę zmienną w kodzie RenderScriptu:

uint32_t unsignedInteger = 1;

zostanie wygenerowany następujący kod 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;
}
  

Konstrukcja

Usługi tego typu są odzwierciedlane w osobnych klasach w klasie <project_root>/gen/com/example/renderscript/ScriptField_struct_name. Ta klasa reprezentuje tablicę obiektu struct i pozwala przydzielić pamięć na określoną liczbę struct s. Jeśli na przykład zdefiniujesz taką strukturę:

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

w ScriptField_Point.java generowany jest ten kod:

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

Wygenerowany kod ułatwia przydzielenie pamięci na potrzeby struktur żądanych przez środowisko wykonawcze RenderScript i interakcji z elementami struct w pamięci. Każda klasa struct definiuje te metody i konstruktory:

  • Przeciążone konstruktory, które umożliwiają przydzielenie pamięci. Konstruktor ScriptField_struct_name(RenderScript rs, int count) pozwala za pomocą parametru count określić liczbę struktur, dla których chcesz przydzielić pamięć. Konstruktor ScriptField_struct_name(RenderScript rs, int count, int usages) definiuje dodatkowy parametr usages, który umożliwia określenie miejsca przeznaczonego na ten przydział pamięci. Dostępne są 4 opcje pamięci:
    • USAGE_SCRIPT: przypisuje się w miejscu pamięci skryptów. Jest to domyślne miejsce pamięci, jeśli nie zostanie określone.
    • USAGE_GRAPHICS_TEXTURE: przypisuje się w przestrzeni pamięci tekstur GPU.
    • USAGE_GRAPHICS_VERTEX: jest przydzielane w przestrzeni pamięci wierzchołkowej GPU.
    • USAGE_GRAPHICS_CONSTANTS: jest przydzielane w stałej pamięci procesora graficznego używanego przez różne obiekty programu.

    Możesz określić wiele przestrzeni pamięci przy użyciu operatora bitowego OR. Spowoduje to powiadomienie środowiska wykonawczego RenderScript, że chcesz uzyskać dostęp do danych we wskazanych przestrzeniach pamięci. Ten przykład przydziela pamięć na potrzeby niestandardowego typu danych zarówno w obszarze pamięci skryptu, jak i w przestrzeni pamięci wierzchołkowej:

    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);
    
  • Statyczna klasa zagnieżdżona (Item) umożliwia utworzenie instancji struct w postaci obiektu. Ta zagnieżdżona klasa jest przydatna, jeśli korzystanie z struct w kodzie Androida ma sens. Gdy skończysz manipulować obiektem, możesz przekazać go do przydzielonej pamięci, wywołując set(Item i, int index, boolean copyNow) i ustawiając Item na żądane miejsce w tablicy. Środowisko wykonawcze RenderScript automatycznie ma dostęp do nowo zapisanej pamięci.
  • Metody dostępu do pobierania i ustawiania wartości każdego pola w elemencie struct. Każda z tych metod akcesora ma parametr index określający element struct w tablicy, w której chcesz odczytywać lub zapisywać dane. Każda metoda ustawiająca ma też parametr copyNow, który określa, czy natychmiast synchronizować tę pamięć ze środowiskiem wykonawczym RenderScript. Aby zsynchronizować wspomnienie, które nie zostało zsynchronizowane, wywołaj copyAll().
  • Metoda createElement() tworzy opis elementu struct w pamięci. Ten opis służy do przydzielania pamięci składającej się z jednego lub wielu elementów.
  • resize() działa podobnie jak realloc() w C, co pozwala rozszerzyć przydzieloną wcześniej pamięć i utrzymać bieżące wartości, które zostały wcześniej utworzone.
  • copyAll() synchronizuje pamięć ustawioną na poziomie platformy ze środowiskiem wykonawczym RenderScript. Gdy wywołujesz metodę ustawiania akcesora na elemencie, możesz określić opcjonalny parametr logiczny copyNow. Jeśli określisz true, pamięć zostanie zsynchronizowana podczas wywoływania metody. Jeśli określisz wartość false (fałsz), możesz wywołać funkcję copyAll() raz, co spowoduje synchronizowanie pamięci ze wszystkimi właściwościami, które nie są jeszcze zsynchronizowane.

Wskaźniki

Wskaźniki globalne są odzwierciedlane w klasie skryptu w lokalizacji project_root/gen/package/name/ScriptC_renderscript_filename. Możesz zadeklarować wskaźniki do struct lub dowolnego z obsługiwanych typów RenderScript, ale właściwość struct nie może zawierać wskaźników ani zagnieżdżonych tablic. Jeśli np. zdefiniujesz te wskaźniki do struct i int32_t

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

Point_t *touchPoints;
int32_t *intPointer;

zostanie wygenerowany następujący kod 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;
}
  

Wygenerowane są metoda get i metoda specjalna o nazwie bind_pointer_name (zamiast metody set()). Metoda bind_pointer_name umożliwia powiązanie pamięci przydzielonej w maszynie wirtualnej z Androidem ze środowiskiem wykonawczym RenderScript (nie można przydzielić pamięci w pliku .rs). Więcej informacji znajdziesz w artykule na temat pracy z przydzieloną pamięcią.

Interfejsy API alokacji pamięci

Aplikacje, które używają RenderScript, nadal działają na maszynie wirtualnej z Androidem. Rzeczywisty kod RenderScript działa natywnie i wymaga dostępu do pamięci przydzielonej w maszynie wirtualnej z Androidem. Aby to zrobić, musisz podłączyć do środowiska wykonawczego RenderScript pamięć przydzieloną w maszynie wirtualnej. Ten proces, nazywany wiązaniem, umożliwia środowisku wykonawczym RenderScript bezproblemową współpracę z pamięcią, o którą prosi, ale nie może jej bezpośrednio przydzielać. Efekt końcowy jest taki sam jak w przypadku wywołania funkcji malloc w języku C. Dodatkową zaletą jest to, że maszyna wirtualna z Androidem może wykonywać procedurę czyszczenia pamięci i współdzielić pamięć z warstwą środowiska wykonawczego RenderScript. Powiązanie jest konieczne tylko w przypadku pamięci alokowanej dynamicznie. W trakcie kompilacji automatycznie tworzona jest statycznie przydzielana pamięć dla kodu RenderScript. Na Rys. 1 znajdziesz więcej informacji o sposobie alokacji pamięci.

Do obsługi tego systemu alokacji pamięci dostępny jest zestaw interfejsów API, które pozwalają maszynie wirtualnej z Androidem przydzielać pamięć i oferować funkcje podobne do wywołania malloc. Ogólnie rzecz biorąc, klasy te opisują, w jaki sposób należy przydzielać pamięć oraz ją realizować. Aby lepiej zrozumieć, jak działają te zajęcia, spróbuj je porównać z prostym wywołaniem malloc, które może wyglądać tak:

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

Wywołanie malloc można podzielić na 2 części: rozmiar przydzielanej pamięci (sizeof(int)) oraz liczbę jej jednostek (10). Platforma Androida udostępnia klasy dla tych 2 części, a także klasę reprezentującą usługę malloc.

Klasa Element reprezentuje część (sizeof(int)) wywołania malloc i obejmuje jedną komórkę alokacji pamięci, np. pojedynczą wartość zmiennoprzecinkową lub element struct. Klasa Type zawiera element Element oraz liczbę elementów do przydzielenia (w naszym przykładzie jest ich 10). Type można porównać do tablicy Element. Klasa Allocation wykonuje rzeczywisty przydział pamięci na podstawie podanej wartości Type i reprezentuje faktyczną przydzieloną pamięć.

W większości przypadków nie trzeba wywoływać bezpośrednio tych interfejsów API alokacji pamięci. Odbite klasy warstw generują kod, który pozwala automatycznie używać tych interfejsów API. Aby przydzielić pamięć, musisz wywołać konstruktor, który jest zadeklarowany w jednej z odbitych klas warstw, a następnie powiązać wynikową pamięć Allocation z RenderScript. W niektórych sytuacjach możesz chcieć używać tych klas bezpośrednio do samodzielnego przydzielania pamięci, na przykład podczas wczytywania bitmapy z zasobu lub gdy chcesz przydzielić pamięć na wskaźniki do typów podstawowych. Instrukcje, jak to zrobić, znajdziesz w sekcji Przydzielanie i powiązanie pamięci do skryptu renderowania. Tabela poniżej zawiera szczegółowe informacje na temat 3 klas zarządzania pamięcią:

Typ obiektu Androida Opis
Element

Element opisuje jedną komórkę w alokacji pamięci i może mieć 2 formy: podstawową lub złożoną.

Podstawowy element zawiera jeden składnik danych dowolnego prawidłowego typu danych RenderScript. Przykłady podstawowych typów danych elementów to pojedyncza wartość float, wektor float4 i pojedynczy kolor RGB-565.

Elementy złożone zawierają listę elementów podstawowych i są tworzone z elementów struct, które deklarujesz w kodzie RenderScript. Na przykład przydział może zawierać wiele elementów struct ułożonych w kolejności w pamięci. Każdy element struct jest uważany za własny element, a nie jako każdy typ danych w tym elemencie.

Type

Typ to szablon alokacji pamięci, który składa się z elementu oraz co najmniej 1 wymiaru. Opisuje układ pamięci (w zasadzie jest to tablica Element), ale nie przydziela pamięci na opisywane dane.

Typ składa się z 5 wymiarów: X, Y, Z, LOD (poziom szczegółowości) i płaszczyzn (mapy sześcianu). Wymiary X, Y i Z możesz ustawić na dowolną dodatnią liczbę całkowitą w ramach ograniczeń dostępnej pamięci. Przydział pojedynczego wymiaru ma wymiar X większy od zera, a wymiary Y i Z wynoszą 0, co oznacza, że go nie ma. Na przykład przydział x=10, y=1 jest uznawany za dwuwymiarowy, a x=10, a y=0 jest uznawany za jednowymiarowy. Wymiary LOD i płaszczyzny to wartości logiczne wskazujące, że występuje lub nie ma ich wcale.

Allocation

Alokacja zapewnia pamięć dla aplikacji na podstawie opisu pamięci reprezentowanej przez Type. Przydzielona pamięć może istnieć jednocześnie w wielu przestrzeniach pamięci. Jeśli pamięć jest modyfikowana w jednym pokoju, musisz ją jawnie zsynchronizować, aby została zaktualizowana we wszystkich innych pokojach, w których się znajduje.

Dane alokacji są przesyłane na 2 główne sposoby – zaznaczanie wpisywania i odznaczanie jego pola wyboru. W przypadku prostych tablic istnieją funkcje copyFrom(), które pobierają tablicę z systemu Android i kopiują ją do magazynu pamięci warstwy natywnej. Niezaznaczone warianty umożliwiają systemowi Android kopiowanie za pomocą tablic struktur, ponieważ nie obsługuje on struktur. Jeśli na przykład dostępny jest przydział, który jest tablicą n zmiennoprzecinkowych, można skopiować dane zawarte w tablicy float[n] lub byte[n*4].

Praca z pamięcią

Niestatyczne zmienne globalne, które deklarujesz w skrypcie RenderScriptu, mają przydzieloną pamięć w trakcie kompilacji. Z tymi zmiennymi możesz pracować bezpośrednio w kodzie RenderScript, bez konieczności przydzielania dla nich pamięci na poziomie platformy Androida. Warstwa platformy Androida ma również dostęp do tych zmiennych za pomocą podanych metod akcesorium, które są generowane w odzwierciedlonych klasach warstwy. Jeśli te zmienne są inicjowane w warstwie środowiska wykonawczego RenderScript, wartości te służą do inicjowania odpowiednich wartości w warstwie platformy Androida. Jeśli zmienne globalne są oznaczone jako stałe, metoda set nie jest generowana. Więcej informacji znajdziesz tutaj.

Uwaga: jeśli używasz określonych struktur RenderScript, które zawierają wskaźniki, np. rs_program_fragment i rs_allocation, musisz najpierw uzyskać obiekt odpowiedniej klasy platformy Androida, a potem wywołać dla tej struktury metodę set, aby powiązać pamięć ze środowiskiem wykonawczym RenderScript. Nie można bezpośrednio manipulować tymi strukturami w warstwie środowiska wykonawczego RenderScript. To ograniczenie nie dotyczy struktur zdefiniowanych przez użytkownika, które zawierają wskaźniki, ponieważ nie można ich eksportować do odbitej klasy warstwy. Jeśli spróbujesz zadeklarować niestatyczną, globalną strukturę, która zawiera wskaźnik, wystąpi błąd kompilatora.

RenderScript obsługuje też wskaźniki, ale musisz jawnie przydzielić pamięć w kodzie platformy Androida. Zadeklarowanie wskaźnika globalnego w pliku .rs pozwala przydzielać pamięć przez odpowiednią klasę odbitej warstwy i wiązać ją z natywną warstwą RenderScript. Możesz z niej korzystać w warstwie platformy Androida lub w warstwie RenderScript, co umożliwia modyfikowanie zmiennych w najodpowiedniejszych warstwie.

Przydział i powiązanie pamięci dynamicznej z renderowaniem

Aby przydzielić pamięć dynamiczną, musisz wywołać konstruktor klasy Script.FieldBase, co jest najczęstszym sposobem. Możesz też utworzyć ręcznie obiekt Allocation, co jest wymagane w przypadku takich wskaźników jak wskaźniki typu podstawowego. Jeśli jest to możliwe, dla ułatwienia używaj konstruktora klasy Script.FieldBase. Po uzyskaniu alokacji pamięci wywołaj odzwierciedloną metodę bind wskaźnika, aby powiązać przydzieloną pamięć ze środowiskiem wykonawczym RenderScript.

W przykładzie poniżej przydzielasz pamięć zarówno do wskaźnika typu podstawowego (intPointer), jak i wskaźnika do struct (touchPoints). Wiąże też pamięć z 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);

   ...
}

Odczyt i zapis w pamięci

Możesz odczytywać i zapisywać w pamięci alokowanej statycznie i dynamicznie zarówno w środowisku wykonawczym RenderScript, jak i w warstwie platformy Androida.

Statycznie przydzielona pamięć ma ograniczenie jednokierunkowej komunikacji na poziomie środowiska wykonawczego RenderScript. Gdy kod RenderScript zmienia wartość zmiennej, nie jest ona przekazywana z powrotem do warstwy platformy Androida w celu zwiększenia wydajności. Ostatnia wartość ustawiona przez platformę Android jest zawsze zwracana podczas wywoływania metody get. Gdy jednak kod platformy Androida modyfikuje zmienną, zmiana ta może zostać później automatycznie przesłana do środowiska wykonawczego RenderScript lub może zostać zsynchronizowana. Jeśli musisz wysyłać dane ze środowiska wykonawczego RenderScript do warstwy platformy Androida, możesz ominąć to ograniczenie za pomocą funkcji rsSendToClient().

Podczas pracy z dynamicznie alokowaną pamięcią wszelkie zmiany w warstwie środowiska wykonawczego RenderScript są przesyłane z powrotem do warstwy platformy Androida, jeśli alokację pamięci zmodyfikujesz za pomocą powiązanego z nią wskaźnika. Zmodyfikowanie obiektu w warstwie platformy Androida natychmiast powoduje propagację tej zmiany z powrotem do warstwy środowiska wykonawczego RenderScript.

Odczytywanie i zapisywanie zmiennych globalnych

Odczytywanie i zapisywanie zmiennych globalnych jest bardzo proste. Metody akcesorów możesz używać na poziomie platformy Androida lub ustawić je bezpośrednio w kodzie RenderScript. Pamiętaj, że zmiany wprowadzone w kodzie RenderScript nie są przekazywane z powrotem do warstwy platformy Androida (więcej szczegółów znajdziesz tutaj).

Na przykład przy zadeklarowaniu następującej struktury zadeklarowanej w pliku o nazwie rsfile.rs:

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

Point_t point;

Wartości możesz przypisać do elementu struct bezpośrednio w narzędziu rsfile.rs. Te wartości nie są przenoszone z powrotem na poziom platformy Androida:

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

Możesz przypisać wartości do elementu struct w warstwie platformy Androida w ten sposób. Te wartości są propagowane z powrotem do poziomu środowiska wykonawczego RenderScript asynchronicznie:

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

Wartości w kodzie RenderScript możesz odczytywać w ten sposób:

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

Wartości w warstwie platformy Androida możesz odczytać za pomocą poniższego kodu. Pamiętaj, że zwraca on wartość tylko wtedy, gdy została ona ustawiona na poziomie platformy Androida. Jeśli ustawisz wartość tylko na poziomie środowiska wykonawczego RenderScript, uzyskasz wyjątek ze wskaźnikiem o wartości null:

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

Odczytywanie i zapisywanie wskaźników globalnych

Przy założeniu, że pamięć została przydzielona na poziomie platformy Androida i powiązana ze środowiskiem wykonawczym RenderScript, możesz odczytywać i zapisywać pamięć z poziomu platformy Androida, używając metod get i set do tego wskaźnika. W warstwie środowiska wykonawczego RenderScript możesz normalnie odczytywać i zapisywać w pamięci wskaźniki, a zmiany są rozpowszechniane z powrotem do warstwy platformy Androida, w przeciwieństwie do pamięci przydzielonej statycznie.

Na przykład w przypadku poniższego wskaźnika na struct w pliku o nazwie rsfile.rs:

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

Point_t *point;

Zakładając, że masz już przydzieloną pamięć w warstwie platformy Androida, możesz normalnie korzystać z wartości w struct. Wszelkie zmiany wprowadzone w elemencie struct za pomocą zmiennej wskaźnika są automatycznie dostępne w warstwie platformy Androida:

Kotlin

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

Java

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

Wartości możesz też odczytywać i zapisywać w przypadku wskaźnika w warstwie platformy Androida:

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

Gdy pamięć jest już powiązana, nie musisz ponownie jej łączyć ze środowiskiem wykonawczym RenderScript za każdym razem, gdy wprowadzasz zmianę wartości.