Расширенная документация по RenderScript

Поскольку приложения, использующие RenderScript, по-прежнему выполняются в виртуальной машине Android, у вас есть доступ ко всем знакомым API фреймворка, но при необходимости вы можете использовать RenderScript. Чтобы упростить это взаимодействие между фреймворком и средой выполнения RenderScript, также присутствует промежуточный уровень кода, который упрощает связь и управление памятью между двумя уровнями кода. В этом документе подробно рассматриваются эти различные уровни кода, а также то, как память распределяется между виртуальной машиной Android и средой выполнения RenderScript.

Слой среды выполнения RenderScript

Код RenderScript компилируется и выполняется в компактном и четко определенном слое среды выполнения. API среды выполнения RenderScript поддерживают ресурсоемкие вычисления, которые можно переносить и автоматически масштабировать в зависимости от количества ядер процессора.

Примечание. Стандартные функции C в NDK должны гарантированно выполняться на ЦП, поэтому RenderScript не может получить доступ к этим библиотекам, так как он предназначен для работы на процессорах разных типов.

Код RenderScript определяется в файлах .rs и .rsh в каталоге src/ проекта для Android. Код компилируется в промежуточный байт-код компилятором llvm, который запускается в рамках сборки Android. Когда приложение запускается на устройстве, байт-код компилируется (в режиме реального времени) в машинный код другим компилятором llvm, который находится на устройстве. Машинный код оптимизируется для устройства и кешируется, поэтому при последующих запусках приложения с поддержкой RenderScript байт-код не перекомпилируется.

Вот некоторые ключевые особенности библиотек среды выполнения RenderScript:

  • Функции запроса на выделение памяти
  • Большая коллекция математических функций со скалярными и векторными перегруженными версиями многих распространенных подпрограмм. Доступны такие операции, как сложение, умножение, скалярное и векторное произведение, а также атомарные арифметические функции и функции сравнения.
  • Процедуры преобразования для примитивных типов данных и векторов, матричные процедуры, а также процедуры для работы с датой и временем.
  • Типы и структуры данных для поддержки системы RenderScript, например векторные типы для определения двух-, трех- или четырехмерных векторов.
  • Функции ведения журнала

Дополнительную информацию о доступных функциях можно найти в документации по API среды выполнения RenderScript.

Отраженный слой

Отраженный слой – это набор классов, которые генерируются инструментами сборки Android, чтобы обеспечить доступ к среде выполнения RenderScript из фреймворка Android. Этот уровень также предоставляет методы и конструкторы, которые позволяют выделять память для указателей, определенных в коде RenderScript, и работать с ней. Ниже перечислены основные компоненты, которые отражены в отчете.

  • Каждый созданный вами файл .rs генерируется в класс с именем project_root/gen/package/name/ScriptC_renderscript_filename типа ScriptC. Это версия файла .rs для .java, которую можно вызвать из фреймворка Android. Этот класс содержит следующие элементы, отраженные в файле .rs:
    • Нестатические функции
    • Нестатические глобальные переменные RenderScript. Для каждой переменной генерируются методы доступа, поэтому вы можете читать и записывать переменные RenderScript из фреймворка Android. Если глобальная переменная инициализируется на уровне среды выполнения RenderScript, эти значения используются для инициализации соответствующих значений на уровне фреймворка Android. Если глобальные переменные помечены как const, метод set не создается. Подробнее…

    • Глобальные указатели
  • Класс struct отражается в собственном классе project_root/gen/package/name/ScriptField_struct_name, который расширяет Script.FieldBase. Этот класс представляет собой массив struct, который позволяет выделять память для одного или нескольких экземпляров этого struct.

Функции

Функции отражаются в самом классе скрипта, который находится в project_root/gen/package/name/ScriptC_renderscript_filename. Например, если вы определите в коде 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;
}

то будет сгенерирован следующий код 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);
}

Функции не могут возвращать значения, поскольку система RenderScript является асинхронной. Когда код фреймворка Android вызывает RenderScript, вызов ставится в очередь и выполняется при первой возможности. Это ограничение позволяет системе RenderScript работать без постоянных прерываний и повышает эффективность. Если бы функции могли возвращать значения, вызов блокировался бы до возврата значения.

Если вы хотите, чтобы код RenderScript отправлял значение обратно в платформу Android, используйте функцию rsSendToClient().

Переменные

Переменные поддерживаемых типов отражаются в самом классе скрипта, расположенном в project_root/gen/package/name/ScriptC_renderscript_filename. Для каждой переменной генерируется набор методов доступа. Например, если вы определите в коде RenderScript следующую переменную:

uint32_t unsignedInteger = 1;

