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
различны.
Чего вы не можете предположить?
Вы не должны полагаться на количество элементов данных аккумулятора, созданных 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, охватываемых на этой странице.