RenderScript — это фреймворк для выполнения ресурсоемких вычислительных задач с высокой производительностью на Android. RenderScript в первую очередь ориентирован на использование с параллельными вычислениями данных, хотя последовательные рабочие нагрузки также могут принести пользу. Среда выполнения RenderScript распараллеливает работу между процессорами, доступными на устройстве, такими как многоядерные ЦП и ГП. Это позволяет вам сосредоточиться на выражении алгоритмов, а не на планировании работы. RenderScript особенно полезен для приложений, выполняющих обработку изображений, вычислительную фотографию или компьютерное зрение.
Для начала работы с RenderScript вам следует понять две основные концепции:
- Сам язык является производным от C99 языком для написания высокопроизводительного вычислительного кода. Написание ядра RenderScript описывает, как использовать его для написания вычислительных ядер.
- API управления используется для управления временем жизни ресурсов RenderScript и контроля выполнения ядра. Он доступен на трех разных языках: Java, C++ в Android NDK и на самом языке ядра, производном от C99. Использование RenderScript из кода Java и Single-Source RenderScript описывают первый и третий варианты соответственно.
Написание ядра RenderScript
Ядро RenderScript обычно находится в файле .rs
в каталоге <project_root>/src/rs
; каждый файл .rs
называется скриптом . Каждый скрипт содержит собственный набор ядер, функций и переменных. Скрипт может содержать:
- Декларация прагмы (
#pragma version(1)
), которая объявляет версию языка ядра RenderScript, используемого в этом скрипте. В настоящее время единственным допустимым значением является 1. - Объявление pragma (
#pragma rs java_package_name(com.example.app)
), которое объявляет имя пакета классов Java, отраженных в этом скрипте. Обратите внимание, что ваш файл.rs
должен быть частью пакета вашего приложения, а не в библиотечном проекте. - Ноль или более вызываемых функций . Вызываемая функция — это однопоточная функция RenderScript, которую можно вызывать из кода Java с произвольными аргументами. Они часто полезны для начальной настройки или последовательных вычислений в рамках более крупного конвейера обработки.
Ноль или более глобальных переменных скрипта . Глобальная переменная скрипта похожа на глобальную переменную в C. Вы можете получить доступ к глобальным переменным скрипта из кода Java, и они часто используются для передачи параметров ядрам RenderScript. Глобальные переменные скрипта более подробно описаны здесь .
Ноль или более вычислительных ядер . Вычислительное ядро — это функция или набор функций, которые можно направить на параллельное выполнение среды выполнения RenderScript по набору данных. Существует два типа вычислительных ядер: ядра сопоставления (также называемые ядрами foreach ) и ядра сокращения .
Ядро отображения — это параллельная функция, которая работает с набором
Allocations
тех же измерений. По умолчанию она выполняется один раз для каждой координаты в этих измерениях. Обычно (но не исключительно) она используется для преобразования набора входныхAllocations
в выходноеAllocation
по одномуElement
за раз.Вот пример простого ядра отображения :
uchar4 RS_KERNEL invert(uchar4 in, uint32_t x, uint32_t y) { uchar4 out = in; out.r = 255 - in.r; out.g = 255 - in.g; out.b = 255 - in.b; return out; }
В большинстве случаев это идентично стандартной функции C. Свойство
RS_KERNEL
, примененное к прототипу функции, указывает, что функция является ядром отображения RenderScript, а не вызываемой функцией. Аргументin
автоматически заполняется на основе входногоAllocation
, переданного в запуск ядра. Аргументыx
иy
обсуждаются ниже . Значение, возвращаемое ядром, автоматически записывается в соответствующее место в выходномAllocation
. По умолчанию это ядро запускается по всему своему входномуAllocation
с одним выполнением функции ядра на каждыйElement
вAllocation
.Ядро отображения может иметь один или несколько входных
Allocations
, один выходнойAllocation
или оба. Среда выполнения RenderScript проверяет, чтобы все входные и выходные Allocations имели одинаковые размеры, и чтобы типыElement
входных и выходных Allocations соответствовали прототипу ядра; если любая из этих проверок не пройдена, RenderScript выдает исключение.ПРИМЕЧАНИЕ. До Android 6.0 (уровень API 23) ядро сопоставления не могло иметь более одного входного
Allocation
.Если вам требуется больше входных или выходных
Allocations
, чем есть в ядре, эти объекты следует привязать к глобальным переменным скриптаrs_allocation
и получить к ним доступ из ядра или вызываемой функции черезrsGetElementAt_ type ()
илиrsSetElementAt_ type ()
.ПРИМЕЧАНИЕ:
RS_KERNEL
— это макрос, автоматически определяемый RenderScript для вашего удобства:#define RS_KERNEL __attribute__((kernel))
Ядро сокращения — это семейство функций, которое работает с набором входных
Allocations
тех же измерений. По умолчанию его функция накопления выполняется один раз для каждой координаты в этих измерениях. Обычно (но не исключительно) оно используется для «сокращения» набора входныхAllocations
до одного значения.Вот пример простого редукционного ядра , которое складывает
Elements
своего входа:#pragma rs reduce(addint) accumulator(addintAccum) static void addintAccum(int *accum, int val) { *accum += val; }
Ядро сокращения состоит из одной или нескольких функций, написанных пользователем.
#pragma rs reduce
используется для определения ядра путем указания его имени (в этом примереaddint
), а также имен и ролей функций, составляющих ядро (в этом примере — функция-accumulator
addintAccum
). Все такие функции должны бытьstatic
. Ядро сокращения всегда требует функцию-accumulator
; оно также может иметь другие функции, в зависимости от того, что вы хотите, чтобы ядро делало.Функция аккумулятора ядра редукции должна возвращать
void
и должна иметь не менее двух аргументов. Первый аргумент (accum
, в этом примере) является указателем на элемент данных аккумулятора , а второй (val
, в этом примере) автоматически заполняется на основе входногоAllocation
, переданного при запуске ядра. Элемент данных аккумулятора создается средой выполнения RenderScript; по умолчанию он инициализируется нулем. По умолчанию это ядро запускается по всему входномуAllocation
с одним выполнением функции аккумулятора на каждыйElement
вAllocation
. По умолчанию конечное значение элемента данных аккумулятора рассматривается как результат редукции и возвращается в Java. Среда выполнения RenderScript проверяет, соответствует ли типElement
входного Allocation прототипу функции аккумулятора; если он не совпадает, RenderScript выдает исключение.Ядро сокращения имеет одно или несколько входных
Allocations
, но не имеет выходныхAllocations
.Более подробное описание ядер редукции приведено здесь .
Ядра Reduction поддерживаются в Android 7.0 (API уровня 24) и более поздних версиях.
Функция ядра отображения или функция аккумулятора ядра редукции могут получить доступ к координатам текущего выполнения с помощью специальных аргументов
x
,y
иz
, которые должны иметь типint
илиuint32_t
. Эти аргументы являются необязательными.Функция ядра сопоставления или функция аккумулятора ядра редукции также могут принимать необязательный специальный аргумент
context
типа rs_kernel_context . Он необходим семейству API среды выполнения, которые используются для запроса определенных свойств текущего выполнения — например, rsGetDimX . (Аргументcontext
доступен в Android 6.0 (уровень API 23) и более поздних версиях.)- Необязательная функция
init()
. Функцияinit()
— это особый тип вызываемой функции, которую RenderScript запускает при первом создании скрипта. Это позволяет автоматически выполнять некоторые вычисления при создании скрипта. - Ноль или более статических глобальных переменных и функций скрипта . Статическая глобальная переменная скрипта эквивалентна глобальной переменной скрипта, за исключением того, что к ней нельзя получить доступ из кода Java. Статическая функция — это стандартная функция C, которую можно вызвать из любого ядра или вызываемой функции в скрипте, но которая не отображается в API Java. Если глобальная переменная скрипта или функция не нуждается в доступе из кода Java, настоятельно рекомендуется объявить ее
static
.
Установка точности с плавающей точкой
Вы можете контролировать требуемый уровень точности с плавающей точкой в скрипте. Это полезно, если полный стандарт IEEE 754-2008 (используется по умолчанию) не требуется. Следующие прагмы могут устанавливать другой уровень точности с плавающей точкой:
-
#pragma rs_fp_full
(по умолчанию, если ничего не указано): для приложений, которым требуется точность с плавающей запятой, как указано в стандарте IEEE 754-2008. -
#pragma rs_fp_relaxed
: Для приложений, которые не требуют строгого соответствия IEEE 754-2008 и могут допускать меньшую точность. Этот режим включает сброс в ноль для денормализаций и округление в сторону нуля. -
#pragma rs_fp_imprecise
: Для приложений, не имеющих строгих требований к точности. Этот режим включает все вrs_fp_relaxed
вместе со следующим:- Операции, приводящие к результату -0,0, могут вместо этого возвращать +0,0.
- Операции над INF и NAN не определены.
Большинство приложений могут использовать rs_fp_relaxed
без каких-либо побочных эффектов. Это может быть очень полезно на некоторых архитектурах из-за дополнительных оптимизаций, доступных только с ослабленной точностью (например, инструкции SIMD CPU).
Доступ к API RenderScript из Java
При разработке приложения Android, использующего RenderScript, вы можете получить доступ к его API из Java одним из двух способов:
-
android.renderscript
— API в этом пакете классов доступны на устройствах под управлением Android 3.0 (API уровня 11) и выше. -
android.support.v8.renderscript
— API в этом пакете доступны через библиотеку поддержки , что позволяет использовать их на устройствах под управлением Android 2.3 (API уровня 9) и выше.
Вот компромиссы:
- Если вы используете API библиотеки поддержки, часть RenderScript вашего приложения будет совместима с устройствами под управлением Android 2.3 (API уровня 9) и выше, независимо от того, какие функции RenderScript вы используете. Это позволяет вашему приложению работать на большем количестве устройств, чем при использовании собственных API (
android.renderscript
). - Некоторые функции RenderScript недоступны через API библиотеки поддержки.
- Если вы используете API библиотеки поддержки, вы получите (возможно, значительно) большие APK, чем при использовании собственных (
android.renderscript
) API.
Использование API библиотеки поддержки RenderScript
Для использования API RenderScript библиотеки поддержки необходимо настроить среду разработки для доступа к ним. Для использования этих API требуются следующие инструменты Android SDK:
- Android SDK Tools версии 22.2 или выше
- Android SDK Build-tools версии 18.1.0 или выше
Обратите внимание, что начиная с Android SDK Build-tools 24.0.0, Android 2.2 (API уровня 8) больше не поддерживается.
Вы можете проверить и обновить установленную версию этих инструментов в Android SDK Manager .
Чтобы использовать API-интерфейсы RenderScript библиотеки поддержки:
- Убедитесь, что у вас установлена необходимая версия Android SDK.
- Обновите настройки процесса сборки Android, включив в них настройки RenderScript:
- Откройте файл
build.gradle
в папке app вашего модуля приложения. - Добавьте в файл следующие настройки RenderScript:
Круто
android { compileSdkVersion 33 defaultConfig { minSdkVersion 9 targetSdkVersion 19 renderscriptTargetApi 18 renderscriptSupportModeEnabled true } }
Котлин
android { compileSdkVersion(33) defaultConfig { minSdkVersion(9) targetSdkVersion(19) renderscriptTargetApi = 18 renderscriptSupportModeEnabled = true } }
Перечисленные выше параметры управляют определенным поведением в процессе сборки Android:
-
renderscriptTargetApi
— указывает версию байт-кода, которую нужно сгенерировать. Мы рекомендуем вам установить это значение на самый низкий уровень API, способный предоставить все используемые вами функции, и установитьrenderscriptSupportModeEnabled
наtrue
. Допустимые значения для этого параметра — любое целочисленное значение от 11 до самого последнего выпущенного уровня API. Если ваша минимальная версия SDK, указанная в манифесте приложения, установлена на другое значение, это значение игнорируется, а целевое значение в файле сборки используется для установки минимальной версии SDK. -
renderscriptSupportModeEnabled
— указывает, что сгенерированный байт-код должен вернуться к совместимой версии, если устройство, на котором он запущен, не поддерживает целевую версию.
-
- Откройте файл
- В классах вашего приложения, использующих RenderScript, добавьте импорт для классов библиотеки поддержки:
Котлин
import android.support.v8.renderscript.*
Ява
import android.support.v8.renderscript.*;
Использование RenderScript из кода Java или Kotlin
Использование RenderScript из кода Java или Kotlin опирается на классы API, расположенные в пакете android.renderscript
или android.support.v8.renderscript
. Большинство приложений следуют одному и тому же базовому шаблону использования:
- Инициализируйте контекст RenderScript. Контекст
RenderScript
, созданный с помощьюcreate(Context)
, гарантирует, что RenderScript может быть использован, и предоставляет объект для управления временем жизни всех последующих объектов RenderScript. Вы должны рассматривать создание контекста как потенциально длительную операцию, поскольку она может создавать ресурсы на разных аппаратных средствах; она не должна находиться на критическом пути приложения, если это вообще возможно. Обычно приложение будет иметь только один контекст RenderScript одновременно. - Создайте по крайней мере одно
Allocation
для передачи в скрипт.Allocation
— это объект RenderScript, который предоставляет хранилище для фиксированного объема данных. Ядра в скриптах принимают объектыAllocation
в качестве входных и выходных данных, и к объектамAllocation
можно получить доступ в ядрах с помощьюrsGetElementAt_ type ()
иrsSetElementAt_ type ()
при привязке к глобальным переменным скрипта. ОбъектыAllocation
позволяют передавать массивы из кода Java в код RenderScript и наоборот. ОбъектыAllocation
обычно создаются с помощьюcreateTyped()
илиcreateFromBitmap()
. - Создайте все необходимые скрипты. При использовании RenderScript вам доступны два типа скриптов:
- ScriptC : Это пользовательские скрипты, описанные в разделе Написание ядра RenderScript выше. Каждый скрипт имеет класс Java, отраженный компилятором RenderScript, чтобы упростить доступ к скрипту из кода Java; этот класс имеет имя
ScriptC_ filename
. Например, если бы отображаемое выше ядро было расположено вinvert.rs
, а контекст RenderScript уже был бы расположен вmRenderScript
, код Java или Kotlin для создания экземпляра скрипта был бы следующим:Котлин
val invert = ScriptC_invert(renderScript)
Ява
ScriptC_invert invert = new ScriptC_invert(renderScript);
- ScriptIntrinsic : Это встроенные ядра RenderScript для общих операций, таких как размытие по Гауссу, свертка и смешивание изображений. Для получения дополнительной информации см. подклассы
ScriptIntrinsic
.
- ScriptC : Это пользовательские скрипты, описанные в разделе Написание ядра RenderScript выше. Каждый скрипт имеет класс Java, отраженный компилятором RenderScript, чтобы упростить доступ к скрипту из кода Java; этот класс имеет имя
- Заполнить Allocation данными. За исключением Allocation, созданных с помощью
createFromBitmap()
, Allocation заполняется пустыми данными при первом создании. Чтобы заполнить Allocation, используйте один из методов "copy" вAllocation
. Методы "copy" являются синхронными . - Установите любые необходимые глобальные переменные скрипта . Вы можете установить глобальные переменные с помощью методов в том же классе
ScriptC_ filename
с именемset_ globalname
. Например, чтобы установить переменнуюint
с именемthreshold
, используйте метод Javaset_threshold(int)
; а чтобы установить переменнуюrs_allocation
с именемlookup
, используйте метод Javaset_lookup(Allocation)
. Методыset
являются асинхронными . - Запустите соответствующие ядра и вызываемые функции.
Методы запуска данного ядра отражены в том же классе
ScriptC_ filename
с методами, названнымиforEach_ mappingKernelName ()
илиreduce_ reductionKernelName ()
. Эти запуски являются асинхронными . В зависимости от аргументов ядра метод принимает одно или несколько выделений, все из которых должны иметь одинаковые измерения. По умолчанию ядро выполняется по каждой координате в этих измерениях; чтобы выполнить ядро по подмножеству этих координат, передайте соответствующийScript.LaunchOptions
в качестве последнего аргумента методуforEach
илиreduce
.Запуск вызываемых функций с использованием методов
invoke_ functionName
отраженных в том же классеScriptC_ filename
. Эти запуски асинхронны . - Извлечение данных из объектов
Allocation
и объектов javaFutureType . Чтобы получить доступ к данным изAllocation
из кода Java, необходимо скопировать эти данные обратно в Java с помощью одного из методов "copy" вAllocation
. Чтобы получить результат ядра сокращения, необходимо использовать методjavaFutureType .get()
. Методы "copy" иget()
являются синхронными . - Разрушить контекст RenderScript. Вы можете уничтожить контекст RenderScript с помощью
destroy()
или разрешив сборщику мусора объект контекста RenderScript. Это приведет к тому, что любое дальнейшее использование любого объекта, принадлежащего этому контексту, вызовет исключение.
Асинхронная модель выполнения
Отраженные методы forEach
, invoke
, reduce
и set
являются асинхронными — каждый из них может вернуться в Java до завершения запрошенного действия. Однако отдельные действия сериализуются в том порядке, в котором они запускаются.
Класс Allocation
предоставляет методы "copy" для копирования данных в и из Allocations. Метод "copy" является синхронным и сериализуется относительно любого из асинхронных действий выше, которые касаются того же Allocation.
Отраженные классы javaFutureType предоставляют метод get()
для получения результата редукции. get()
является синхронным и сериализуется относительно редукции (которая является асинхронной).
RenderScript с одним источником
Android 7.0 (уровень API 24) представляет новую функцию программирования под названием Single-Source RenderScript , в которой ядра запускаются из скрипта, в котором они определены, а не из Java. Этот подход в настоящее время ограничен отображением ядер, которые в этом разделе для краткости просто называются «ядрами». Эта новая функция также поддерживает создание распределений типа rs_allocation
изнутри скрипта. Теперь можно реализовать целый алгоритм исключительно в скрипте, даже если требуется несколько запусков ядра. Преимущество двоякое: более читаемый код, поскольку он сохраняет реализацию алгоритма на одном языке; и потенциально более быстрый код из-за меньшего количества переходов между Java и RenderScript при нескольких запусках ядра.
В Single-Source RenderScript вы пишете ядра, как описано в Writing a RenderScript Kernel . Затем вы пишете вызываемую функцию, которая вызывает rsForEach()
для их запуска. Этот API принимает функцию ядра в качестве первого параметра, за которым следуют распределения ввода и вывода. Похожий API rsForEachWithOptions()
принимает дополнительный аргумент типа rs_script_call_t
, который указывает подмножество элементов из распределений ввода и вывода для обработки функцией ядра.
Чтобы начать вычисление RenderScript, вы вызываете вызываемую функцию из Java. Следуйте инструкциям в разделе Использование RenderScript из кода Java . На этапе запуск соответствующих ядер вызовите вызываемую функцию с помощью invoke_ function_name ()
, что запустит все вычисление, включая запуск ядер.
Выделения часто требуются для сохранения и передачи промежуточных результатов от одного запуска ядра к другому. Вы можете создать их с помощью rsCreateAllocation() . Одной из простых в использовании форм этого API является rsCreateAllocation_<T><W>(…)
, где T — тип данных для элемента, а W — ширина вектора для элемента. API принимает размеры в измерениях X, Y и Z в качестве аргументов. Для одномерных или двумерных выделений размер для измерения Y или Z можно опустить. Например, rsCreateAllocation_uchar4(16384)
создает одномерное выделение из 16384 элементов, каждый из которых имеет тип uchar4
.
Распределения управляются системой автоматически. Вам не нужно явно освобождать или освобождать их. Однако вы можете вызвать rsClearObject(rs_allocation* alloc)
чтобы указать, что вам больше не нужен дескриптор alloc
для базового распределения, чтобы система могла освободить ресурсы как можно раньше.
Раздел Writing a RenderScript Kernel содержит пример ядра, которое инвертирует изображение. Пример ниже расширяет его для применения более одного эффекта к изображению с помощью Single-Source RenderScript. Он включает еще одно ядро, greyscale
, которое превращает цветное изображение в черно-белое. Вызываемая функция process()
затем последовательно применяет эти два ядра к входному изображению и создает выходное изображение. Выделения как для ввода, так и для вывода передаются как аргументы типа rs_allocation
.
// File: singlesource.rs #pragma version(1) #pragma rs java_package_name(com.android.rssample) static const float4 weight = {0.299f, 0.587f, 0.114f, 0.0f}; uchar4 RS_KERNEL invert(uchar4 in, uint32_t x, uint32_t y) { uchar4 out = in; out.r = 255 - in.r; out.g = 255 - in.g; out.b = 255 - in.b; return out; } uchar4 RS_KERNEL greyscale(uchar4 in) { const float4 inF = rsUnpackColor8888(in); const float4 outF = (float4){ dot(inF, weight) }; return rsPackColorTo8888(outF); } void process(rs_allocation inputImage, rs_allocation outputImage) { const uint32_t imageWidth = rsAllocationGetDimX(inputImage); const uint32_t imageHeight = rsAllocationGetDimY(inputImage); rs_allocation tmp = rsCreateAllocation_uchar4(imageWidth, imageHeight); rsForEach(invert, inputImage, tmp); rsForEach(greyscale, tmp, outputImage); }
Функцию process()
можно вызвать из Java или Kotlin следующим образом:
Котлин
val RS: RenderScript = RenderScript.create(context) val script = ScriptC_singlesource(RS) val inputAllocation: Allocation = Allocation.createFromBitmapResource( RS, resources, R.drawable.image ) val outputAllocation: Allocation = Allocation.createTyped( RS, inputAllocation.type, Allocation.USAGE_SCRIPT or Allocation.USAGE_IO_OUTPUT ) script.invoke_process(inputAllocation, outputAllocation)
Ява
// File SingleSource.java RenderScript RS = RenderScript.create(context); ScriptC_singlesource script = new ScriptC_singlesource(RS); Allocation inputAllocation = Allocation.createFromBitmapResource( RS, getResources(), R.drawable.image); Allocation outputAllocation = Allocation.createTyped( RS, inputAllocation.getType(), Allocation.USAGE_SCRIPT | Allocation.USAGE_IO_OUTPUT); script.invoke_process(inputAllocation, outputAllocation);
Этот пример показывает, как алгоритм, включающий два запуска ядра, может быть полностью реализован на самом языке RenderScript. Без Single-Source RenderScript вам пришлось бы запускать оба ядра из кода Java, отделяя запуски ядра от определений ядра и усложняя понимание всего алгоритма. Код Single-Source RenderScript не только легче читать, но и устраняет переход между Java и скриптом между запусками ядра. Некоторые итеративные алгоритмы могут запускать ядра сотни раз, что делает накладные расходы на такой переход значительными.
Скрипт Глобальные
Глобальная переменная скрипта — это обычная static
глобальная переменная в файле скрипта ( .rs
). Для глобальной переменной скрипта с именем var , определенной в файле filename .rs
, будет метод get_ var
отраженный в классе ScriptC_ filename
. Если глобальная переменная не является const
, будет также метод set_ var
.
У данного скриптового глобального есть два отдельных значения — значение Java и значение скрипта . Эти значения ведут себя следующим образом:
- Если var имеет статический инициализатор в скрипте, он указывает начальное значение var как в Java, так и в скрипте. В противном случае это начальное значение равно нулю.
- Доступ к переменной var внутри скрипта для чтения и записи ее значения скрипта.
- Метод
get_ var
считывает значение Java. - Метод
set_ var
(если он существует) немедленно записывает значение Java и асинхронно записывает значение скрипта.
ПРИМЕЧАНИЕ. Это означает, что за исключением любого статического инициализатора в скрипте, значения, записанные в глобальную переменную из скрипта, не видны Java.
Глубокое сокращение ядер
Редукция — это процесс объединения набора данных в одно значение. Это полезный примитив в параллельном программировании, с такими приложениями, как:
- вычисление суммы или произведения всех данных
- вычисление логических операций (
and
,or
,xor
) над всеми данными - нахождение минимального или максимального значения в данных
- поиск определенного значения или координат определенного значения в данных
В Android 7.0 (уровень API 24) и более поздних версиях RenderScript поддерживает ядра сокращения , позволяющие использовать эффективные пользовательские алгоритмы сокращения. Вы можете запускать ядра сокращения на входах с 1, 2 или 3 измерениями.
Пример выше показывает простое ядро сокращения addint . Вот более сложное ядро сокращения findMinAndMax , которое находит местоположения минимальных и максимальных long
значений в одномерном Allocation
:
#define LONG_MAX (long)((1UL << 63) - 1) #define LONG_MIN (long)(1UL << 63) #pragma rs reduce(findMinAndMax) \ initializer(fMMInit) accumulator(fMMAccumulator) \ combiner(fMMCombiner) outconverter(fMMOutConverter) // Either a value and the location where it was found, or INITVAL. typedef struct { long val; int idx; // -1 indicates INITVAL } IndexedVal; typedef struct { IndexedVal min, max; } MinAndMax; // In discussion below, this initial value { { LONG_MAX, -1 }, { LONG_MIN, -1 } } // is called INITVAL. static void fMMInit(MinAndMax *accum) { accum->min.val = LONG_MAX; accum->min.idx = -1; accum->max.val = LONG_MIN; accum->max.idx = -1; } //---------------------------------------------------------------------- // In describing the behavior of the accumulator and combiner functions, // it is helpful to describe hypothetical functions // IndexedVal min(IndexedVal a, IndexedVal b) // IndexedVal max(IndexedVal a, IndexedVal b) // MinAndMax minmax(MinAndMax a, MinAndMax b) // MinAndMax minmax(MinAndMax accum, IndexedVal val) // // The effect of // IndexedVal min(IndexedVal a, IndexedVal b) // is to return the IndexedVal from among the two arguments // whose val is lesser, except that when an IndexedVal // has a negative index, that IndexedVal is never less than // any other IndexedVal; therefore, if exactly one of the // two arguments has a negative index, the min is the other // argument. Like ordinary arithmetic min and max, this function // is commutative and associative; that is, // // min(A, B) == min(B, A) // commutative // min(A, min(B, C)) == min((A, B), C) // associative // // The effect of // IndexedVal max(IndexedVal a, IndexedVal b) // is analogous (greater . . . never greater than). // // Then there is // // MinAndMax minmax(MinAndMax a, MinAndMax b) { // return MinAndMax(min(a.min, b.min), max(a.max, b.max)); // } // // Like ordinary arithmetic min and max, the above function // is commutative and associative; that is: // // minmax(A, B) == minmax(B, A) // commutative // minmax(A, minmax(B, C)) == minmax((A, B), C) // associative // // Finally define // // MinAndMax minmax(MinAndMax accum, IndexedVal val) { // return minmax(accum, MinAndMax(val, val)); // } //---------------------------------------------------------------------- // This function can be explained as doing: // *accum = minmax(*accum, IndexedVal(in, x)) // // This function simply computes minimum and maximum values as if // INITVAL.min were greater than any other minimum value and // INITVAL.max were less than any other maximum value. Note that if // *accum is INITVAL, then this function sets // *accum = IndexedVal(in, x) // // After this function is called, both accum->min.idx and accum->max.idx // will have nonnegative values: // - x is always nonnegative, so if this function ever sets one of the // idx fields, it will set it to a nonnegative value // - if one of the idx fields is negative, then the corresponding // val field must be LONG_MAX or LONG_MIN, so the function will always // set both the val and idx fields static void fMMAccumulator(MinAndMax *accum, long in, int x) { IndexedVal me; me.val = in; me.idx = x; if (me.val <= accum->min.val) accum->min = me; if (me.val >= accum->max.val) accum->max = me; } // This function can be explained as doing: // *accum = minmax(*accum, *val) // // This function simply computes minimum and maximum values as if // INITVAL.min were greater than any other minimum value and // INITVAL.max were less than any other maximum value. Note that if // one of the two accumulator data items is INITVAL, then this // function sets *accum to the other one. static void fMMCombiner(MinAndMax *accum, const MinAndMax *val) { if ((accum->min.idx < 0) || (val->min.val < accum->min.val)) accum->min = val->min; if ((accum->max.idx < 0) || (val->max.val > accum->max.val)) accum->max = val->max; } static void fMMOutConverter(int2 *result, const MinAndMax *val) { result->x = val->min.idx; result->y = val->max.idx; }
ПРИМЕЧАНИЕ: Здесь можно найти больше примеров ядер сокращения.
Для запуска ядра редукции среда выполнения RenderScript создает одну или несколько переменных, называемых элементами данных аккумулятора, для хранения состояния процесса редукции. Среда выполнения RenderScript выбирает количество элементов данных аккумулятора таким образом, чтобы максимизировать производительность. Тип элементов данных аккумулятора ( accumType ) определяется функцией аккумулятора ядра — первым аргументом этой функции является указатель на элемент данных аккумулятора. По умолчанию каждый элемент данных аккумулятора инициализируется нулем (как если бы это было сделано с помощью memset
); однако вы можете написать функцию инициализации , которая будет выполнять другие действия.
Пример: В ядре addint элементы данных аккумулятора (типа int
) используются для сложения входных значений. Функция инициализации отсутствует, поэтому каждый элемент данных аккумулятора инициализируется нулем.
Пример: В ядре findMinAndMax элементы данных аккумулятора (типа MinAndMax
) используются для отслеживания минимальных и максимальных значений, найденных до сих пор. Существует функция инициализации, которая устанавливает их в LONG_MAX
и LONG_MIN
соответственно; и устанавливает местоположения этих значений в -1, указывая, что значения фактически отсутствуют в (пустой) части обработанного ввода.
RenderScript вызывает вашу функцию аккумулятора один раз для каждой координаты во входных данных. Обычно ваша функция должна обновлять элемент данных аккумулятора каким-либо образом в соответствии с входными данными.
Пример: в ядре addint функция аккумулятора добавляет значение входного элемента к элементу данных аккумулятора.
Пример: В ядре findMinAndMax функция аккумулятора проверяет, является ли значение входного элемента меньшим или равным минимальному значению, записанному в элементе данных аккумулятора, и/или большим или равным максимальному значению, записанному в элементе данных аккумулятора, и обновляет элемент данных аккумулятора соответствующим образом.
После того, как функция аккумулятора была вызвана один раз для каждой координаты во входных данных, RenderScript должен объединить элементы данных аккумулятора в один элемент данных аккумулятора. Вы можете написать функцию-комбинатор , чтобы сделать это. Если функция аккумулятора имеет один вход и не имеет специальных аргументов , то вам не нужно писать функцию-комбинатор; RenderScript будет использовать функцию-комбинатор для объединения элементов данных аккумулятора. (Вы все равно можете написать функцию-комбинатор, если это поведение по умолчанию вам не нужно.)
Пример: В ядре addint нет функции combiner, поэтому будет использоваться функция accumulator. Это правильное поведение, потому что если мы разделим коллекцию значений на две части и сложим значения в этих двух частях по отдельности, то сложение этих двух сумм будет таким же, как и сложение всей коллекции.
Пример: В ядре findMinAndMax функция объединения проверяет, меньше ли минимальное значение, записанное в элементе данных аккумулятора "source" *val
, чем минимальное значение, записанное в элементе данных аккумулятора "destination" *accum
, и обновляет *accum
соответствующим образом. Она выполняет аналогичную работу для максимального значения. Это обновляет *accum
до состояния, которое было бы, если бы все входные значения были накоплены в *accum
а не некоторые в *accum
, а некоторые в *val
.
После объединения всех элементов данных аккумулятора RenderScript определяет результат редукции для возврата в Java. Для этого можно написать функцию outconverter . Вам не нужно писать функцию outconverter, если вы хотите, чтобы конечное значение объединенных элементов данных аккумулятора было результатом редукции.
Пример: В ядре addint нет функции outconverter. Конечное значение объединенных элементов данных — это сумма всех элементов ввода, что является значением, которое мы хотим вернуть.
Пример: в ядре findMinAndMax функция outconverter инициализирует результирующее значение int2
для хранения местоположений минимального и максимального значений, полученных в результате объединения всех элементов данных аккумулятора.
Написание редукционного ядра
#pragma rs reduce
определяет ядро редукции, указывая его имя, а также имена и роли функций, составляющих ядро. Все такие функции должны быть static
. Ядро редукции всегда требует функцию accumulator
; вы можете опустить некоторые или все другие функции, в зависимости от того, что вы хотите, чтобы ядро делало.
#pragma rs reduce(kernelName) \ initializer(initializerName) \ accumulator(accumulatorName) \ combiner(combinerName) \ outconverter(outconverterName)
Значение элементов в #pragma
следующее:
-
reduce( kernelName )
(обязательно): Указывает, что определяется ядро reduce. Отраженный метод Javareduce_ kernelName
запустит ядро. initializer( initializerName )
(необязательно): Указывает имя функции инициализатора для этого ядра редукции. При запуске ядра RenderScript вызывает эту функцию один раз для каждого элемента данных аккумулятора . Функция должна быть определена следующим образом:static void initializerName(accumType *accum) { … }
accum
— указатель на элемент данных аккумулятора, который эта функция инициализирует.Если вы не предоставите функцию инициализации, RenderScript инициализирует каждый элемент данных аккумулятора нулем (как если бы это было сделано с помощью
memset
), ведя себя так, как если бы была функция инициализации, которая выглядит следующим образом:static void initializerName(accumType *accum) { memset(accum, 0, sizeof(*accum)); }
accumulator( accumulatorName )
(обязательно): Указывает имя функции аккумулятора для этого ядра редукции. При запуске ядра RenderScript вызывает эту функцию один раз для каждой координаты во входных данных, чтобы обновить элемент данных аккумулятора каким-либо образом в соответствии с входными данными. Функция должна быть определена следующим образом:static void accumulatorName(accumType *accum, in1Type in1, …, inNType inN [, specialArguments]) { … }
accum
— указатель на элемент данных аккумулятора для изменения этой функцией.in1
—in N
— один или несколько аргументов, которые автоматически заполняются на основе входных данных, переданных при запуске ядра, по одному аргументу на вход. Функция аккумулятора может опционально принимать любой из специальных аргументов .Примером ядра с несколькими входами является
dotProduct
.-
combiner( combinerName )
(необязательно): Указывает имя функции объединения для этого ядра редукции. После того, как RenderScript вызывает функцию накопления один раз для каждой координаты во входных данных, он вызывает эту функцию столько раз, сколько необходимо для объединения всех элементов данных накопления в один элемент данных накопления. Функция должна быть определена следующим образом:
static void combinerName(accumType *accum, const accumType *other) { … }
accum
— это указатель на элемент данных аккумулятора «назначения», который эта функция может изменить.other
— это указатель на элемент данных аккумулятора «источника», который эта функция может «объединить» в*accum
.ПРИМЕЧАНИЕ: Возможно, что
*accum
,*other
или оба были инициализированы, но никогда не передавались в функцию аккумулятора; то есть один или оба никогда не обновлялись в соответствии с какими-либо входными данными. Например, в ядре findMinAndMax функция объединенияfMMCombiner
явно проверяетidx < 0
поскольку это указывает на такой элемент данных аккумулятора, значение которого равно INITVAL .Если вы не предоставите функцию объединения, RenderScript вместо нее использует функцию накопления, ведя себя так, как если бы была функция объединения, которая выглядит следующим образом:
static void combinerName(accumType *accum, const accumType *other) { accumulatorName(accum, *other); }
Функция объединения обязательна, если ядро имеет более одного входа, если тип входных данных не совпадает с типом данных аккумулятора или если функция аккумулятора принимает один или несколько специальных аргументов .
outconverter( outconverterName )
(необязательно): Указывает имя функции outconverter для этого ядра редукции. После того, как RenderScript объединяет все элементы данных аккумулятора, он вызывает эту функцию, чтобы определить результат редукции для возврата в Java. Функция должна быть определена следующим образом:static void outconverterName(resultType *result, const accumType *accum) { … }
result
— указатель на элемент данных результата (выделенный, но не инициализированный средой выполнения RenderScript) для этой функции для инициализации с результатом редукции. resultType — тип этого элемента данных, который не обязательно должен совпадать с accumType .accum
— указатель на конечный элемент данных аккумулятора, вычисленный функцией combiner .Если вы не предоставите функцию выходного преобразователя, RenderScript копирует конечный элемент данных аккумулятора в элемент данных результата, ведя себя так, как если бы была функция выходного преобразователя, которая выглядит следующим образом:
static void outconverterName(accumType *result, const accumType *accum) { *result = *accum; }
Если вам нужен другой тип результата, нежели тип данных аккумулятора, то функция outconverter обязательна.
Обратите внимание, что ядро имеет входные типы, тип элемента данных аккумулятора и тип результата, ни один из которых не обязательно должен быть одинаковым. Например, в ядре findMinAndMax входной тип long
, тип элемента данных аккумулятора MinAndMax
и тип результата int2
— все они разные.
Чего вы не можете предположить?
Вы не должны полагаться на количество элементов данных аккумулятора, созданных renderscript для данного запуска ядра. Нет никакой гарантии, что два запуска одного и того же ядра с одинаковым входом (ы) создаст одинаковое количество элементов данных аккумулятора.
Вы не должны полагаться на порядок, в котором рендерпикса вызывает функции инициализатора, аккумулятора и комбинации; Это может даже назвать некоторых из них параллельно. Нет никакой гарантии, что два запуска одного и того же ядра с одним и тем же входом будут следовать тому же порядку. Единственная гарантия заключается в том, что только функция инициализатора когда -либо увидит ненициализированный элемент данных аккумулятора. Например:
- Нет никакой гарантии, что все элементы данных аккумулятора будут инициализированы до того, как будет вызвана функция аккумулятора, хотя она будет вызвана только инициализированным элементом данных аккумулятора.
- Нет никаких гарантий на порядок, в котором входные элементы передаются в функцию аккумулятора.
- Нет никакой гарантии, что функция аккумулятора была вызвана для всех входных элементов до того, как будет вызвана функция Combiner.
Одним из последствий этого является то, что ядро FindMinandMax не является детерминированным: если вход содержит более одного возникновения одного и того же минимального или максимального значения, у вас нет способа узнать, какое возникновение найдет ядро.
Что вы должны гарантировать?
Поскольку система рендеров может выбрать для выполнения ядра по -разному , вы должны следовать определенным правилам, чтобы убедиться, что ваше ядро ведет себя так, как вы хотите. Если вы не следовали этим правилам, вы можете получить неверные результаты, неэнергиническое поведение или ошибки времени выполнения.
Приведенные ниже правила часто говорят, что два элемента данных аккумулятора должны иметь « одинаковое значение» . Что это значит? Это зависит от того, что вы хотите, чтобы у ядра. Для математического сокращения, такого как AddInt , обычно имеет смысл для «одного и того же» означать математическое равенство. Для «выбора любого» поиска, такого как FindMinandMax («Найдите местонахождение минимальных и максимальных входных значений»), где может быть более одного возникновения идентичных входных значений, все места данного входного значения должны рассматриваться как «одинаковое». Вы можете написать аналогичное ядро, чтобы «найти местоположение самого левого минимального и максимального входного значения», где (скажем) минимальное значение в месте 100 предпочтительнее, чем идентичное минимальное значение в месте 200; Для этого ядра «то же самое» означало бы идентичное местоположение , а не просто идентичное значение , а функции аккумулятора и комбината должны отличаться от функций для FindminandMax .
Функция инициализатора должна создать значение идентификации . То есть, еслиI
и A
являются элементами данных аккумулятора, инициализированными функцией инициализатора, и I
никогда не был передан функции аккумулятора (но A
, был), тогда-
combinerName (& A , & I )
должен оставитьA
же самое -
combinerName (& I , & A )
должен оставитьI
таким же ,A
Пример: в ядре AddInt элемент данных аккумулятора инициализируется до нуля. Функция Combiner для этого ядра выполняет дополнение; Ноль является значением идентификации для добавления.
Пример: в ядре FindMinandMax элемент данных аккумулятора инициализируется для INITVAL
.
-
fMMCombiner(& A , & I )
оставляетA
, потому чтоI
INITVAL
. -
fMMCombiner(& I , & A )
устанавливаетI
наA
, потому чтоI
INITVAL
.
Следовательно, INITVAL
действительно является значением личности.
Функция комбинации должна быть коммутативной . То есть, если A
и B
являются элементами данных аккумулятора, инициализированными функцией инициализатора, и которые могут быть переданы в функцию аккумулятора нулевым или более раз, то combinerName (& A , & B )
должны установить A
на то же значение , что и combinerName (& B , & A )
настройки B
.
Пример: в ядре Addint функция Combiner добавляет два значения элементов данных аккумулятора; Дополнение коммутативно.
Пример: в ядре FindminandMax , fMMCombiner(& A , & B )
такой же, как A = minmax( A , B )
, а minmax
является коммутативным, так что fMMCombiner
также.
Функция комбинации должна быть ассоциативной . То есть, если A
, B
и C
являются элементами данных аккумулятора, инициализированными функцией инициализатора, и это может быть передано в функцию аккумулятора нулевым или более раз, тогда следующие две кодовые последовательности должны установить A
на одно и то же значение :
combinerName(&A, &B); combinerName(&A, &C);
combinerName(&B, &C); combinerName(&A, &B);
Пример: в ядре Addint функция Combiner добавляет два значения элементов данных аккумулятора:
A = A + B A = A + C // Same as // A = (A + B) + C
B = B + C A = A + B // Same as // A = A + (B + C) // B = B + C
Дополнение является ассоциативным, и поэтому функция Combiner также.
Пример: в ядре Findminandmax ,
fMMCombiner(&A, &B)
A = minmax(A, B)
A = minmax(A, B) A = minmax(A, C) // Same as // A = minmax(minmax(A, B), C)
B = minmax(B, C) A = minmax(A, B) // Same as // A = minmax(A, minmax(B, C)) // B = minmax(B, C)
minmax
ассоциативен, и поэтому fMMCombiner
также.
Функция аккумулятора и функция комбинации вместе должны подчиняться основному правилу складывания . То есть, если A
и B
являются элементами данных аккумулятора, A
инициализирована функцией инициализатора и, возможно, были переданы в функцию аккумулятора нулевой или более раз, B
не был инициализирован, и ARGS является списком аргументов входных и специальных аргументов для конкретного вызова к функции аккумулятора, тогда следующие два кодовых последовательности должны устанавливать A
и то же значение :
accumulatorName(&A, args); // statement 1
initializerName(&B); // statement 2 accumulatorName(&B, args); // statement 3 combinerName(&A, &B); // statement 4
Пример: в ядре Addint для входного значения V :
- Оператор 1 такой же, как
A += V
- Оператор 2 то же самое, что
B = 0
- Оператор 3 совпадает с
B += V
, что такое же, какB = V
- Оператор 4 совпадает с
A += B
, что такое же, какA += V
Заявления 1 и 4 устанавливают A
к тому же значению, и поэтому это ядро подчиняется основному правилу складывания.
Пример: в ядре FindMinandMax для входного значения V при координате x :
- Оператор 1 то же самое, что
A = minmax(A, IndexedVal( V , X ))
- Оператор 2 такой же, как
B = INITVAL
- Заявление 3 такое же, как и
что, потому что B является начальным значением, совпадает сB = minmax(B, IndexedVal(V, X))
B = IndexedVal(V, X)
- Заявление 4 такое же, как и
что такое же, какA = minmax(A, B)
A = minmax(A, IndexedVal(V, X))
Заявления 1 и 4 устанавливают A
к тому же значению, и поэтому это ядро подчиняется основному правилу складывания.
Вызов ядра сокращения из кода Java
Для восстановления ядра с именем kernelname ScriptC_ filename
определенного в filename .rs
Котлин
// Function 1 fun reduce_kernelName(ain1: Allocation, …, ainN: Allocation): javaFutureType // Function 2 fun reduce_kernelName(ain1: Allocation, …, ainN: Allocation, sc: Script.LaunchOptions): javaFutureType // Function 3 fun reduce_kernelName(in1: Array<devecSiIn1Type>, …, inN: Array<devecSiInNType>): javaFutureType
Ява
// Method 1 public javaFutureType reduce_kernelName(Allocation ain1, …, Allocation ainN); // Method 2 public javaFutureType reduce_kernelName(Allocation ain1, …, Allocation ainN, Script.LaunchOptions sc); // Method 3 public javaFutureType reduce_kernelName(devecSiIn1Type[] in1, …, devecSiInNType[] inN);
Вот несколько примеров вызова ядра Addint :
Котлин
val script = ScriptC_example(renderScript) // 1D array // and obtain answer immediately val input1 = intArrayOf(…) val sum1: Int = script.reduce_addint(input1).get() // Method 3 // 2D allocation // and do some additional work before obtaining answer val typeBuilder = Type.Builder(RS, Element.I32(RS)).apply { setX(…) setY(…) } val input2: Allocation = Allocation.createTyped(RS, typeBuilder.create()).also { populateSomehow(it) // fill in input Allocation with data } val result2: ScriptC_example.result_int = script.reduce_addint(input2) // Method 1 doSomeAdditionalWork() // might run at same time as reduction val sum2: Int = result2.get()
Ява
ScriptC_example script = new ScriptC_example(renderScript); // 1D array // and obtain answer immediately int input1[] = …; int sum1 = script.reduce_addint(input1).get(); // Method 3 // 2D allocation // and do some additional work before obtaining answer Type.Builder typeBuilder = new Type.Builder(RS, Element.I32(RS)); typeBuilder.setX(…); typeBuilder.setY(…); Allocation input2 = createTyped(RS, typeBuilder.create()); populateSomehow(input2); // fill in input Allocation with data ScriptC_example.result_int result2 = script.reduce_addint(input2); // Method 1 doSomeAdditionalWork(); // might run at same time as reduction int sum2 = result2.get();
Метод 1 имеет один аргумент Allocation
ввода для каждого входного аргумента в функции аккумулятора ядра. Среда выполнения рендеров проверяет, что все входные распределения имеют одинаковые размеры и что тип Element
каждого из входных распределений соответствует соответствующему входному аргументу прототипа функции аккумулятора. Если какая -либо из этих проверок не удастся, renderscript бросает исключение. Ядро выполняет каждую координату в этих измерениях.
Метод 2 такой же, как метод 1, за исключением того, что метод 2 принимает дополнительный аргумент, sc
можно использовать для ограничения выполнения ядра до подмножества координат.
Метод 3 такой же, как и метод 1, за исключением того, что вместо того, чтобы принимать входы на распределение, он принимает входы массива Java. Это удобство, которое избавляет вас от необходимости записать код, чтобы явно создать данные и копировать его из массива Java. Однако использование метода 3 вместо метода 1 не увеличивает производительность кода . Для каждой входной массивы метод 3 создает временное 1-мерное распределение с подходящим типом Element
и включенным setAutoPadding(boolean)
и копирует массив с распределением, как если бы с помощью соответствующего метода Allocation
copyFrom()
. Затем он вызывает метод 1, передавая эти временные распределения.
ПРИМЕЧАНИЕ. Если ваше приложение сделает несколько вызовов ядра с одним и тем же массивом, или с разными массивами одинаковых измерений и типа элемента, вы можете улучшить производительность, явно создавая, заполняя и повторно используя распределения, вместо использования метода 3.
Javafuturetype , тип возврата отраженных методов сокращения, является отраженным статическим вложенным классом в классе ScriptC_ filename
. Он представляет собой будущий результат пробега сокращения ядра. Чтобы получить фактический результат прогона, вызовите метод get()
этого класса, который возвращает значение типа javaresulttype . get()
синхронно .
Котлин
class ScriptC_filename(rs: RenderScript) : ScriptC(…) { object javaFutureType { fun get(): javaResultType { … } } }
Ява
public class ScriptC_filename extends ScriptC { public static class javaFutureType { public javaResultType get() { … } } }
javaresulttype определяется из результата функции Outconverter . Если RESTORKTYPE не является беззнатным типом (скаляр, вектор или массив), JavaresultType является непосредственно соответствующим типом Java. Если ResultType является безрецептурным типом, и есть более крупный подписанный Java -тип, то JavaresultType - это то, что более крупный подписанный Java -тип; В противном случае это непосредственно соответствующий тип Java. Например:
- Если ResoudType
int
,int2
илиint[15]
, то javaresulttype - этоint
,Int2
илиint[]
. Все значения ResultType могут быть представлены Javaresulttype . - Если ResultType
uint
,uint2
илиuint[15]
, то javaresulttype - этоlong
,Long2
илиlong[]
. Все значения ResultType могут быть представлены Javaresulttype . - Если ResoudType -
ulong
,ulong2
илиulong[15]
, то javaresulttype - этоlong
,Long2
илиlong[]
. Существуют определенные значения результата , которые не могут быть представлены Javaresulttype .
Javafuturetype - это будущий тип результата, соответствующий результату функции Outconverter .
- Если ResultType не является типом массива, то javafuturetype -
result_ resultType
. - Если ResultType является массивом количества длины с членами типа Membertype , то JavafutureType - это
resultArray Count _ memberType
.
Например:
Котлин
class ScriptC_filename(rs: RenderScript) : ScriptC(…) { // for kernels with int result object result_int { fun get(): Int = … } // for kernels with int[10] result object resultArray10_int { fun get(): IntArray = … } // for kernels with int2 result // note that the Kotlin type name "Int2" is not the same as the script type name "int2" object result_int2 { fun get(): Int2 = … } // for kernels with int2[10] result // note that the Kotlin type name "Int2" is not the same as the script type name "int2" object resultArray10_int2 { fun get(): Array<Int2> = … } // for kernels with uint result // note that the Kotlin type "long" is a wider signed type than the unsigned script type "uint" object result_uint { fun get(): Long = … } // for kernels with uint[10] result // note that the Kotlin type "long" is a wider signed type than the unsigned script type "uint" object resultArray10_uint { fun get(): LongArray = … } // for kernels with uint2 result // note that the Kotlin type "Long2" is a wider signed type than the unsigned script type "uint2" object result_uint2 { fun get(): Long2 = … } // for kernels with uint2[10] result // note that the Kotlin type "Long2" is a wider signed type than the unsigned script type "uint2" object resultArray10_uint2 { fun get(): Array<Long2> = … } }
Ява
public class ScriptC_filename extends ScriptC { // for kernels with int result public static class result_int { public int get() { … } } // for kernels with int[10] result public static class resultArray10_int { public int[] get() { … } } // for kernels with int2 result // note that the Java type name "Int2" is not the same as the script type name "int2" public static class result_int2 { public Int2 get() { … } } // for kernels with int2[10] result // note that the Java type name "Int2" is not the same as the script type name "int2" public static class resultArray10_int2 { public Int2[] get() { … } } // for kernels with uint result // note that the Java type "long" is a wider signed type than the unsigned script type "uint" public static class result_uint { public long get() { … } } // for kernels with uint[10] result // note that the Java type "long" is a wider signed type than the unsigned script type "uint" public static class resultArray10_uint { public long[] get() { … } } // for kernels with uint2 result // note that the Java type "Long2" is a wider signed type than the unsigned script type "uint2" public static class result_uint2 { public Long2 get() { … } } // for kernels with uint2[10] result // note that the Java type "Long2" is a wider signed type than the unsigned script type "uint2" public static class resultArray10_uint2 { public Long2[] get() { … } } }
Если javaresulttype является типом объекта (включая тип массива), каждый вызов javaFutureType .get()
в одном и том же экземпляре будет возвращать один и тот же объект.
Если javaresulttype не может представлять все значения типа Resultype , а ядро снижения дает непреднамеренное значение, то javaFutureType .get()
вызывает исключение.
Метод 3 и devecsiinxtype
devecsiinxtype - это тип Java, соответствующий инктипе соответствующего аргумента функции аккумулятора . Если inxtype не является бессобным типом или векторным типом, Devecsiinxtype является непосредственно соответствующим типом Java. Если inxtype является беззнатным скалярным типом, то Devecsiinxtype - это тип Java, непосредственно соответствующий подписанному скалярному типу того же размера. Если inxtype является подписанным векторным типом, то devecsiinxtype является типом Java, непосредственно соответствующим типу векторного компонента. Если inxtype является типом без знаки векторного типа, то devecsiinxtype - тип Java, непосредственно соответствующий подписанному скалярному типу того же размера, что и тип векторного компонента. Например:
- Если inxtype in
int
, то devecsiinxtype inint
. - Если inxtype - это
int2
, то Devecsiinxtype - этоint
. Массив представляет собой сплющенное представление: у него в два раза больше скалярных элементов, чем ассигнование имеет 2-компонентные векторные элементы. Это так же, как методыcopyFrom()
работыAllocation
. - Если inxtype
uint
, то Deficesiinxtype ISint
. Подписанное значение в массиве Java интерпретируется как неподписанное значение одного и того же битпаттерна в распределении. Это так же, как методыcopyFrom()
работыAllocation
. - Если inxtype - это
uint2
, то Deficesiinxtype - этоint
. Это комбинация способа обработкиint2
иuint
: массив представляет собой сплющенное представление, а значения подписанных массива Java интерпретируются как значения renderscript без знака.
Обратите внимание, что для метода 3 типы вводов обрабатываются иначе, чем типы результатов:
- Вход векторного сценария сгладится на стороне Java, тогда как векторный результат сценария не является.
- Неподписанный вход сценария представлен как подписанный вход того же размера на стороне Java, тогда как беззнательный результат сценария представлен в виде расширенного подписанного типа со стороны Java (за исключением случаев
ulong
).
Больше примеров сокращения ядра
#pragma rs reduce(dotProduct) \ accumulator(dotProductAccum) combiner(dotProductSum) // Note: No initializer function -- therefore, // each accumulator data item is implicitly initialized to 0.0f. static void dotProductAccum(float *accum, float in1, float in2) { *accum += in1*in2; } // combiner function static void dotProductSum(float *accum, const float *val) { *accum += *val; }
// Find a zero Element in a 2D allocation; return (-1, -1) if none #pragma rs reduce(fz2) \ initializer(fz2Init) \ accumulator(fz2Accum) combiner(fz2Combine) static void fz2Init(int2 *accum) { accum->x = accum->y = -1; } static void fz2Accum(int2 *accum, int inVal, int x /* special arg */, int y /* special arg */) { if (inVal==0) { accum->x = x; accum->y = y; } } static void fz2Combine(int2 *accum, const int2 *accum2) { if (accum2->x >= 0) *accum = *accum2; }
// Note that this kernel returns an array to Java #pragma rs reduce(histogram) \ accumulator(hsgAccum) combiner(hsgCombine) #define BUCKETS 256 typedef uint32_t Histogram[BUCKETS]; // Note: No initializer function -- // therefore, each bucket is implicitly initialized to 0. static void hsgAccum(Histogram *h, uchar in) { ++(*h)[in]; } static void hsgCombine(Histogram *accum, const Histogram *addend) { for (int i = 0; i < BUCKETS; ++i) (*accum)[i] += (*addend)[i]; } // Determines the mode (most frequently occurring value), and returns // the value and the frequency. // // If multiple values have the same highest frequency, returns the lowest // of those values. // // Shares functions with the histogram reduction kernel. #pragma rs reduce(mode) \ accumulator(hsgAccum) combiner(hsgCombine) \ outconverter(modeOutConvert) static void modeOutConvert(int2 *result, const Histogram *h) { uint32_t mode = 0; for (int i = 1; i < BUCKETS; ++i) if ((*h)[i] > (*h)[mode]) mode = i; result->x = mode; result->y = (*h)[mode]; }
Дополнительные образцы кода
BasicRenderScript , RenderScriptIntrinsic и Hello Compute Samples дополнительно демонстрируют использование API, охватываемых на этой странице.
,Renderscript - это структура для выполнения вычислительно интенсивных задач на высокой производительности на Android. Renderscript в первую очередь ориентирован на использование с помощью данных, параллельных вычислений, хотя последовательные рабочие нагрузки также могут принести пользу. Среда выполнения рендеров параллелизирует работу по процессорам, доступным на устройстве, таких как многоядерные процессоры и графические процессоры. Это позволяет вам сосредоточиться на выражении алгоритмов, а не на планировании работы. Renderscript особенно полезен для приложений, выполняющих обработку изображений, вычислительную фотографию или компьютерное зрение.
Начнем с renderscript, есть две основные концепции, которые вы должны понимать:
- Сам язык является языком, полученным из C99 для написания высокопроизводительного вычислительного кода. Написание ядра Renderscript описывает, как использовать его для написания вычислительных ядер.
- Control API используется для управления временем срока службы ресурсов рендеров и контроля выполнения ядра. Он доступен на трех разных языках: Java, C ++ в Android NDK и сам язык ядра, полученного из C99. Использование renderscript из кода Java и рендеров с одним источником опишите первый и третий параметры соответственно.
Написание ядра рендеров
Ядро рендеров обычно находится в файле .rs
в каталоге <project_root>/src/rs
; Каждый файл .rs
называется сценарием . Каждый сценарий содержит свой собственный набор ядер, функций и переменных. Сценарий может содержать:
- Прагма -декларация (
#pragma version(1)
), которая объявляет версию языка ядра Renderscript, используемой в этом скрипте. В настоящее время 1 является единственным допустимым значением. - Прагма -объявление (
#pragma rs java_package_name(com.example.app)
), которое объявляет имя пакета классов Java, отраженное из этого сценария. Обратите внимание, что ваш файл.rs
должен быть частью вашего пакета приложений, а не в библиотечном проекте. - Ноль или более вызываемых функций . Вызывшая функция-это функция рендеров с одним нагрузкой, которую вы можете вызвать из кода Java с произвольными аргументами. Они часто полезны для начальной настройки или последовательных вычислений в более крупном обработке.
Ноль или более сценариев глобальных . Глобальный скрипт аналогичен глобальной переменной в C. Вы можете получить доступ к глобальным сценариям из кода Java, и они часто используются для перемещения параметров в рендеровные ядра. Глобалы сценария объясняются более подробно здесь .
Ноль или более вычислительных ядер . Вычислительное ядро - это функция или набор функций, которые вы можете направить время выполнения Renderscript, чтобы выполнить параллельно по сбору данных. Есть два вида вычислительных ядер: картирование ядра (также вызываемые ядра для факультета ) и ядра восстановления .
Ядро отображения - это параллельная функция, которая работает на сборе
Allocations
одинаковых измерений. По умолчанию он выполняется один раз для каждой координаты в этих измерениях. Обычно он (но не исключительно) используется для преобразования коллекции входныхAllocations
в выходноеAllocation
по одномуElement
за раз.Вот пример простого картирования ядра :
uchar4 RS_KERNEL invert(uchar4 in, uint32_t x, uint32_t y) { uchar4 out = in; out.r = 255 - in.r; out.g = 255 - in.g; out.b = 255 - in.b; return out; }
В большинстве случаев это идентично стандартной функции C. Свойство
RS_KERNEL
, применяемое к прототипу функции, указывает, что функция представляет собой ядро отображения рендеров вместо вызываемой функции. Аргументin
автоматически заполняется на основеAllocation
ввода, передаваемого в запуск ядра. Аргументыx
иy
обсуждаются ниже . Значение, возвращаемое из ядра, автоматически записывается в соответствующее местоположение приAllocation
вывода. По умолчанию это ядро выполняется по всему своему входномуAllocation
, с одним выполнением функции ядра наElement
вAllocation
.Ядро отображения может иметь одно или несколько входных
Allocations
, единоеAllocation
вывода или оба. Проверка выполнения рендеров проверяет, что все входные и выходные распределения имеют одинаковые измерения, и что типыElement
входных и выходных распределений соответствуют прототипу ядра; Если какая -либо из этих проверок не удается, renderscript бросает исключение.ПРИМЕЧАНИЕ. До Android 6.0 (уровень API 23) ядро отображения может не иметь более одного
Allocation
ввода.Если вам нужно больше входных или выходных
Allocations
, чем есть ядро, эти объекты должны быть связаны с глобальными сценариямиrs_allocation
и доступны из ядра или вызывкой функции черезrsGetElementAt_ type ()
илиrsSetElementAt_ type ()
.ПРИМЕЧАНИЕ.
RS_KERNEL
определяется макросом автоматически с помощью renderscript для вашего удобства:#define RS_KERNEL __attribute__((kernel))
Ядро восстановления - это семейство функций, которое работает на сборе входных
Allocations
одинаковых измерений. По умолчанию его функция аккумулятора выполняется один раз для каждой координаты в этих измерениях. Обычно (но не исключительно) используется для «уменьшения» набора входныхAllocations
до одного значения.Вот пример простого восстановления ядра , который добавляет
Elements
его ввода:#pragma rs reduce(addint) accumulator(addintAccum) static void addintAccum(int *accum, int val) { *accum += val; }
Снижение ядра состоит из одной или нескольких написанных пользователями функций.
#pragma rs reduce
используется для определения ядра, указав его имя (addint
, в этом примере) и имена и роли функций, которые составляют ядро (в этом примере функцияaccumulator
addintAccum
). Все такие функции должны бытьstatic
. Ядро снижения всегда требует функцииaccumulator
; У него также могут быть другие функции, в зависимости от того, что вы хотите, чтобы у ядра.Функция аккумулятора сокращения ядра должна вернуть
void
и должна иметь как минимум два аргумента. Первый аргумент (accum
, в этом примере) является указателем на элемент данных аккумулятора , а второй (val
, в этом примере) автоматически заполняется на основеAllocation
ввода, передаваемого в запуск ядра. Элемент данных аккумулятора создается во время выполнения Renderscript; По умолчанию он инициализируется до нуля. По умолчанию это ядро выполняется по всему своему входномуAllocation
, с одним выполнением функции аккумулятора наElement
вAllocation
. По умолчанию конечное значение элемента данных аккумулятора рассматривается как результат сокращения и возвращается на Java. Проверка времени выполнения рендеров, чтобы гарантировать, что типElement
распределения ввода соответствует прототипу функции аккумулятора; Если это не соответствует, renderscript бросает исключение.Снижение ядра имеет одно или несколько входных
Allocations
, но без выходныхAllocations
.Снижение ядра объясняются более подробно здесь .
Снижение ядра поддерживается в Android 7.0 (API -уровне 24) и позже.
Функция картирования ядра или функция аккумулятора сокращения ядра может получить доступ к координатам текущего выполнения, используя специальные аргументы
x
,y
иz
, которые должны быть типаint
илиuint32_t
. Эти аргументы являются необязательными.Функция картирования ядра или функция аккумулятора с редким ядра может также принимать дополнительный
context
специального аргумента типа rs_kernel_context . Это необходимо для семейства API -интерфейсов времени выполнения, которые используются для запроса определенных свойств текущего исполнения - например, RsgetDimx . (Аргументcontext
доступен в Android 6.0 (уровень API 23) и позже.)- Необязательная функция
init()
. Функцияinit()
представляет собой особый тип вызываемой функции, которая рендеровсписчика работает, когда сценарий первым создан. Это позволяет автоматически проходить некоторые вычисления при создании сценариев. - Ноль или более статические глобальные сценарии и функции . Статический скрипт Global эквивалентен глобальному сценарию, за исключением того, что к нему нельзя получить из кода Java. Статическая функция - это стандартная функция C, которую можно вызвать из любого ядра или вызываемой функции в сценарии, но не подвергается воздействию Java API. Если сценарий Global или функцию не требуется от кода Java, настоятельно рекомендуется объявить
static
.
Установка точности с плавающей запятой
Вы можете управлять необходимым уровнем точности с плавающей точкой в сценарии. Это полезно, если полный стандарт IEEE 754-2008 (используется по умолчанию) не требуется. Следующие прагмы могут установить другой уровень точности плавающей точки:
-
#pragma rs_fp_full
(по умолчанию, если ничего не указано): Для приложений, которые требуют точность плавающей запятой, как указано стандартом IEEE 754-2008. -
#pragma rs_fp_relaxed
: для приложений, которые не требуют строгого соответствия IEEE 754-2008 и могут терпеть меньшую точность. Этот режим включает в себя промывку в нулевую для денормов и круглых Towards-Zero. -
#pragma rs_fp_imprecise
: для приложений, которые не имеют строгих требований точности. Этот режим включает все вrs_fp_relaxed
вместе со следующим:- Операции, приводящие к -0,0, могут вернуть +0.0 вместо этого.
- Операции на INF и NAN не определены.
Большинство приложений могут использовать rs_fp_relaxed
без каких -либо побочных эффектов. Это может быть очень полезным для некоторых архитектур из -за дополнительных оптимизаций, доступных только с расслабленной точностью (например, инструкции CPU SIMD).
Доступ к API -интерфейсам из Java
При разработке приложения Android, которое использует renderscript, вы можете получить доступ к его API из Java одним из двух способов:
-
android.renderscript
- API в этом пакете класса доступны на устройствах под управлением Android 3.0 (API -уровень 11) и выше. -
android.support.v8.renderscript
- API в этом пакете доступны через библиотеку поддержки , которая позволяет использовать их на устройствах под управлением Android 2.3 (API Level 9) и выше.
Вот компромиссы:
- Если вы используете API -интерфейсы библиотеки поддержки, часть вашего приложения будет совместима с устройствами, работающими на Android 2.3 (API -уровне 9) и выше, независимо от того, какие функции рендеров вы используете. Это позволяет вашему приложению работать на большем количестве устройств, чем если вы используете нативные (
android.renderscript
) API. - Некоторые функции рендеров не доступны через API -интерфейсы библиотеки поддержки.
- Если вы используете API -интерфейсы библиотеки поддержки, вы получите (возможно, значительно) большие APK, чем если вы используете API -интерфейсы Native (
android.renderscript
).
Использование API библиотеки поддержки Renderscript
Чтобы использовать API -интерфейсы библиотеки поддержки, вы должны настроить среду разработки, чтобы иметь возможность получить к ним доступ. Для использования этих API требуются следующие инструменты Android SDK:
- Android SDK Tools Revision 22.2 или выше
- Android SDK Build-Tools Revision 18.1.0 или выше
Обратите внимание, что начиная с Android SDK Build-Tools 24.0.0, Android 2.2 (API-уровень 8) больше не поддерживается.
Вы можете проверить и обновить установленную версию этих инструментов в Android SDK Manager .
Чтобы использовать API -интерфейсы библиотеки поддержки:
- Убедитесь, что у вас установлена необходимая версия Android SDK.
- Обновите настройки для процесса сборки Android, чтобы включить настройки rederscript:
- Откройте файл
build.gradle
в папке приложения модуля приложения. - Добавьте следующие настройки rederscript в файл:
Круто
android { compileSdkVersion 33 defaultConfig { minSdkVersion 9 targetSdkVersion 19 renderscriptTargetApi 18 renderscriptSupportModeEnabled true } }
Котлин
android { compileSdkVersion(33) defaultConfig { minSdkVersion(9) targetSdkVersion(19) renderscriptTargetApi = 18 renderscriptSupportModeEnabled = true } }
Настройки, перечисленные выше, контролируют конкретное поведение в процессе сборки Android:
-
renderscriptTargetApi
- определяет сгенерированную версию Bytecode. Мы рекомендуем вам установить это значение для самого низкого уровня API, способного предоставить все функциональные возможности, которые вы используете, и установитьrenderscriptSupportModeEnabled
totrue
. Допустимые значения для этого настройки представляют собой любое целочисленное значение от 11 до самого последнего выпущенного уровня API. Если ваша минимальная версия SDK, указанная в манифесте вашего приложения, устанавливается на другое значение, это значение игнорируется, и целевое значение в файле сборки используется для установки минимальной версии SDK. -
renderscriptSupportModeEnabled
- указывает, что сгенерированный байт -код должен вернуться к совместимой версии, если устройство, на котором он работает, не поддерживает целевую версию.
-
- Откройте файл
- В ваших классах приложений, которые используют renderscript, добавьте импорт для классов библиотеки поддержки:
Котлин
import android.support.v8.renderscript.*
Ява
import android.support.v8.renderscript.*;
Использование renderscript из кода Java или Kotlin
Использование renderscript из кода Java или Kotlin зависит от классов API, расположенных в android.renderscript
или android.support.v8.renderscript
. Большинство приложений следуют той же базовой схеме использования:
- Инициализировать контекст рендеров. Контекст
RenderScript
, созданный с помощьюcreate(Context)
, гарантирует, что RenderScript может использоваться и предоставляет объект для управления временем срока службы всех последующих объектов renderscript. Вы должны считать создание контекста потенциально длительной операцией, поскольку оно может создавать ресурсы на разных деталях оборудования; Это не должно быть в критическом пути приложения, если это вообще возможно. Как правило, приложение будет иметь только один контекст рендеров за раз. - Создайте хотя бы одно
Allocation
, которое будет передано в сценарий.Allocation
- это объект рендеров, который обеспечивает хранилище для фиксированного количества данных. Ядра в сценариях принимают объектыAllocation
в качестве их ввода и вывода, а объектыAllocation
могут быть доступны в ядрах с использованиемrsGetElementAt_ type ()
иrsSetElementAt_ type ()
при гранике с глобалами сценариев. ОбъектыAllocation
позволяют массивам быть переданы из кода Java, чтобы рендеровский код и наоборот. ОбъектыAllocation
обычно создаются с использованиемcreateTyped()
илиcreateFromBitmap()
. - Создайте все сценарии, необходимые. При использовании renderscript есть два типа сценариев:
- SCRIPTC : Это пользовательские сценарии, как описано в написании ядра рендеров выше. Каждый сценарий имеет класс Java, отраженный компилятором renderscript, чтобы легко добраться до сценария из кода Java; У этого класса есть имя
ScriptC_ filename
. Например, если ядро отображения выше было расположено вinvert.rs
, а контекст рендеров уже находился вmRenderScript
, код Java или Kotlin для создания сценария будет:Котлин
val invert = ScriptC_invert(renderScript)
Ява
ScriptC_invert invert = new ScriptC_invert(renderScript);
- ScriptIntrinsic : это встроенные ядра рендеров для общих операций, такие как размытие гауссов, свертка и смешивание изображений. Для получения дополнительной информации см. Подклассы
ScriptIntrinsic
.
- SCRIPTC : Это пользовательские сценарии, как описано в написании ядра рендеров выше. Каждый сценарий имеет класс Java, отраженный компилятором renderscript, чтобы легко добраться до сценария из кода Java; У этого класса есть имя
- Заполняют ассигнования с данными. За исключением ассигнований, созданных с помощью
createFromBitmap()
, ассигнование заполняется пустыми данными, когда оно впервые создано. Чтобы заполнить распределение, используйте один из методов «копии» приAllocation
. Методы «копии» синхронны . - Установите любые необходимые глобальные сценарии . Вы можете установить глобалы, используя методы в одном и том же классе
ScriptC_ filename
с именемset_ globalname
. Например, чтобы установить переменнуюint
сthreshold
, используйте метод Javaset_threshold(int)
; и для того, чтобы установить переменнуюrs_allocation
с именемlookup
, используйте метод Javaset_lookup(Allocation)
.set
методы асинхронны . - Запустите соответствующие ядра и вызывчивые функции.
Методы запуска данного ядра отражаются в одном и том же классе
ScriptC_ filename
с помощью методов с именемforEach_ mappingKernelName ()
илиreduce_ reductionKernelName ()
. Эти запуски асинхронны . В зависимости от аргументов с ядром, метод принимаетforEach
илиreduce
ассигнований, которые должны иметь одинаковые измерения. По умолчанию ядро выполняется над каждой координатой в этих измерениях; Чтобы выполнить ядро по подмножеству этих координат, передайте соответствующийScript.LaunchOptions
Запуск вызывших функций с использованием методов
invoke_ functionName
отраженных в одном и том же классеScriptC_ filename
. Эти запуски асинхронны . - Получить данные из объектов
Allocation
и объектов JavafutureType . Чтобы получить доступ к данным изAllocation
из кода Java, вы должны скопировать эти данные обратно в Java, используя один из методов «копирования» приAllocation
. Чтобы получить результат восстановления ядра, вы должны использовать методjavaFutureType .get()
. Методы «копия» иget()
являются синхронными . - Снести контекст рендеров. Вы можете уничтожить контекст рендеров с помощью
destroy()
или, позволяя собирать объект контекста рендеров. Это приводит к дальнейшему использованию любого объекта, принадлежащего этому контексту, чтобы добавить исключение.
Асинхронная модель исполнения
Отраженные forEach
, invoke
, reduce
и set
методы являются асинхронными - каждый может вернуться на Java, прежде чем завершить запрошенное действие. Тем не менее, отдельные действия сериализованы в том порядке, в котором они запущены.
Класс Allocation
предоставляет методы «копировать» для копирования данных на распределения и из -за. Метод «копии» является синхронным и сериализован относительно любого из асинхронных действий выше, которые касаются одного и того же распределения.
Отраженные классы JavafutureType обеспечивают метод get()
для получения результата сокращения. get()
синхронно и сериализован по отношению к сокращению (что является асинхронным).
Рендеров с одним источником
Android 7.0 (API-уровень 24) представляет новую функцию программирования, называемую рендеров , в котором ядра запускаются из сценария, где они определены, а не от Java. Этот подход в настоящее время ограничен картированием ядра, которые просто называются «ядрами» в этом разделе для краткости. Эта новая функция также поддерживает создание ассигнования типа rs_allocation
изнутри сценария. Теперь можно реализовать целый алгоритм исключительно в сценарии, даже если требуется несколько запуска ядра. Преимущество двойной: более читаемый код, потому что он сохраняет реализацию алгоритма на одном языке; и потенциально более быстрый код, из -за меньшего количества переходов между Java и рендеров в нескольких запусках ядра.
В рендерсписах с одним источником вы пишете ядра, как описано в написании ядра рендеров . Затем вы пишете вызывную функцию, которая вызывает rsForEach()
, чтобы запустить их. Этот API принимает функцию ядра в качестве первого параметра, за которым следует входные и выходные распределения. Аналогичный API rsForEachWithOptions()
принимает дополнительный аргумент типа rs_script_call_t
, который определяет подмножество элементов из входных и выходных распределений для обработки функции ядра.
Чтобы начать вычисление рендеров, вы называете вызывную функцию из Java. Следуйте шагам в использовании renderscript из кода Java . При запуске шага соответствующие ядра вызовите функцию вызова, используя invoke_ function_name ()
, которая запустит все вычисления, включая запуск ядра.
Ассигнования часто необходимы для сохранения и передачи промежуточных результатов от одного запуска ядра к другому. Вы можете создать их с помощью rscreateallocation () . Одной из простых в использовании формы этого API является rsCreateAllocation_<T><W>(…)
, где t тип данных для элемента, а W -ширина вектора для элемента. API принимает размеры в размерах x, y и z в качестве аргументов. Для распределений 1D или 2D размер для измерения Y или Z может быть опущен. Например, rsCreateAllocation_uchar4(16384)
создает 1D -распределение элементов 16384, каждый из которых имеет тип uchar4
.
Ассистенты управляются системой автоматически. Вам не нужно явно освобождать или освобождать их. Тем не менее, вы можете вызвать rsClearObject(rs_allocation* alloc)
, чтобы указать, что вам больше не нужна ручка alloc
для базового распределения, чтобы система могла высвободить ресурсы как можно раньше.
// File: singlesource.rs #pragma version(1) #pragma rs java_package_name(com.android.rssample) static const float4 weight = {0.299f, 0.587f, 0.114f, 0.0f}; uchar4 RS_KERNEL invert(uchar4 in, uint32_t x, uint32_t y) { uchar4 out = in; out.r = 255 - in.r; out.g = 255 - in.g; out.b = 255 - in.b; return out; } uchar4 RS_KERNEL greyscale(uchar4 in) { const float4 inF = rsUnpackColor8888(in); const float4 outF = (float4){ dot(inF, weight) }; return rsPackColorTo8888(outF); } void process(rs_allocation inputImage, rs_allocation outputImage) { const uint32_t imageWidth = rsAllocationGetDimX(inputImage); const uint32_t imageHeight = rsAllocationGetDimY(inputImage); rs_allocation tmp = rsCreateAllocation_uchar4(imageWidth, imageHeight); rsForEach(invert, inputImage, tmp); rsForEach(greyscale, tmp, outputImage); }
You can call the process()
function from Java or Kotlin as follows:
Котлин
val RS: RenderScript = RenderScript.create(context) val script = ScriptC_singlesource(RS) val inputAllocation: Allocation = Allocation.createFromBitmapResource( RS, resources, R.drawable.image ) val outputAllocation: Allocation = Allocation.createTyped( RS, inputAllocation.type, Allocation.USAGE_SCRIPT or Allocation.USAGE_IO_OUTPUT ) script.invoke_process(inputAllocation, outputAllocation)
Ява
// File SingleSource.java RenderScript RS = RenderScript.create(context); ScriptC_singlesource script = new ScriptC_singlesource(RS); Allocation inputAllocation = Allocation.createFromBitmapResource( RS, getResources(), R.drawable.image); Allocation outputAllocation = Allocation.createTyped( RS, inputAllocation.getType(), Allocation.USAGE_SCRIPT | Allocation.USAGE_IO_OUTPUT); script.invoke_process(inputAllocation, outputAllocation);
This example shows how an algorithm that involves two kernel launches can be implemented completely in the RenderScript language itself. Without Single-Source RenderScript, you would have to launch both kernels from the Java code, separating kernel launches from kernel definitions and making it harder to understand the whole algorithm. Not only is the Single-Source RenderScript code easier to read, it also eliminates the transitioning between Java and the script across kernel launches. Some iterative algorithms may launch kernels hundreds of times, making the overhead of such transitioning considerable.
Script Globals
A script global is an ordinary non- static
global variable in a script ( .rs
) file. For a script global named var defined in the file filename .rs
, there will be a method get_ var
reflected in the class ScriptC_ filename
. Unless the global is const
, there will also be a method set_ var
.
A given script global has two separate values -- a Java value and a script value. These values behave as follows:
- If var has a static initializer in the script, it specifies the initial value of var in both Java and the script. Otherwise, that initial value is zero.
- Accesses to var within the script read and write its script value.
- The
get_ var
method reads the Java value. - The
set_ var
method (if it exists) writes the Java value immediately, and writes the script value asynchronously .
NOTE: This means that except for any static initializer in the script, values written to a global from within a script are not visible to Java.
Reduction Kernels in Depth
Reduction is the process of combining a collection of data into a single value. This is a useful primitive in parallel programming, with applications such as the following:
- computing the sum or product over all the data
- computing logical operations (
and
,or
,xor
) over all the data - finding the minimum or maximum value within the data
- searching for a specific value or for the coordinate of a specific value within the data
In Android 7.0 (API level 24) and later, RenderScript supports reduction kernels to allow efficient user-written reduction algorithms. You may launch reduction kernels on inputs with 1, 2, or 3 dimensions.
An example above shows a simple addint reduction kernel. Here is a more complicated findMinAndMax reduction kernel that finds the locations of the minimum and maximum long
values in a 1-dimensional Allocation
:
#define LONG_MAX (long)((1UL << 63) - 1) #define LONG_MIN (long)(1UL << 63) #pragma rs reduce(findMinAndMax) \ initializer(fMMInit) accumulator(fMMAccumulator) \ combiner(fMMCombiner) outconverter(fMMOutConverter) // Either a value and the location where it was found, or INITVAL. typedef struct { long val; int idx; // -1 indicates INITVAL } IndexedVal; typedef struct { IndexedVal min, max; } MinAndMax; // In discussion below, this initial value { { LONG_MAX, -1 }, { LONG_MIN, -1 } } // is called INITVAL. static void fMMInit(MinAndMax *accum) { accum->min.val = LONG_MAX; accum->min.idx = -1; accum->max.val = LONG_MIN; accum->max.idx = -1; } //---------------------------------------------------------------------- // In describing the behavior of the accumulator and combiner functions, // it is helpful to describe hypothetical functions // IndexedVal min(IndexedVal a, IndexedVal b) // IndexedVal max(IndexedVal a, IndexedVal b) // MinAndMax minmax(MinAndMax a, MinAndMax b) // MinAndMax minmax(MinAndMax accum, IndexedVal val) // // The effect of // IndexedVal min(IndexedVal a, IndexedVal b) // is to return the IndexedVal from among the two arguments // whose val is lesser, except that when an IndexedVal // has a negative index, that IndexedVal is never less than // any other IndexedVal; therefore, if exactly one of the // two arguments has a negative index, the min is the other // argument. Like ordinary arithmetic min and max, this function // is commutative and associative; that is, // // min(A, B) == min(B, A) // commutative // min(A, min(B, C)) == min((A, B), C) // associative // // The effect of // IndexedVal max(IndexedVal a, IndexedVal b) // is analogous (greater . . . never greater than). // // Then there is // // MinAndMax minmax(MinAndMax a, MinAndMax b) { // return MinAndMax(min(a.min, b.min), max(a.max, b.max)); // } // // Like ordinary arithmetic min and max, the above function // is commutative and associative; that is: // // minmax(A, B) == minmax(B, A) // commutative // minmax(A, minmax(B, C)) == minmax((A, B), C) // associative // // Finally define // // MinAndMax minmax(MinAndMax accum, IndexedVal val) { // return minmax(accum, MinAndMax(val, val)); // } //---------------------------------------------------------------------- // This function can be explained as doing: // *accum = minmax(*accum, IndexedVal(in, x)) // // This function simply computes minimum and maximum values as if // INITVAL.min were greater than any other minimum value and // INITVAL.max were less than any other maximum value. Note that if // *accum is INITVAL, then this function sets // *accum = IndexedVal(in, x) // // After this function is called, both accum->min.idx and accum->max.idx // will have nonnegative values: // - x is always nonnegative, so if this function ever sets one of the // idx fields, it will set it to a nonnegative value // - if one of the idx fields is negative, then the corresponding // val field must be LONG_MAX or LONG_MIN, so the function will always // set both the val and idx fields static void fMMAccumulator(MinAndMax *accum, long in, int x) { IndexedVal me; me.val = in; me.idx = x; if (me.val <= accum->min.val) accum->min = me; if (me.val >= accum->max.val) accum->max = me; } // This function can be explained as doing: // *accum = minmax(*accum, *val) // // This function simply computes minimum and maximum values as if // INITVAL.min were greater than any other minimum value and // INITVAL.max were less than any other maximum value. Note that if // one of the two accumulator data items is INITVAL, then this // function sets *accum to the other one. static void fMMCombiner(MinAndMax *accum, const MinAndMax *val) { if ((accum->min.idx < 0) || (val->min.val < accum->min.val)) accum->min = val->min; if ((accum->max.idx < 0) || (val->max.val > accum->max.val)) accum->max = val->max; } static void fMMOutConverter(int2 *result, const MinAndMax *val) { result->x = val->min.idx; result->y = val->max.idx; }
NOTE: There are more example reduction kernels here .
In order to run a reduction kernel, the RenderScript runtime creates one or more variables called accumulator data items to hold the state of the reduction process. The RenderScript runtime picks the number of accumulator data items in such a way as to maximize performance. The type of the accumulator data items ( accumType ) is determined by the kernel's accumulator function -- the first argument to that function is a pointer to an accumulator data item. By default, every accumulator data item is initialized to zero (as if by memset
); however, you may write an initializer function to do something different.
Example: In the addint kernel, the accumulator data items (of type int
) are used to add up input values. There is no initializer function, so each accumulator data item is initialized to zero.
Example: In the findMinAndMax kernel, the accumulator data items (of type MinAndMax
) are used to keep track of the minimum and maximum values found so far. There is an initializer function to set these to LONG_MAX
and LONG_MIN
, respectively; and to set the locations of these values to -1, indicating that the values are not actually present in the (empty) portion of the input that has been processed.
RenderScript calls your accumulator function once for every coordinate in the input(s). Typically, your function should update the accumulator data item in some way according to the input.
Example: In the addint kernel, the accumulator function adds the value of an input Element to the accumulator data item.
Example: In the findMinAndMax kernel, the accumulator function checks to see whether the value of an input Element is less than or equal to the minimum value recorded in the accumulator data item and/or greater than or equal to the maximum value recorded in the accumulator data item, and updates the accumulator data item accordingly.
After the accumulator function has been called once for every coordinate in the input(s), RenderScript must combine the accumulator data items together into a single accumulator data item. You may write a combiner function to do this. If the accumulator function has a single input and no special arguments , then you do not need to write a combiner function; RenderScript will use the accumulator function to combine the accumulator data items. (You may still write a combiner function if this default behavior is not what you want.)
Example: In the addint kernel, there is no combiner function, so the accumulator function will be used. This is the correct behavior, because if we split a collection of values into two pieces, and we add up the values in those two pieces separately, adding up those two sums is the same as adding up the entire collection.
Example: In the findMinAndMax kernel, the combiner function checks to see whether the minimum value recorded in the "source" accumulator data item *val
is less than the minimum value recorded in the "destination" accumulator data item *accum
, and updates *accum
accordingly. It does similar work for the maximum value. This updates *accum
to the state it would have had if all of the input values had been accumulated into *accum
rather than some into *accum
and some into *val
.
After all of the accumulator data items have been combined, RenderScript determines the result of the reduction to return to Java. You may write an outconverter function to do this. You do not need to write an outconverter function if you want the final value of the combined accumulator data items to be the result of the reduction.
Example: In the addint kernel, there is no outconverter function. The final value of the combined data items is the sum of all Elements of the input, which is the value we want to return.
Example: In the findMinAndMax kernel, the outconverter function initializes an int2
result value to hold the locations of the minimum and maximum values resulting from the combination of all of the accumulator data items.
Writing a reduction kernel
#pragma rs reduce
defines a reduction kernel by specifying its name and the names and roles of the functions that make up the kernel. All such functions must be static
. A reduction kernel always requires an accumulator
function; you can omit some or all of the other functions, depending on what you want the kernel to do.
#pragma rs reduce(kernelName) \ initializer(initializerName) \ accumulator(accumulatorName) \ combiner(combinerName) \ outconverter(outconverterName)
The meaning of the items in the #pragma
is as follows:
-
reduce( kernelName )
(mandatory): Specifies that a reduction kernel is being defined. A reflected Java methodreduce_ kernelName
will launch the kernel. initializer( initializerName )
(optional): Specifies the name of the initializer function for this reduction kernel. When you launch the kernel, RenderScript calls this function once for each accumulator data item . The function must be defined like this:static void initializerName(accumType *accum) { … }
accum
is a pointer to an accumulator data item for this function to initialize.If you do not provide an initializer function, RenderScript initializes every accumulator data item to zero (as if by
memset
), behaving as if there were an initializer function that looks like this:static void initializerName(accumType *accum) { memset(accum, 0, sizeof(*accum)); }
accumulator( accumulatorName )
(mandatory): Specifies the name of the accumulator function for this reduction kernel. When you launch the kernel, RenderScript calls this function once for every coordinate in the input(s), to update an accumulator data item in some way according to the input(s). The function must be defined like this:static void accumulatorName(accumType *accum, in1Type in1, …, inNType inN [, specialArguments]) { … }
accum
is a pointer to an accumulator data item for this function to modify.in1
throughin N
are one or more arguments that are automatically filled in based on the inputs passed to the kernel launch, one argument per input. The accumulator function may optionally take any of the special arguments .An example kernel with multiple inputs is
dotProduct
.-
combiner( combinerName )
(optional): Specifies the name of the combiner function for this reduction kernel. After RenderScript calls the accumulator function once for every coordinate in the input(s), it calls this function as many times as necessary to combine all accumulator data items into a single accumulator data item. The function must be defined like this:
static void combinerName(accumType *accum, const accumType *other) { … }
accum
is a pointer to a "destination" accumulator data item for this function to modify.other
is a pointer to a "source" accumulator data item for this function to "combine" into*accum
.NOTE: It is possible that
*accum
,*other
, or both have been initialized but have never been passed to the accumulator function; that is, one or both have never been updated according to any input data. For example, in the findMinAndMax kernel, the combiner functionfMMCombiner
explicitly checks foridx < 0
because that indicates such an accumulator data item, whose value is INITVAL .If you do not provide a combiner function, RenderScript uses the accumulator function in its place, behaving as if there were a combiner function that looks like this:
static void combinerName(accumType *accum, const accumType *other) { accumulatorName(accum, *other); }
A combiner function is mandatory if the kernel has more than one input, if the input data type is not the same as the accumulator data type, or if the accumulator function takes one or more special arguments .
outconverter( outconverterName )
(optional): Specifies the name of the outconverter function for this reduction kernel. After RenderScript combines all of the accumulator data items, it calls this function to determine the result of the reduction to return to Java. The function must be defined like this:static void outconverterName(resultType *result, const accumType *accum) { … }
result
is a pointer to a result data item (allocated but not initialized by the RenderScript runtime) for this function to initialize with the result of the reduction. resultType is the type of that data item, which need not be the same as accumType .accum
is a pointer to the final accumulator data item computed by the combiner function .If you do not provide an outconverter function, RenderScript copies the final accumulator data item to the result data item, behaving as if there were an outconverter function that looks like this:
static void outconverterName(accumType *result, const accumType *accum) { *result = *accum; }
If you want a different result type than the accumulator data type, then the outconverter function is mandatory.
Note that a kernel has input types, an accumulator data item type, and a result type, none of which need to be the same. For example, in the findMinAndMax kernel, the input type long
, accumulator data item type MinAndMax
, and result type int2
are all different.
What can't you assume?
You must not rely on the number of accumulator data items created by RenderScript for a given kernel launch. There is no guarantee that two launches of the same kernel with the same input(s) will create the same number of accumulator data items.
- There is no guarantee that all accumulator data items will be initialized before the accumulator function is called, although it will only be called on an initialized accumulator data item.
- There is no guarantee on the order in which input Elements are passed to the accumulator function.
- There is no guarantee that the accumulator function has been called for all input Elements before the combiner function is called.
One consequence of this is that the findMinAndMax kernel is not deterministic: If the input contains more than one occurrence of the same minimum or maximum value, you have no way of knowing which occurrence the kernel will find.
What must you guarantee?
Because the RenderScript system can choose to execute a kernel in many different ways , you must follow certain rules to ensure that your kernel behaves the way you want. If you do not follow these rules, you may get incorrect results, nondeterministic behavior, or runtime errors.
The rules below often say that two accumulator data items must have " the same value" . What does this mean? That depends on what you want the kernel to do. For a mathematical reduction such as addint , it usually makes sense for "the same" to mean mathematical equality. For a "pick any" search such as findMinAndMax ("find the location of minimum and maximum input values") where there might be more than one occurrence of identical input values, all locations of a given input value must be considered "the same". You could write a similar kernel to "find the location of leftmost minimum and maximum input values" where (say) a minimum value at location 100 is preferred over an identical minimum value at location 200; for this kernel, "the same" would mean identical location , not merely identical value , and the accumulator and combiner functions would have to be different than those for findMinAndMax .
The initializer function must create an identity value . That is, ifI
and A
are accumulator data items initialized by the initializer function, and I
has never been passed to the accumulator function (but A
may have been), then-
combinerName (& A , & I )
must leaveA
the same -
combinerName (& I , & A )
must leaveI
the same asA
Example: In the addint kernel, an accumulator data item is initialized to zero. The combiner function for this kernel performs addition; zero is the identity value for addition.
Example: In the findMinAndMax kernel, an accumulator data item is initialized to INITVAL
.
-
fMMCombiner(& A , & I )
leavesA
the same, becauseI
isINITVAL
. -
fMMCombiner(& I , & A )
setsI
toA
, becauseI
isINITVAL
.
Therefore, INITVAL
is indeed an identity value.
The combiner function must be commutative . That is, if A
and B
are accumulator data items initialized by the initializer function, and that may have been passed to the accumulator function zero or more times, then combinerName (& A , & B )
must set A
to the same value that combinerName (& B , & A )
sets B
.
Example: In the addint kernel, the combiner function adds the two accumulator data item values; addition is commutative.
Example: In the findMinAndMax kernel, fMMCombiner(& A , & B )
is the same as A = minmax( A , B )
, and minmax
is commutative, so fMMCombiner
is also.
The combiner function must be associative . That is, if A
, B
, and C
are accumulator data items initialized by the initializer function, and that may have been passed to the accumulator function zero or more times, then the following two code sequences must set A
to the same value :
combinerName(&A, &B); combinerName(&A, &C);
combinerName(&B, &C); combinerName(&A, &B);
Example: In the addint kernel, the combiner function adds the two accumulator data item values:
A = A + B A = A + C // Same as // A = (A + B) + C
B = B + C A = A + B // Same as // A = A + (B + C) // B = B + C
Addition is associative, and so the combiner function is also.
Example: In the findMinAndMax kernel,
fMMCombiner(&A, &B)
A = minmax(A, B)
A = minmax(A, B) A = minmax(A, C) // Same as // A = minmax(minmax(A, B), C)
B = minmax(B, C) A = minmax(A, B) // Same as // A = minmax(A, minmax(B, C)) // B = minmax(B, C)
minmax
is associative, and so fMMCombiner
is also.
The accumulator function and combiner function together must obey the basic folding rule . That is, if A
and B
are accumulator data items, A
has been initialized by the initializer function and may have been passed to the accumulator function zero or more times, B
has not been initialized, and args is the list of input arguments and special arguments for a particular call to the accumulator function, then the following two code sequences must set A
to the same value :
accumulatorName(&A, args); // statement 1
initializerName(&B); // statement 2 accumulatorName(&B, args); // statement 3 combinerName(&A, &B); // statement 4
Example: In the addint kernel, for an input value V :
- Statement 1 is the same as
A += V
- Statement 2 is the same as
B = 0
- Statement 3 is the same as
B += V
, which is the same asB = V
- Statement 4 is the same as
A += B
, which is the same asA += V
Statements 1 and 4 set A
to the same value, and so this kernel obeys the basic folding rule.
Example: In the findMinAndMax kernel, for an input value V at coordinate X :
- Statement 1 is the same as
A = minmax(A, IndexedVal( V , X ))
- Statement 2 is the same as
B = INITVAL
- Statement 3 is the same as
which, because B is the initial value, is the same asB = minmax(B, IndexedVal(V, X))
B = IndexedVal(V, X)
- Statement 4 is the same as
which is the same asA = minmax(A, B)
A = minmax(A, IndexedVal(V, X))
Statements 1 and 4 set A
to the same value, and so this kernel obeys the basic folding rule.
Calling a reduction kernel from Java code
For a reduction kernel named kernelName defined in the file filename .rs
, there are three methods reflected in the class ScriptC_ filename
:
Котлин
// Function 1 fun reduce_kernelName(ain1: Allocation, …, ainN: Allocation): javaFutureType // Function 2 fun reduce_kernelName(ain1: Allocation, …, ainN: Allocation, sc: Script.LaunchOptions): javaFutureType // Function 3 fun reduce_kernelName(in1: Array<devecSiIn1Type>, …, inN: Array<devecSiInNType>): javaFutureType
Ява
// Method 1 public javaFutureType reduce_kernelName(Allocation ain1, …, Allocation ainN); // Method 2 public javaFutureType reduce_kernelName(Allocation ain1, …, Allocation ainN, Script.LaunchOptions sc); // Method 3 public javaFutureType reduce_kernelName(devecSiIn1Type[] in1, …, devecSiInNType[] inN);
Here are some examples of calling the addint kernel:
Котлин
val script = ScriptC_example(renderScript) // 1D array // and obtain answer immediately val input1 = intArrayOf(…) val sum1: Int = script.reduce_addint(input1).get() // Method 3 // 2D allocation // and do some additional work before obtaining answer val typeBuilder = Type.Builder(RS, Element.I32(RS)).apply { setX(…) setY(…) } val input2: Allocation = Allocation.createTyped(RS, typeBuilder.create()).also { populateSomehow(it) // fill in input Allocation with data } val result2: ScriptC_example.result_int = script.reduce_addint(input2) // Method 1 doSomeAdditionalWork() // might run at same time as reduction val sum2: Int = result2.get()
Ява
ScriptC_example script = new ScriptC_example(renderScript); // 1D array // and obtain answer immediately int input1[] = …; int sum1 = script.reduce_addint(input1).get(); // Method 3 // 2D allocation // and do some additional work before obtaining answer Type.Builder typeBuilder = new Type.Builder(RS, Element.I32(RS)); typeBuilder.setX(…); typeBuilder.setY(…); Allocation input2 = createTyped(RS, typeBuilder.create()); populateSomehow(input2); // fill in input Allocation with data ScriptC_example.result_int result2 = script.reduce_addint(input2); // Method 1 doSomeAdditionalWork(); // might run at same time as reduction int sum2 = result2.get();
Method 1 has one input Allocation
argument for every input argument in the kernel's accumulator function . The RenderScript runtime checks to ensure that all of the input Allocations have the same dimensions and that the Element
type of each of the input Allocations matches that of the corresponding input argument of the accumulator function's prototype. If any of these checks fail, RenderScript throws an exception. The kernel executes over every coordinate in those dimensions.
Method 2 is the same as Method 1 except that Method 2 takes an additional argument sc
that can be used to limit the kernel execution to a subset of the coordinates.
Method 3 is the same as Method 1 except that instead of taking Allocation inputs it takes Java array inputs. This is a convenience that saves you from having to write code to explicitly create an Allocation and copy data to it from a Java array. However, using Method 3 instead of Method 1 does not increase the performance of the code . For each input array, Method 3 creates a temporary 1-dimensional Allocation with the appropriate Element
type and setAutoPadding(boolean)
enabled, and copies the array to the Allocation as if by the appropriate copyFrom()
method of Allocation
. It then calls Method 1, passing those temporary Allocations.
NOTE: If your application will make multiple kernel calls with the same array, or with different arrays of the same dimensions and Element type, you may improve performance by explicitly creating, populating, and reusing Allocations yourself, instead of by using Method 3.
javaFutureType , the return type of the reflected reduction methods, is a reflected static nested class within the ScriptC_ filename
class. It represents the future result of a reduction kernel run. To obtain the actual result of the run, call the get()
method of that class, which returns a value of type javaResultType . get()
is synchronous .
Котлин
class ScriptC_filename(rs: RenderScript) : ScriptC(…) { object javaFutureType { fun get(): javaResultType { … } } }
Ява
public class ScriptC_filename extends ScriptC { public static class javaFutureType { public javaResultType get() { … } } }
- If resultType is
int
,int2
, orint[15]
, then javaResultType isint
,Int2
, orint[]
. All values of resultType can be represented by javaResultType . - If resultType is
uint
,uint2
, oruint[15]
, then javaResultType islong
,Long2
, orlong[]
. All values of resultType can be represented by javaResultType . - If resultType is
ulong
,ulong2
, orulong[15]
, then javaResultType islong
,Long2
, orlong[]
. There are certain values of resultType that cannot be represented by javaResultType .
javaFutureType is the future result type corresponding to the resultType of the outconverter function .
- If resultType is not an array type, then javaFutureType is
result_ resultType
. - If resultType is an array of length Count with members of type memberType , then javaFutureType is
resultArray Count _ memberType
.
Например:
Котлин
class ScriptC_filename(rs: RenderScript) : ScriptC(…) { // for kernels with int result object result_int { fun get(): Int = … } // for kernels with int[10] result object resultArray10_int { fun get(): IntArray = … } // for kernels with int2 result // note that the Kotlin type name "Int2" is not the same as the script type name "int2" object result_int2 { fun get(): Int2 = … } // for kernels with int2[10] result // note that the Kotlin type name "Int2" is not the same as the script type name "int2" object resultArray10_int2 { fun get(): Array<Int2> = … } // for kernels with uint result // note that the Kotlin type "long" is a wider signed type than the unsigned script type "uint" object result_uint { fun get(): Long = … } // for kernels with uint[10] result // note that the Kotlin type "long" is a wider signed type than the unsigned script type "uint" object resultArray10_uint { fun get(): LongArray = … } // for kernels with uint2 result // note that the Kotlin type "Long2" is a wider signed type than the unsigned script type "uint2" object result_uint2 { fun get(): Long2 = … } // for kernels with uint2[10] result // note that the Kotlin type "Long2" is a wider signed type than the unsigned script type "uint2" object resultArray10_uint2 { fun get(): Array<Long2> = … } }
Ява
public class ScriptC_filename extends ScriptC { // for kernels with int result public static class result_int { public int get() { … } } // for kernels with int[10] result public static class resultArray10_int { public int[] get() { … } } // for kernels with int2 result // note that the Java type name "Int2" is not the same as the script type name "int2" public static class result_int2 { public Int2 get() { … } } // for kernels with int2[10] result // note that the Java type name "Int2" is not the same as the script type name "int2" public static class resultArray10_int2 { public Int2[] get() { … } } // for kernels with uint result // note that the Java type "long" is a wider signed type than the unsigned script type "uint" public static class result_uint { public long get() { … } } // for kernels with uint[10] result // note that the Java type "long" is a wider signed type than the unsigned script type "uint" public static class resultArray10_uint { public long[] get() { … } } // for kernels with uint2 result // note that the Java type "Long2" is a wider signed type than the unsigned script type "uint2" public static class result_uint2 { public Long2 get() { … } } // for kernels with uint2[10] result // note that the Java type "Long2" is a wider signed type than the unsigned script type "uint2" public static class resultArray10_uint2 { public Long2[] get() { … } } }
If javaResultType is an object type (including an array type), each call to javaFutureType .get()
on the same instance will return the same object.
If javaResultType cannot represent all values of type resultType , and a reduction kernel produces an unrepresentible value, then javaFutureType .get()
throws an exception.
Method 3 and devecSiInXType
- If inXType is
int
, then devecSiInXType isint
. - If inXType is
int2
, then devecSiInXType isint
. The array is a flattened representation: It has twice as many scalar Elements as the Allocation has 2-component vector Elements. This is the same way that thecopyFrom()
methods ofAllocation
work. - If inXType is
uint
, then deviceSiInXType isint
. A signed value in the Java array is interpreted as an unsigned value of the same bitpattern in the Allocation. This is the same way that thecopyFrom()
methods ofAllocation
work. - If inXType is
uint2
, then deviceSiInXType isint
. This is a combination of the wayint2
anduint
are handled: The array is a flattened representation, and Java array signed values are interpreted as RenderScript unsigned Element values.
Note that for Method 3 , input types are handled differently than result types:
- A script's vector input is flattened on the Java side, whereas a script's vector result is not.
- A script's unsigned input is represented as a signed input of the same size on the Java side, whereas a script's unsigned result is represented as a widened signed type on the Java side (except in the case of
ulong
).
More example reduction kernels
#pragma rs reduce(dotProduct) \ accumulator(dotProductAccum) combiner(dotProductSum) // Note: No initializer function -- therefore, // each accumulator data item is implicitly initialized to 0.0f. static void dotProductAccum(float *accum, float in1, float in2) { *accum += in1*in2; } // combiner function static void dotProductSum(float *accum, const float *val) { *accum += *val; }
// Find a zero Element in a 2D allocation; return (-1, -1) if none #pragma rs reduce(fz2) \ initializer(fz2Init) \ accumulator(fz2Accum) combiner(fz2Combine) static void fz2Init(int2 *accum) { accum->x = accum->y = -1; } static void fz2Accum(int2 *accum, int inVal, int x /* special arg */, int y /* special arg */) { if (inVal==0) { accum->x = x; accum->y = y; } } static void fz2Combine(int2 *accum, const int2 *accum2) { if (accum2->x >= 0) *accum = *accum2; }
// Note that this kernel returns an array to Java #pragma rs reduce(histogram) \ accumulator(hsgAccum) combiner(hsgCombine) #define BUCKETS 256 typedef uint32_t Histogram[BUCKETS]; // Note: No initializer function -- // therefore, each bucket is implicitly initialized to 0. static void hsgAccum(Histogram *h, uchar in) { ++(*h)[in]; } static void hsgCombine(Histogram *accum, const Histogram *addend) { for (int i = 0; i < BUCKETS; ++i) (*accum)[i] += (*addend)[i]; } // Determines the mode (most frequently occurring value), and returns // the value and the frequency. // // If multiple values have the same highest frequency, returns the lowest // of those values. // // Shares functions with the histogram reduction kernel. #pragma rs reduce(mode) \ accumulator(hsgAccum) combiner(hsgCombine) \ outconverter(modeOutConvert) static void modeOutConvert(int2 *result, const Histogram *h) { uint32_t mode = 0; for (int i = 1; i < BUCKETS; ++i) if ((*h)[i] > (*h)[mode]) mode = i; result->x = mode; result->y = (*h)[mode]; }
Additional code samples
The BasicRenderScript , RenderScriptIntrinsic , and Hello Compute samples further demonstrate the use of the APIs covered on this page.