то будет сгенерирован следующий код 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;
}
  

Структуры

Структуры отражаются в собственных классах, расположенных в <project_root>/gen/com/example/renderscript/ScriptField_struct_name. Этот класс представляет собой массив struct и позволяет выделять память для указанного количества struct. Например, если вы определите следующую структуру:

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

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

Сгенерированный код предоставляется для удобства выделения памяти для структур, запрашиваемых средой выполнения RenderScript, и для взаимодействия с struct в памяти. Класс каждого объекта struct определяет следующие методы и конструкторы:

  • Перегруженные конструкторы, позволяющие выделять память. Конструктор ScriptField_struct_name(RenderScript rs, int count) позволяет определить количество структур, для которых вы хотите выделить память, с помощью параметра count. Конструктор ScriptField_struct_name(RenderScript rs, int count, int usages) определяет дополнительный параметр usages, который позволяет указать пространство памяти для этого распределения памяти. Доступны четыре варианта:
    • USAGE_SCRIPT – выделяет память в пространстве памяти скрипта. Это пространство памяти по умолчанию, если вы не укажете другое.
    • USAGE_GRAPHICS_TEXTURE – выделяет память в пространстве памяти текстур графического процессора.
    • USAGE_GRAPHICS_VERTEX – выделяет память в пространстве памяти вершин графического процессора.
    • USAGE_GRAPHICS_CONSTANTS – выделяет память в области памяти констант графического процессора, которая используется различными объектами программы.

    Вы можете указать несколько областей памяти, используя побитовый оператор OR. Это сообщает среде выполнения RenderScript, что вы намерены получить доступ к данным в указанных областях памяти. В следующем примере память для пользовательского типа данных выделяется как в скрипте, так и в памяти вершин:

    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);
  • Статический вложенный класс Item позволяет создать экземпляр struct в виде объекта. Этот вложенный класс полезен, если в коде Android удобнее работать с struct. Завершив работу с объектом, вы можете отправить его в выделенную память, вызвав set(Item i, int index, boolean copyNow) и установив Item в нужную позицию в массиве. Среда выполнения RenderScript автоматически получает доступ к вновь записанной памяти.
  • Методы доступа для получения и установки значений каждого поля в структуре. У каждого из этих методов доступа есть параметр index, который позволяет указать struct в массиве, который вы хотите прочитать или в который хотите записать данные. Каждый метод setter также имеет параметр copyNow, который указывает, следует ли немедленно синхронизировать эту память со средой выполнения RenderScript. Чтобы синхронизировать все воспоминания, которые ещё не были синхронизированы, вызовите copyAll().
  • Метод createElement() создает описание структуры в памяти. Это описание используется для выделения памяти, состоящей из одного или нескольких элементов.
  • resize() работает аналогично realloc() в C, позволяя расширять ранее выделенную память, сохраняя текущие значения, которые были созданы ранее.
  • copyAll() синхронизирует память, которая была задана на уровне фреймворка, со средой выполнения RenderScript. При вызове метода доступа set для участника можно указать необязательный логический параметр copyNow. Если указать true, память будет синхронизироваться при вызове метода. Если указать значение false, то можно вызвать copyAll() один раз, и он синхронизирует память для всех свойств, которые ещё не синхронизированы.

Указатели

Глобальные указатели отражаются в самом классе скрипта, расположенном в project_root/gen/package/name/ScriptC_renderscript_filename. Вы можете объявить указатели на struct или любой из поддерживаемых типов RenderScript, но struct не может содержать указатели или вложенные массивы. Например, если вы определите следующие указатели на struct и int32_t:

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

Point_t *touchPoints;
int32_t *intPointer;

то будет сгенерирован следующий код 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;
}
  

Генерируется метод get и специальный метод bind_pointer_name (вместо метода set()). Метод bind_pointer_name позволяет связать память, выделенную в виртуальной машине Android, со средой выполнения RenderScript (вы не можете выделить память в файле .rs). Подробнее о том, как работать с выделенной памятью…

API для выделения памяти

Приложения, использующие RenderScript, по-прежнему работают на виртуальной машине Android. Однако сам код RenderScript выполняется нативно и требует доступа к памяти, выделенной в виртуальной машине Android. Для этого необходимо подключить память, выделенную на виртуальной машине, к среде выполнения RenderScript. Этот процесс, называемый связыванием, позволяет среде выполнения RenderScript беспрепятственно работать с памятью, которую она запрашивает, но не может явно выделить. Результат будет таким же, как при вызове malloc в C. Дополнительное преимущество заключается в том, что виртуальная машина Android может выполнять сборку мусора, а также совместно использовать память с уровнем среды выполнения RenderScript. Привязка необходима только для динамически выделяемой памяти. Статически выделенная память автоматически создается для кода RenderScript во время компиляции. Подробнее о том, как происходит распределение памяти, рассказывается в разделе Рисунок 1.

