RenderScript — это фреймворк для высокопроизводительного выполнения ресурсоёмких вычислительных задач на Android. RenderScript в первую очередь ориентирован на параллельные вычисления, хотя последовательные нагрузки также могут быть полезны. Среда выполнения RenderScript распараллеливает работу между процессорами устройства, такими как многоядерные центральные процессоры и графические процессоры. Это позволяет сосредоточиться на реализации алгоритмов, а не на планировании задач. RenderScript особенно полезен для приложений, выполняющих обработку изображений, вычислительную фотографию или компьютерное зрение.
Чтобы начать работу с RenderScript, вам следует понять две основные концепции:
- Сам язык является производным от C99 и предназначен для написания высокопроизводительного вычислительного кода. В разделе «Написание ядра RenderScript» описывается, как использовать его для написания вычислительных ядер.
- API управления используется для управления жизненным циклом ресурсов RenderScript и управления выполнением ядра. Он доступен на трёх языках: Java, C++ в Android NDK и на языке ядра, производном от C99. Примеры «Использование RenderScript из кода Java» и «RenderScript с одним исходным кодом» описывают первый и третий варианты соответственно.
Написание ядра RenderScript
Ядро RenderScript обычно находится в файле .rs в каталоге <project_root>/src/rs ; каждый файл .rs называется скриптом . Каждый скрипт содержит собственный набор ядер, функций и переменных. Скрипт может содержать:
- Объявление прагмы (
#pragma version(1)), которое объявляет версию языка ядра RenderScript, используемого в этом скрипте. В настоящее время единственное допустимое значение — 1. - Объявление прагмы (
#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 проверяет, что все входные и выходные объекты распределения имеют одинаковые размеры, а типы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. Среда выполнения RenderScript проверяет, соответствует ли типElementвходного распределения прототипу функции аккумулятора; если он не совпадает, 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-инструкции процессора).
Доступ к 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 (возможно, значительно) большего размера, чем при использовании собственных API (
android.renderscript).
Использование 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; этот класс имеет имя
- Заполнение распределений данными. За исключением распределений, созданных с помощью
createFromBitmap(), распределение заполняется пустыми данными при его создании. Для заполнения распределения используйте один из методов копирования вAllocation. Методы копирования являются синхронными . - Установите все необходимые глобальные переменные скрипта . Вы можете задать глобальные переменные с помощью методов того же класса
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 с помощью одного из методов копированияAllocation. Чтобы получить результат ядра редукции, необходимо использовать методjavaFutureType .get(). Методы copy иget()являются синхронными . - Удалите контекст RenderScript. Вы можете удалить контекст RenderScript с помощью
destroy()или разрешив сборке мусора объект контекста RenderScript. Это приведёт к тому, что любое дальнейшее использование любого объекта, принадлежащего этому контексту, будет вызывать исключение.
Асинхронная модель выполнения
Отражённые методы forEach , invoke , reduce и set являются асинхронными — каждый из них может вернуть управление в Java до завершения запрошенного действия. Однако отдельные действия сериализуются в порядке их запуска.
Класс Allocation предоставляет методы копирования для копирования данных в/из Allocation. Метод копирования является синхронным и сериализуется относительно любого из асинхронных действий, описанных выше, которые затрагивают то же самое Allocation.
Отраженные классы javaFutureType предоставляют метод get() для получения результата редукции. get() является синхронным и сериализуется относительно редукции (которая является асинхронной).
RenderScript с одним источником
В Android 7.0 (уровень API 24) представлена новая функция программирования под названием Single-Source RenderScript (Single-Source RenderScript ), при которой ядра запускаются из скрипта, в котором они определены, а не из Java. В настоящее время этот подход ограничен отображением ядер, которые в этом разделе для краткости называются просто «ядрами». Эта новая функция также поддерживает создание выделений типа rs_allocation внутри скрипта. Теперь можно реализовать целый алгоритм целиком в скрипте, даже если требуется несколько запусков ядра. Преимущество двойное: более читаемый код, поскольку реализация алгоритма выполняется на одном языке; и потенциально более быстрый код из-за меньшего количества переходов между Java и RenderScript при нескольких запусках ядра.
В RenderScript с одним источником ядра пишутся так, как описано в разделе «Написание ядра RenderScript» . Затем вы пишете вызываемую функцию, которая вызывает 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 для базового выделения, и система сможет освободить ресурсы как можно раньше.
В разделе «Написание ядра RenderScript» представлен пример ядра, инвертирующего изображение. В примере ниже он расширен для применения к изображению нескольких эффектов с использованием 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. Без использования RenderScript с одним источником пришлось бы запускать оба ядра из кода Java, отделяя запуски ядра от определений ядра и затрудняя понимание всего алгоритма. Код 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 нет функции объединения, поэтому будет использоваться функция накопления. Это правильное поведение, поскольку если мы разделим набор значений на две части и сложим значения в этих двух частях по отдельности, то суммирование этих двух сумм будет равносильно суммированию всего набора.
Пример: В ядре findMinAndMax функция объединения проверяет, меньше ли минимальное значение, записанное в элементе данных аккумулятора «источника» *val , минимального значения, записанного в элементе данных аккумулятора «приемника» *accum , и обновляет *accum соответствующим образом. Аналогичная работа выполняется для максимального значения. Это обновляет *accum до состояния, которое было бы, если бы все входные значения были накоплены в *accum а не часть в *accum , а часть в *val .
После объединения всех элементов данных аккумулятора RenderScript определяет результат редукции для возврата в Java. Для этого можно написать функцию-преобразователь . Если вы хотите, чтобы итоговое значение объединённых элементов данных аккумулятора было результатом редукции, писать функцию-преобразователь не нужно.
Пример: В ядре 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 )(обязательно): указывает, что определяется ядро-редукция. Отражённый метод 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 различны.
Чего вы не можете предположить?
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.
You must not rely on the order in which RenderScript calls the initializer, accumulator, and combiner functions; it may even call some of them in parallel. There is no guarantee that two launches of the same kernel with the same input will follow the same order. The only guarantee is that only the initializer function will ever see an uninitialized accumulator data item. For example:
- 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 leaveAthe same -
combinerName (& I , & A )must leaveIthe 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 )leavesAthe same, becauseIisINITVAL. -
fMMCombiner(& I , & A )setsItoA, becauseIisINITVAL.
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 :
Kotlin
// 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
Java
// 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:
Kotlin
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()
Java
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 .
Kotlin
class ScriptC_filename(rs: RenderScript) : ScriptC(…) { object javaFutureType { fun get(): javaResultType { … } } }
Java
public class ScriptC_filename extends ScriptC { public static class javaFutureType { public javaResultType get() { … } } }
javaResultType is determined from the resultType of the outconverter function . Unless resultType is an unsigned type (scalar, vector, or array), javaResultType is the directly corresponding Java type. If resultType is an unsigned type and there is a larger Java signed type, then javaResultType is that larger Java signed type; otherwise, it is the directly corresponding Java type. For example:
- 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.
Например:
Kotlin
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> = … } }
Java
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
devecSiInXType is the Java type corresponding to the inXType of the corresponding argument of the accumulator function . Unless inXType is an unsigned type or a vector type, devecSiInXType is the directly corresponding Java type. If inXType is an unsigned scalar type, then devecSiInXType is the Java type directly corresponding to the signed scalar type of the same size. If inXType is a signed vector type, then devecSiInXType is the Java type directly corresponding to the vector component type. If inXType is an unsigned vector type, then devecSiInXType is the Java type directly corresponding to the signed scalar type of the same size as the vector component type. For example:
- 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 ofAllocationwork. - 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 ofAllocationwork. - If inXType is
uint2, then deviceSiInXType isint. This is a combination of the wayint2anduintare 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.