Для поддержки этой системы распределения памяти существует набор API, которые позволяют виртуальной машине Android выделять память и предлагают функциональность, аналогичную вызову malloc. Эти классы описывают, как должна выделяться память, и выполняют выделение. Чтобы лучше понять, как работают эти классы, полезно рассмотреть их в контексте простого вызова malloc, который может выглядеть следующим образом:

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

Вызов malloc можно разделить на две части: размер выделяемой памяти (sizeof(int)) и количество единиц этой памяти, которые должны быть выделены (10). Фреймворк Android предоставляет классы для этих двух частей, а также класс, представляющий сам malloc.

Класс Element представляет часть (sizeof(int)) вызова malloc и инкапсулирует одну ячейку выделения памяти, например одно значение с плавающей запятой или структуру. Класс Type инкапсулирует Element и количество элементов, которые нужно выделить (в нашем примере – 10). Type можно рассматривать как массив объектов Element. Класс Allocation выполняет фактическое выделение памяти на основе заданного Type и представляет фактически выделенную память.

В большинстве случаев вам не нужно напрямую вызывать эти API распределения памяти. Классы отраженного слоя автоматически генерируют код для использования этих API. Чтобы выделить память, вам нужно лишь вызвать конструктор, объявленный в одном из классов отраженного слоя, а затем привязать полученный объект Allocation к RenderScript. В некоторых случаях вам может потребоваться использовать эти классы напрямую, чтобы самостоятельно выделять память, например при загрузке растрового изображения из ресурса или когда вы хотите выделить память для указателей на примитивные типы. Подробнее о том, как это сделать, рассказывается в разделе Выделение и привязка памяти к RenderScript. В таблице ниже приведено подробное описание трех классов управления памятью.

Тип объекта Android Описание
Element

Элемент описывает одну ячейку распределения памяти и может иметь две формы: базовую или сложную.

Базовый элемент содержит один компонент данных любого допустимого типа данных RenderScript. Примеры базовых типов данных элементов включают одно значение float, вектор float4 или один цвет RGB-565.

Сложные элементы содержат список базовых элементов и создаются из struct, которые вы объявляете в коде RenderScript. Например, в одном выделении может быть несколько объектов struct, упорядоченных в памяти. Каждая структура считается отдельным элементом, а не типом данных внутри этой структуры.

Type

Тип – это шаблон распределения памяти, который состоит из элемента и одного или нескольких измерений. Он описывает макет памяти (в основном массив Element), но не выделяет память для данных, которые описывает.

Тип состоит из пяти параметров: X, Y, Z, LOD (уровень детализации) и Faces (грани кубической карты). Вы можете задать для размеров X, Y и Z любое положительное целое число в пределах доступной памяти. В одномерном распределении размер X больше нуля, а размеры Y и Z равны нулю, что указывает на их отсутствие. Например, распределение x=10, y=1 считается двумерным, а x=10, y=0 – одномерным. Параметры LOD и Faces являются логическими и указывают на наличие или отсутствие.

Allocation

Выделение памяти предоставляет память для приложений на основе описания памяти, представленного Type. Выделенная память может одновременно существовать в нескольких пространствах памяти. Если память изменяется в одном пространстве, необходимо явно синхронизировать память, чтобы она обновлялась во всех остальных пространствах, в которых она существует.

Данные о распределении загружаются одним из двух основных способов: с проверкой типа и без нее. Для простых массивов есть функции copyFrom(), которые берут массив из системы Android и копируют его в хранилище памяти нативного уровня. Варианты без проверки позволяют системе Android копировать массивы структур, поскольку она не поддерживает структуры. Например, если есть область памяти, которая представляет собой массив из n чисел с плавающей запятой, то можно скопировать данные, содержащиеся в массиве float[n] или массиве byte[n*4].

Как работать с памятью

Нестатическим глобальным переменным, которые вы объявляете в RenderScript, память выделяется во время компиляции. Вы можете работать с этими переменными непосредственно в коде RenderScript, не выделяя для них память на уровне фреймворка Android. Уровень фреймворка Android также имеет доступ к этим переменным с помощью предоставленных методов доступа, которые генерируются в отраженных классах уровня. Если эти переменные инициализируются на уровне среды выполнения RenderScript, эти значения используются для инициализации соответствующих значений на уровне платформы Android. Если глобальные переменные помечены как const, метод set не генерируется. Подробнее…

Примечание. Если вы используете определенные структуры RenderScript, содержащие указатели, например rs_program_fragment и rs_allocation, сначала необходимо получить объект соответствующего класса фреймворка Android, а затем вызвать метод set для этой структуры, чтобы связать память со средой выполнения RenderScript. Вы не можете напрямую управлять этими структурами на уровне среды выполнения RenderScript. Это ограничение неприменимо к пользовательским структурам, содержащим указатели, поскольку их нельзя экспортировать в класс отраженного слоя. Если вы попытаетесь объявить нестатическую глобальную структуру, содержащую указатель, возникнет ошибка компилятора.

RenderScript также поддерживает указатели, но вы должны явно выделить память в коде фреймворка Android. При объявлении глобального указателя в файле .rs вы выделяете память через соответствующий класс отраженного слоя и связываете эту память с нативным слоем RenderScript. Вы можете взаимодействовать с этой памятью как на уровне фреймворка Android, так и на уровне RenderScript, что позволяет изменять переменные на наиболее подходящем уровне.

Выделение и привязка динамической памяти к RenderScript

Чтобы выделить динамическую память, нужно вызвать конструктор класса Script.FieldBase. Это самый распространенный способ. В качестве альтернативы можно создать Allocation вручную, что требуется, например, для указателей примитивных типов. Для простоты рекомендуется использовать конструктор класса Script.FieldBase. После выделения памяти вызовите отраженный метод bind указателя, чтобы связать выделенную память со средой выполнения RenderScript.

В примере ниже память выделяется как для указателя примитивного типа intPointer, так и для указателя на структуру touchPoints. Он также связывает память с 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);

   ...
}

Чтение и запись в память

Вы можете читать и записывать данные в статически и динамически выделенную память как на уровне среды выполнения RenderScript, так и на уровне фреймворка Android.

Статически выделенная память имеет ограничение на одностороннюю связь на уровне среды выполнения RenderScript. Когда код RenderScript изменяет значение переменной, оно не передается обратно на уровень фреймворка Android в целях эффективности. При вызове метода get всегда возвращается последнее значение, заданное фреймворком Android. Однако когда код фреймворка Android изменяет переменную, это изменение может быть автоматически передано в среду выполнения RenderScript или синхронизировано позже. Если вам нужно отправить данные из среды выполнения RenderScript на уровень фреймворка Android, используйте функцию rsSendToClient().

При работе с динамически выделенной памятью любые изменения на уровне среды выполнения RenderScript распространяются обратно на уровень фреймворка Android, если вы изменили выделение памяти с помощью связанного с ним указателя. Изменение объекта на уровне фреймворка Android немедленно распространяется на уровень среды выполнения RenderScript.

Чтение и запись глобальных переменных

Чтение и запись глобальных переменных – это простой процесс. Вы можете использовать методы доступа на уровне фреймворка Android или задать их непосредственно в коде RenderScript. Помните, что любые изменения, которые вы вносите в код RenderScript, не распространяются обратно на уровень фреймворка Android (подробнее об этом читайте здесь).

Например, в файле rsfile.rs объявлена следующая структура:

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

Point_t point;

Вы можете присвоить значения структуре непосредственно в rsfile.rs. Эти значения не передаются обратно на уровень фреймворка Android:

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

Вы можете присвоить значения структуре на уровне фреймворка Android, как показано ниже. Эти значения передаются обратно на уровень среды выполнения RenderScript асинхронно.

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

Вы можете прочитать значения в коде RenderScript следующим образом:

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

Вы можете прочитать значения на уровне фреймворка Android с помощью следующего кода: Учтите, что этот код возвращает значение, только если оно было задано на уровне фреймворка Android. Если вы зададите значение только на уровне среды выполнения 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());

Чтение и запись глобальных указателей

Если память была выделена на уровне фреймворка Android и связана со средой выполнения RenderScript, вы можете читать и записывать данные в память на уровне фреймворка Android, используя методы get и set для этого указателя. В отличие от статически выделенной памяти, на уровне среды выполнения RenderScript вы можете читать и записывать данные в память с помощью указателей, и изменения будут передаваться обратно на уровень фреймворка Android.

Например, если у вас есть указатель на struct в файле rsfile.rs:

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

Point_t *point;

Если вы уже выделили память на уровне фреймворка Android, то можете получить доступ к значениям в struct обычным способом. Любые изменения, которые вы вносите в структуру через ее переменную-указатель, автоматически становятся доступными для уровня фреймворка Android:

Kotlin

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

Java

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

Вы также можете читать и записывать значения в указатель на уровне фреймворка 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);

После того как память уже привязана, вам не нужно повторно привязывать ее к среде выполнения RenderScript каждый раз, когда вы вносите изменения в значение.