RenderScript to platforma do wykonywania zadań wymagających dużej mocy obliczeniowej z dużą wydajnością na urządzeniu z Androidem. Skrypt RenderScript jest przeznaczony głównie do użycia w obliczeniach równoległych, chociaż zadań również może być korzystne. Środowisko wykonawcze RenderScript działa równolegle działają z różnymi procesorami dostępnymi w urządzeniu, np. procesorami wielordzeniowymi i GPU. Dzięki temu na wyrażaniu algorytmów, a nie na planowaniu pracy. Obecny skrypt RenderScript szczególnie przydaje się w aplikacjach do przetwarzania obrazu, fotografii obliczeniowej lub rozpoznawania obrazów.
Przed rozpoczęciem korzystania z języka RenderScript musisz pamiętać o 2 głównych koncepcjach:
- Sam język to język wywodzący się z C99, służący do pisania wydajnych obliczeń obliczeniowych. w kodzie. Tworzenie jądra RenderScriptu zawiera opis: jak jej używać do pisania jąder obliczeniowych.
- Interfejs control API służy do zarządzania czasem trwania zasobów RenderScript i na sterowanie wykonywaniem jądra systemu operacyjnego. Program jest dostępny w trzech różnych językach: Java oraz C++ w Androidzie. NDK oraz język jądra pochodzącego z C99. Używanie kodu RenderScript z kodu Java i Metoda z jednego źródła RenderScript opisuje pierwsze i trzecie opcje.
Pisanie jądra RenderScriptu
Jądro RenderScript znajduje się zwykle w pliku .rs
katalog <project_root>/src/rs
; każdy plik .rs
jest nazywany
script. Każdy skrypt zawiera własny zestaw jąder, funkcji i zmiennych. Skrypt może
zawierają:
- Deklaracja pragmy (
#pragma version(1)
) deklarująca wersję klucza Język jądra RenderScriptu użyty w tym skrypcie. Obecnie jedyną prawidłową wartością jest 1. - Deklaracja pragmy (
#pragma rs java_package_name(com.example.app)
), która deklaruje nazwę pakietu klas Java odzwierciedlonych przez ten skrypt. Pamiętaj, że plik.rs
musi być częścią pakietu aplikacji, a nie w w projekcie bibliotecznym. - Co najmniej 0 funkcji wywoływanych. Funkcja wywoływana to jednowątkowy skrypt RenderScript , którą możesz wywołać z poziomu kodu Java za pomocą dowolnych argumentów. Są one często przydatne, konfiguracji początkowej lub obliczeń szeregowych w większym potoku przetwarzania.
0 lub więcej globalnych skryptów. Skrypt globalny jest podobny do zmiennej globalnej w C. Dostępne opcje globalnie skryptu dostępu z kodu Java, które są często używane do przekazywania parametrów do RenderScriptu jądra systemu operacyjnego. Globalne wartości skryptu zostały szczegółowo wyjaśnione tutaj.
0 lub więcej jąder obliczeniowych. Jądro obliczeniowe to funkcja lub zbiór funkcji, które można równolegle wykonywać środowisko wykonawcze RenderScript w zbiorze danych. Są 2 rodzaje obliczeń jądra systemu: mapowanie jądra (inaczej jedna lub jądra) i jądro redukcji.
Jądro mapowania to funkcja równoległa, która działa na zbiorze
Allocations
o tych samych wymiarach. Domyślnie jest wykonywany raz na każdą współrzędną w tych wymiarach. Jest on zwykle (ale nie wyłącznie) używany do przekształć zbiór danych wejściowychAllocations
w wyjdźAllocation
jedenElement
przy obecnie się znajdujesz.Oto przykład prostego jądra mapowania:
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; }
Pod wieloma względami równa się standardowemu C . Właściwość
RS_KERNEL
zastosowana do Prototyp funkcji określa, że funkcja jest jądrem mapowania RenderScript, a nie . Argumentin
jest wypełniany automatycznie na podstawie dane wejścioweAllocation
przekazane do uruchomienia jądra. argumentyx
iy
są omówiono poniżej. Wartość zwrócona z jądra to automatycznie zapisane w odpowiedniej lokalizacji danych wyjściowychAllocation
. Domyślnie to jądro jest uruchamiane na wszystkich danych wejściowychAllocation
, z jednym wykonaniem funkcji jądra naElement
wAllocation
.Jądro mapowania może mieć 1 lub więcej danych wejściowych
Allocations
, 1 wyjściowyAllocation
lub oba te elementy. Renderowanie w środowisku wykonawczym sprawdza, czy wszystkie przydziały wejściowe i wyjściowe mają takie same oraz że typy danych wejściowych i wyjściowych typuElement
Przydziały są zgodne z prototypem jądra. jeśli któraś z tych kontroli nie powiedzie się, RenderScript zgłasza wyjątek.UWAGA: przed Androidem 6.0 (poziom interfejsu API 23) jądro mapowania może nie może mieć więcej niż jednej wartości wejściowej
Allocation
.Jeśli potrzebujesz dodatkowych danych wejściowych lub wyjściowych
Allocations
niż jądro, te obiekty powinny być powiązane z globalnymi zasobami skrypturs_allocation
i uzyskiwane z poziomu jądra lub niedopuszczalnej funkcji przezrsGetElementAt_type()
lubrsSetElementAt_type()
.UWAGA:
RS_KERNEL
jest makrem zdefiniowane automatycznie przez RenderScript dla Twojej wygody:#define RS_KERNEL __attribute__((kernel))
Jądro redukcji to rodzina funkcji, które działają na zbiorze danych wejściowych
Allocations
o tych samych wymiarach. Domyślnie jego funkcja zasobnika jest wykonywana raz na każdą w tych wymiarach. Jest on zwykle używany (ale nie wyłącznie) do „redukcji” w zbiór danych wejściowychAllocations
do pojedynczej .Oto przykład prostej redukcji jądro, które łączy
Elements
dane wejściowe:#pragma rs reduce(addint) accumulator(addintAccum) static void addintAccum(int *accum, int val) { *accum += val; }
Jądro redukcji składa się z co najmniej 1 funkcji napisanej przez użytkownika. Do definiowania jądra służy
#pragma rs reduce
– podaj jego nazwę (w tym przykładzieaddint
) oraz nazwy i role funkcji, które ją jądro (funkcjaaccumulator
addintAccum
, w tym ). Wszystkie takie funkcje muszą mieć typstatic
. Jądro redukcji zawsze wymaga funkcjiaccumulator
; może też pełnić inne funkcje które ma być wykonane przez jądro.Funkcja akumulatora redukcji musi zwracać wartość
void
i musi mieć co najmniej dwóch argumentów. Pierwszy argument (w tym przykładzieaccum
) jest wskaźnikiem do element danych zasobnika, a drugi (w tym przykładzieval
) to wypełnione automatycznie na podstawie danych wejściowych z polaAllocation
przekazanych do uruchomienia jądra systemu operacyjnego. Element danych zasobnika jest tworzony przez środowisko wykonawcze RenderScript. autor: inicjowana jest wartość zerowa. Domyślnie to jądro jest uruchamiane na wszystkich danych wejściowychAllocation
, z jednym wykonaniem funkcji akumulatora naElement
w:Allocation
. Według domyślnie, ostateczna wartość elementu danych akumulatora jest traktowana jako wynik funkcji i wraca do Javy. Środowisko wykonawcze RenderScript sprawdza, czy typElement
przydziału wejściowego jest zgodny z parametrem zasobnika prototyp; Jeśli nie, RenderScript zgłasza wyjątek.Jądro redukcji ma co najmniej 1 wejście
Allocations
, ale nie ma danych wyjściowychAllocations
.Jądro redukcji wyjaśniamy bardziej szczegółowo tutaj.
Jądro redukcji są obsługiwane w Androidzie 7.0 (poziom interfejsu API 24) i nowszych.
Funkcja jądra mapowania lub funkcja zasobnika jądra redukcyjnego może uzyskiwać dostęp do współrzędnych. bieżącego wykonania przy użyciu specjalnych argumentów
x
,y
iz
, które muszą być typuint
lubuint32_t
. Te argumenty są opcjonalne.funkcję jądra mapowania lub zasobnik jądra redukcji, funkcja może także przyjmować opcjonalny specjalny argument
context
typu rs_kernel_context. Jest to potrzebne przez rodzinę interfejsów API środowiska wykonawczego używanych do wysyłania zapytań określone właściwości bieżącego wykonania, na przykład rsGetDimX. (Argumentcontext
jest dostępny w Androidzie 6.0 (poziom interfejsu API 23) i nowszych.- Opcjonalna funkcja
init()
. Funkcjainit()
to specjalny typ funkcji Niedostępna funkcja uruchamiana przez RenderScript podczas tworzenia pierwszej instancji skryptu. Pozwala to na korzystanie z obliczeniach, aby odbywały się automatycznie podczas tworzenia skryptu. - 0 lub więcej globalnych wartości i funkcji skryptów statycznych. Statyczny skrypt globalny jest odpowiednikiem
skrypt globalny, ale nie można uzyskać do niego dostępu z poziomu kodu Java. Funkcja statyczna to standardowa funkcja C
funkcja, która może być wywoływana z dowolnego jądra lub funkcji niedostępnej w skrypcie, ale nie jest ujawniona
do interfejsu Java API. Jeśli dostęp do globalnego skryptu lub funkcji nie jest potrzebny z poziomu kodu Java, można
zdecydowanie zalecamy zadeklarowanie jej jako
static
.
Ustawianie precyzji liczby zmiennoprzecinkowej
Możesz kontrolować wymagany poziom dokładności liczby zmiennoprzecinkowej w skrypcie. Jest to przydatne, jeśli Pełny standard IEEE 754-2008 (używany domyślnie) nie jest wymagany. Te pragmy mogą ustawić różne poziomy dokładności liczb zmiennoprzecinkowych:
#pragma rs_fp_full
(domyślnie, jeśli nic nie zostało określone): w przypadku aplikacji, które wymagają precyzja zmiennoprzecinkowa zgodnie ze standardem IEEE 754-2008.#pragma rs_fp_relaxed
: dla aplikacji, które nie wymagają rygorystycznego standardu IEEE 754-2008 zgodności i tolerować mniejszą precyzję. Ten tryb włącza czyszczenie do zera w przypadku denormów i do zera.#pragma rs_fp_imprecise
: dla aplikacji, które nie wymagają bardzo rygorystycznej precyzji . Ten tryb włącza wszystko w usłudzers_fp_relaxed
oraz :- Operacje z wartością -0,0 mogą zwracać zamiast tego +0,0.
- Operacje na INF i NAN są niezdefiniowane.
Większość aplikacji może korzystać z rs_fp_relaxed
bez żadnych efektów ubocznych. Może to być bardzo
jest korzystny w przypadku niektórych architektur ze względu na dodatkowe optymalizacje dostępne tylko w przypadku
precyzji (np. instrukcje dotyczące procesora SIMD).
Dostęp do interfejsów RenderScript API z poziomu Javy
Gdy tworzysz aplikację na Androida korzystającą z języka RenderScript, jej interfejs API możesz uzyskać za pomocą języka Java na jeden z dwóch sposobów:
android.renderscript
– interfejsy API w tym pakiecie klas są dostępne na urządzeniach z Androidem 3.0 (poziom interfejsu API 11) lub nowszym.android.support.v8.renderscript
– interfejsy API w tym pakiecie są dostępne w Centrum pomocy Biblioteka, która umożliwia korzystanie z nich na urządzeniach z Androidem 2.3 (poziom interfejsu API 9) oraz wyższe.
Oto wady:
- Jeśli używasz interfejsów API biblioteki pomocy, część aplikacji w języku RenderScript będzie
zgodne z urządzeniami z Androidem 2.3 (poziom interfejsu API 9) lub nowszym niezależnie od tego, który skrypt RenderScript
funkcji używanych przez Ciebie. Dzięki temu aplikacja może działać na większej liczbie urządzeń niż w przypadku
natywne (
android.renderscript
) interfejsy API. - Niektóre funkcje RenderScript nie są dostępne w interfejsach API z biblioteki pomocy.
- Jeśli używasz interfejsów API biblioteki pomocy, otrzymasz (prawdopodobnie znacznie) większe pliki APK niż
jeśli używasz natywnych (
android.renderscript
) interfejsów API.
Korzystanie z interfejsów API biblioteki pomocy RenderScript
Aby korzystać z interfejsów API RenderScript Biblioteki pomocy, musisz skonfigurować programowanie i środowisko, aby mieć do nich dostęp. Poniższe narzędzia Android SDK są wymagane do korzystania z tych interfejsów API:
- Android SDK Tools w wersji 22.2 lub nowszej
- Android SDK Build-tools w wersji 18.1.0 lub nowszej
Uwaga: począwszy od pakietu Android SDK Build-tools w wersji 24.0.0, przez Androida 2.2, (Poziom 8 interfejsu API) nie jest już obsługiwany.
Zainstalowaną wersję tych narzędzi możesz sprawdzić i zaktualizować w Menedżer pakietów SDK na Androida
Aby użyć interfejsów RenderScript API z biblioteki pomocy:
- Upewnij się, że masz zainstalowaną wymaganą wersję pakietu SDK na Androida.
- Zaktualizuj ustawienia procesu kompilacji Androida, dodając ustawienia RenderScript:
- Otwórz plik
build.gradle
w folderze aplikacji modułu aplikacji. - Dodaj do pliku te ustawienia RenderScript:
Odlotowe
android { compileSdkVersion 33 defaultConfig { minSdkVersion 9 targetSdkVersion 19 renderscriptTargetApi 18 renderscriptSupportModeEnabled true } }
Kotlin
android { compileSdkVersion(33) defaultConfig { minSdkVersion(9) targetSdkVersion(19) renderscriptTargetApi = 18 renderscriptSupportModeEnabled = true } }
Wymienione powyżej ustawienia określają działanie w procesie kompilacji Androida:
renderscriptTargetApi
– określa wersję kodu bajtowego do . Zalecamy ustawienie tej wartości na najniższy poziom interfejsu API, jaki jest dostępny wszystkich używanych funkcji i ustawrenderscriptSupportModeEnabled
do:true
. Dla tego ustawienia prawidłowymi wartościami są dowolne liczby całkowite z 11 do najnowszego poziomu interfejsu API. Jeśli Twoja minimalna wersja pakietu SDK określona w pliku manifestu aplikacji jest ustawiona na inną wartość, jest ignorowany, a wartość docelowa w pliku kompilacji służy do określenia minimalnej wartości, Wersja pakietu SDK.renderscriptSupportModeEnabled
– określa, że wygenerowany kod na urządzeniu, na którym działa, kod bajtowy powinien wrócić do zgodnej wersji on nie obsługuje wersji docelowej.
- Otwórz plik
- W klasach aplikacji, które korzystają z RenderScriptu, dodaj import do biblioteki pomocy.
zajęcia:
Kotlin
import android.support.v8.renderscript.*
Java
import android.support.v8.renderscript.*;
używanie RenderScriptu za pomocą języka Java lub Kotlin Code,
Używanie RenderScriptu za pomocą kodu Java lub Kotlin opiera się na klasach API znajdujących się w
android.renderscript
lub pakiet android.support.v8.renderscript
. Większość
aplikacje są zgodne z tym samym podstawowym wzorcem użytkowania:
- Zainicjuj kontekst RenderScriptu. Kontekst
RenderScript
utworzony za pomocą metodycreate(Context)
gwarantuje, że skrypt RenderScript może być używany i udostępnia obiektu, aby kontrolować czas życia wszystkich kolejnych obiektów RenderScript. Trzeba wziąć pod uwagę kontekst jako potencjalnie długo trwającej operacji, ponieważ może ona tworzyć zasoby w różnych Sprzęt; nie powinien znajdować się na ścieżce krytycznej aplikacji, jeśli w ogóle jak to tylko możliwe. Zwykle aplikacja ma tylko jeden kontekst RenderScript naraz. - Utwórz co najmniej jeden element typu
Allocation
, który ma być przekazywany do skrypt.Allocation
to obiekt RenderScript, który udostępnia do przechowywania stałej ilości danych. Ziarna w skryptach zajmująAllocation
jako dane wejściowe i wyjściowe, a obiektyAllocation
mogą być dostęp w jądrze przy użyciu interfejsursGetElementAt_type()
irsSetElementAt_type()
w przypadku powiązania jako globalnego skryptu. ObiektyAllocation
umożliwiają przekazywanie tablic z kodu Java do skryptu RenderScript w kodzie i na odwrót. ObiektyAllocation
są zwykle tworzone za pomocą funkcjicreateTyped()
lubcreateFromBitmap()
. - Utwórz potrzebne skrypty. Dostępne są 2 rodzaje skryptów
podczas korzystania z kodu RenderScript:
- ScriptC: są to skrypty zdefiniowane przez użytkownika zgodnie z opisem w sekcji Pisanie jądra RenderScriptu powyżej. Każdy skrypt ma klasę Java
uwzględniany przez kompilator RenderScript, aby ułatwić dostęp do skryptu z poziomu kodu Java;
te zajęcia mają nazwę
ScriptC_filename
. Jeśli na przykład jądro mapowania te znajdowały się w lokalizacjiinvert.rs
, a kontekst RenderScriptu znajdował się już wmRenderScript
, kod Java lub Kotlin do utworzenia wystąpienia skryptu będzie wyglądał tak:Kotlin
val invert = ScriptC_invert(renderScript)
Java
ScriptC_invert invert = new ScriptC_invert(renderScript);
- ScriptIntrinsic: to wbudowane jądra RenderScriptu służące do typowych operacji.
takie jak rozmycie Gaussa, splot czy przenikanie obrazów. Więcej informacji znajdziesz w podklasach funkcji
ScriptIntrinsic
- ScriptC: są to skrypty zdefiniowane przez użytkownika zgodnie z opisem w sekcji Pisanie jądra RenderScriptu powyżej. Każdy skrypt ma klasę Java
uwzględniany przez kompilator RenderScript, aby ułatwić dostęp do skryptu z poziomu kodu Java;
te zajęcia mają nazwę
- Wypełnianie przydziałów danymi. Z wyjątkiem przydziałów utworzonych za pomocą funkcji
createFromBitmap()
w polu Alokacja znajdują się puste dane, gdy jest które zostały utworzone po raz pierwszy. Aby wypełnić pole Przydział, użyj jednego z funkcji „Kopiuj”Allocation
. Kopia są synchroniczne. - Ustaw niezbędne globalne wartości skryptu. Możesz ustawić wartości globalne za pomocą metod w sekcji
ta sama klasa
ScriptC_filename
o nazwieset_globalname
. Dla: przykład, aby ustawić zmiennąint
o nazwiethreshold
, użyj funkcji Metoda Javaset_threshold(int)
; oraz ustawić zmiennejrs_allocation
o nazwielookup
, użyj Javy metodaset_lookup(Allocation)
. Metodyset
są asynchroniczne. - Uruchom odpowiednie jądra i niemożliwe funkcje.
Metody uruchamiania danego jądra: znajdują się w tej samej klasie
ScriptC_filename
za pomocą metod o nazwachforEach_mappingKernelName()
lubreduce_reductionKernelName()
. Zmiany te są asynchroniczne. W zależności od argumentów jądra przyjmuje co najmniej jeden przydział, który musi mieć te same wymiary. Domyślnie jądro uruchamia się w przypadku wszystkich współrzędnych w tych wymiarach, do uruchomienia jądra na podzbiorze tych współrzędnych, przekazać odpowiedniScript.LaunchOptions
jako ostatni argument w metodzieforEach
lubreduce
.Uruchamiaj funkcje, których nie można wywołać, używając metod
invoke_functionName
w tej samej klasieScriptC_filename
. Zmiany te są asynchroniczne. - Pobieranie danych z
Allocation
obiektów i javaFutureType. Aby uzyskać dostęp do danych zAllocation
z kodu Java, musisz je skopiować lub wrócić do Javy za pomocą jednej z funkcji wAllocation
. Aby uzyskać wynik redukcji jądra, należy użyć metodyjavaFutureType.get()
. Kopia i metodyget()
są synchroniczne. - Przeanalizuj kontekst RenderScriptu. Możesz zniszczyć kontekst RenderScriptu
za pomocą funkcji
destroy()
lub przez włączenie kontekstu RenderScript jako obiekt do czyszczenia pamięci. Powoduje to dalsze korzystanie z należących do niej obiektów do zgłoszenia wyjątku.
Asynchroniczny model wykonywania
Wyróżnione forEach
, invoke
, reduce
,
a metody set
są asynchroniczne – każda może wrócić do Javy przed zakończeniem
wymagane działanie. Poszczególne działania są jednak szeregowane w kolejności, w jakiej zostały uruchomione.
Klasa Allocation
zawiera polecenie „copy” metod kopiowania danych do
i sekcji Alokacje. „Kopia” jest synchroniczna i jest zserializowana względem dowolnego
działań asynchronicznych powyżej, które mają wpływ na tę samą alokację.
Odzwierciedlone klasy javaFutureType zapewniają
metody get()
w celu uzyskania wyniku redukcji. get()
to
synchroniczna i jest zserializowana zgodnie z redukcją (asynchroniczną).
Skrypt RenderScript z jednego źródła
Android 7.0 (poziom API 24) wprowadza nową funkcję programowania o nazwie Jedno źródłową
RenderScript, w którym jądra są uruchamiane ze skryptu, w którym zostały zdefiniowane, a nie
z Javy. To podejście jest obecnie ograniczone do jąder mapowania, które określa się po prostu jako „jądro”
w tej sekcji, aby zapewnić zwięzłość. Ta nowa funkcja obsługuje też tworzenie alokacji typu
rs_allocation
w skrypcie. Teraz można
wdrożysz cały algorytm wyłącznie w skrypcie, nawet jeśli wymagane jest uruchomienie wielu jądra systemu.
Korzyści są 2 korzyści: bardziej czytelny kod, ponieważ dzięki temu implementacja algorytmu jest
jeden język; i potencjalnie szybszy kod ze względu na mniejszą liczbę przejść między Javą
Zastosowanie obsługi języka RenderScript w przypadku wielu jądra systemu.
W języku RenderScript na jedno źródło treści zapisujesz jądra w sposób opisany tutaj:
Jak napisać jądro RenderScript Następnie piszesz niemożliwą funkcję, która wywołuje
rsForEach()
, aby je uruchomić. Ten interfejs API przyjmuje funkcję jądra jako
oraz alokacje wejściowe i wyjściowe. Podobny interfejs API
rsForEachWithOptions()
przyjmuje dodatkowy argument typu
.
rs_script_call_t
, który określa podzbiór elementów z danych wejściowych i
przydziały danych wyjściowych, które ma przetworzyć funkcja jądra.
Aby rozpocząć obliczenia w języku RenderScript, wywołaj funkcję niedostępną w języku Java.
Wykonaj czynności opisane w sekcji Używanie skryptu RenderScript z kodu Java.
W kroku uruchom odpowiednie jądra wywołaj
funkcji wywoływanej przez funkcję invoke_function_name()
, która spowoduje uruchomienie funkcji
całe obliczenia, w tym uruchamianie jąder.
Aby zapisać i przekazać, konieczne są często przydziały
wyników pośrednich między uruchomieniem jądra systemu operacyjnego. Możesz je utworzyć za pomocą
rsCreateAllocation(). Prosta w użyciu forma tego interfejsu API to
rsCreateAllocation_<T><W>(…)
, gdzie T to typ danych dla
elementu, a W to szerokość wektora elementu. Interfejs API przyjmuje rozmiary w
wymiarów X, Y i Z jako argumentów. W przypadku przydziałów 1D lub 2D rozmiar wymiaru Y lub Z może
zostać pominięty. Na przykład rsCreateAllocation_uchar4(16384)
tworzy przydział 1D o wartości
16384 elementy, z których każdy jest typu uchar4
.
Przydziałami zarządza system automatycznie. Ty
nie muszą być specjalnie zwalniane ani zwalniane. Możesz jednak zadzwonić
rsClearObject(rs_allocation* alloc)
, aby wskazać, że nie potrzebujesz już nicku.
alloc
do przydziału bazowego,
aby system mógł jak najszybciej zwolnić zasoby.
Sekcja Tworzenie jądra RenderScriptu zawiera przykład
jądro, które odwraca obraz. Poniższy przykład rozwija tę opcję, aby zastosować do obrazu więcej niż 1 efekt.
korzystając z języka RenderScript na pojedyncze źródło. Zawiera inne jądro (greyscale
), które przekształca
aby uzyskać czarno-biały obraz. Wywoływana funkcja process()
stosuje następnie te 2 jądro
obok obrazu wejściowego i generuje obraz wyjściowy. Przydziały danych wejściowych i
dane wyjściowe są przekazywane jako argumenty typu
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); }
Funkcję process()
możesz wywołać w Javie lub Kotlinie w następujący sposób:
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)
Java
// 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);
Ten przykład pokazuje, jak można w pełni wdrożyć algorytm, który obejmuje 2 uruchomienia jądra w samym języku RenderScript. Bez pojedynczego źródła RenderScript, musisz uruchomić oba jądro z kodu Java, oddzielając uruchomienia jądra od definicji jądra i utrudniać zrozumienie całego algorytmu. Jest to nie tylko kod RenderScript z jednego źródła jest łatwiejszy do odczytania, a dodatkowo eliminuje między Javą a skryptem podczas uruchamiania jądra systemu operacyjnego. Niektóre iteracyjne algorytmy mogą uruchamiać jądra co sprawia, że koszty związane z takimi zmianami są znaczne.
Globalne wartości skryptu
Skrypt globalny to zwykły element inny niż static
w pliku skryptu (.rs
). Scenariusz
globalna o nazwie var zdefiniowany w
filename.rs
, będzie
metoda get_var
odzwierciedlona w
zajęcia ScriptC_filename
. O ile operator globalny
jest const
, będzie też
metoda set_var
.
Dany skrypt globalny ma 2 oddzielne wartości: Java i script. Wartości te działają w ten sposób:
- Jeśli skrypt var ma w skrypcie inicjator statyczny, określa początkową wartość zmiennej var zarówno w Javie, jak i w tagu skrypt. W przeciwnym razie wartość początkowa wynosi 0.
- Uzyskuje dostęp do parametru var w ramach skryptu z odczytem i zapisem jego wartości skryptu.
- Metoda
get_var
odczytuje język Java . - Metoda
set_var
(jeśli istnieje) zapisuje Natychmiastowa wartość Java i zapisuje wartość skryptu asynchronicznie.
UWAGA: to oznacza, że oprócz static inicjator w skrypcie, wartości zapisane w wartości globalnej z są niewidoczne dla Javy.
Głębokość redukcji ziaren
Zmniejszanie to proces łączenia zbioru danych w jeden . Jest to przydatny element podstawowy przy programowaniu równoległym z aplikacjami, takimi jak :
- obliczanie sumy lub iloczynu na wszystkich danych
- operacje logiczne (
and
,or
,xor
) we wszystkich danych - znajdowanie minimalnej lub maksymalnej wartości w danych
- podczas wyszukiwania konkretnej wartości lub współrzędnych konkretnej wartości w danych
W Androidzie 7.0 (poziom interfejsu API 24) i nowszych skrypt RenderScript obsługuje jądro redukcji, do wydajnych algorytmów redukcji napisanych przez użytkownika. Możesz uruchamiać jądra redukcji na danych wejściowych z 1, 2 lub 3 wymiary.
Powyższy przykład przedstawia proste jądro redukcji addint.
Oto bardziej skomplikowany jądro redukcji findMinAndMax.
który znajduje lokalizacje minimalnej i maksymalnej wartości long
w
Jednowymiarowy 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; }
UWAGA: jest więcej przykładów. jądra systemu znajdziesz tutaj.
Aby można było uruchomić jądro redukcji, środowisko wykonawcze RenderScript tworzy co najmniej jeden
zmiennych nazywanymi danymi akumulatora
elementów do przechowywania stanu procesu redukcji. Środowisko wykonawcze RenderScript
wybiera liczbę elementów danych z akumulatora w taki sposób, by zmaksymalizować skuteczność. Typ
elementów danych akumulatora (accumType) jest określany przez zasobnik jądra
funkcji – pierwszy argument do tej funkcji jest wskaźnikiem do danych z akumulatora.
elementu. Domyślnie każdy element danych zasobnika jest inicjowany do zera (jak w przypadku
autor: memset
); możesz jednak napisać funkcję inicjującą, aby to zrobić
w inny sposób.
Przykład: w polu addint
jądro, elementy danych akumulatora (typu int
) są używane do sumowania danych wejściowych
. Nie ma funkcji inicjatora, więc każdy element danych akumulatora jest inicjowany
zero.
Przykład: In
jądro findMinAndMax, czyli elementy danych akumulatora
(typu MinAndMax
) są używane do śledzenia wartości minimalnych i maksymalnych
do tej pory. Dostępna jest funkcja inicjująca do ustawiania tych wartości na LONG_MAX
oraz
LONG_MIN
; i ustawić lokalizacje tych wartości na -1, co oznacza, że
wartości nie występują w (pustej) części danych wejściowych, która została
przetworzono.
RenderScript wywołuje funkcję zasobnika raz na każdą współrzędną w parametrze danych wejściowych. Zwykle funkcja powinna w jakiś sposób aktualizować element danych akumulatora zgodnie z danymi wejściowymi.
Przykład: w polu addint jądro, funkcja akumulatora dodaje wartość elementu wejściowego do zasobnika elementu danych.
Przykład: In jądro findMinAndMax, czyli funkcja akumulatora sprawdza, czy wartość elementu wejściowego jest mniejsza czy równa minimalnej wartości wartość zarejestrowana w elemencie danych akumulatora lub większa lub równa wartości maksymalnej zarejestrowanej w elemencie danych zasobnika, a także aktualizuje ten element odpowiednio się zmienia.
Po wywołaniu funkcji zasobnika raz dla każdej współrzędnej w danych wejściowych RenderScript musi połączyć zasobnik elementów danych razem w jedną pozycję danych akumulatora. Możesz napisać kombinację . Jeśli funkcja akumulatora ma tylko jeden sygnał wejściowy i brak specjalnych argumentów, nie musisz pisać łącznika funkcji; RenderScript użyje funkcji akumulatora, aby połączyć dane z zasobnika elementy(ów). (Możesz utworzyć funkcję łączącą, jeśli to domyślne działanie nie jest tym, w pobliżu.
Przykład: w polu addint jądro, nie ma funkcji łączącej, więc zostanie użyta funkcja zasobnika. To jest działa prawidłowo, ponieważ jeśli podzielimy zbiór wartości na dwa części i dodaj wartości z tych 2 części osobno. Suma tych dwóch sum jest równa aby uzupełnić całą kolekcję.
Przykład: In
jądro findMinAndMax, czyli funkcja łącząca
sprawdza, czy minimalna wartość zarejestrowana w „źródle” dane akumulatora
element *val
jest mniejszy niż minimalna wartość zarejestrowana w „miejscu docelowym”
element danych zasobnika *accum
, aktualizacje *accum
odpowiednio się zmienia. To samo działa w przypadku wartości maksymalnej. Ta czynność aktualizuje *accum
do stanu, jaki uzyskałaby, gdyby wszystkie wartości wejściowe zostały zgromadzone w
*accum
zamiast kilku w *accum
, a jeszcze innych w
*val
Po połączeniu wszystkich elementów danych akumulatora RenderScript określa w wyniku redukcji i powrót do Javy. Możesz napisać outconverter . Nie musisz pisać funkcji konwertera, aby wartość końcową połączonych elementów danych akumulatora jako wynik redukcji.
Przykład: w jądrze addint nie ma funkcji konwertera zewnętrznego. Ostateczna wartość elementów połączonych danych to suma wszystkich elementów danych wejściowych, czyli wartości, które chcemy zwrócić.
Przykład: In
jądro findMinAndMax, czyli funkcja outconverter,
inicjuje wartość wynikową int2
w celu przechowywania lokalizacji minimalnych i
maksymalnych wartości wynikających z połączenia wszystkich elementów danych akumulatora.
Pisanie jądra redukcji
#pragma rs reduce
definiuje jądro redukcji przez
podając jego nazwę oraz nazwy i role funkcji, które tworzą
gdy ją uruchamiają. Wszystkie takie funkcje muszą być
static
Jądro redukcji zawsze wymaga accumulator
funkcji; możesz pominąć niektóre lub wszystkie pozostałe funkcje, w zależności od tego, czego chcesz
przez jądra.
#pragma rs reduce(kernelName) \ initializer(initializerName) \ accumulator(accumulatorName) \ combiner(combinerName) \ outconverter(outconverterName)
Elementy w #pragma
mają następujące znaczenie:
reduce(kernelName)
(wymagane): określa, że jądro redukcji jest nie jest zdefiniowany. Odzwierciedlona metoda Javyreduce_kernelName
uruchomi jądro.initializer(initializerName)
(opcjonalny): określa nazwę funkcja inicjująca tego jądra redukcji. Po uruchomieniu jądra kod RenderScript tę funkcję po jednym razie dla każdego elementu danych akumulatora. funkcja musi być zdefiniowana w następujący sposób:static void initializerName(accumType *accum) { … }
accum
wskazuje element danych akumulatora dla tej funkcji, aby zainicjować.Jeśli nie podasz funkcji inicjującej, RenderScript zainicjuje każdy zasobnik elementu danych do zera (jak w przypadku funkcji
memset
), zachowując się tak, jakby miał miejsce inicjator wygląda tak:static void initializerName(accumType *accum) { memset(accum, 0, sizeof(*accum)); }
accumulator(accumulatorName)
(obowiązkowe): określa nazwę funkcji akumulatora dla tej jądra redukcji. Po uruchomieniu jądra kod RenderScript tę funkcję raz na każdą współrzędną wejściowych, aby zaktualizować elementu danych akumulatora w jakiś sposób zgodnie z danymi wejściowymi. Funkcja musi być zdefiniowany w ten sposób:static void accumulatorName(accumType *accum, in1Type in1, …, inNType inN [, specialArguments]) { … }
accum
wskazuje element danych akumulatora dla tej funkcji, aby modyfikować. Odin1
doinN
to co najmniej jeden argument, który są automatycznie wypełniane na podstawie danych wejściowych przekazywanych do uruchomienia jądra – 1 argument na dane wejściowe. Funkcja zasobnika może opcjonalnie przyjąć dowolny ze argumentów specjalnych.Przykładowe jądro z wieloma danymi wejściowymi to
dotProduct
.combiner(combinerName)
(opcjonalnie): określa nazwę funkcji łączenia jądra redukcji. Po wywołaniu przez RenderScript funkcji zasobnika raz na każdą współrzędną w danych wejściowych, wywołuje tę funkcję tyle samo by połączyć wszystkie elementy danych akumulatora w jeden elementu danych akumulatora. Funkcja musi być zdefiniowana w ten sposób:
static void combinerName(accumType *accum, const accumType *other) { … }
accum
wskazuje „miejsce docelowe” element danych akumulatora dla tego do zmiany.other
wskazuje „źródło” element danych akumulatora dla tej funkcji do „połączenia” na*accum
.UWAGA: to możliwe. że funkcje
*accum
,*other
lub oba zostały zainicjowane, ale nigdy została przekazana do funkcji akumulatora; oznacza to, że któraś z nich nigdy nie została zaktualizowana zgodnie z dowolnymi danymi wejściowymi. Na przykład w polu jądro findMinAndMax, czyli składnik łączący funkcjafMMCombiner
wyraźnie sprawdza funkcjęidx < 0
, ponieważ wskazuje taki element danych akumulatora, którego wartość to INITVAL.Jeśli nie podasz funkcji łączącej, RenderScript użyje funkcji zasobnika w funkcji działa tak, jakby istniała funkcja łącząca, która wygląda tak:
static void combinerName(accumType *accum, const accumType *other) { accumulatorName(accum, *other); }
Funkcja łącząca jest wymagana, jeśli jądro ma więcej niż 1 dane wejściowe, jeśli dane wejściowe typ danych nie jest taki sam jak typ danych akumulatora lub jeśli funkcja zasobnika przyjmuje taki sam typ lub więcej specjalnych argumentów.
outconverter(outconverterName)
(opcjonalnie): określa nazwę funkcji outconverter dla tej jądra redukcji. Po połączeniu przez RenderScript całego zasobnika elementów danych, wywołuje tę funkcję, aby określić wynik funkcji i powrót do języka Java. Funkcja musi być zdefiniowana w ten sposób: to:static void outconverterName(resultType *result, const accumType *accum) { … }
result
to wskaźnik do elementu danych wyniku (przydzielonego, ale nie zainicjowanego) przez środowisko wykonawcze RenderScript), aby zainicjować tę funkcję z wynikiem funkcji . Parametr resultType określa typ danego elementu danych, który nie musi być taki sam. jako accumType.accum
wskazuje wskaźnik do ostatecznego elementu danych zasobnika obliczaną przez funkcję kombinacyjną.Jeśli nie podasz funkcji outconverter, RenderScript skopiuje końcowy zasobnik elementu danych do wynikowego elementu danych, zachowując się w taki sposób, jakby istniała funkcja przekonwertowania, która wygląda tak:
static void outconverterName(accumType *result, const accumType *accum) { *result = *accum; }
Jeśli chcesz uzyskać inny typ wyniku niż typ danych akumulatora, musisz użyć funkcji konwertera.
Pamiętaj, że jądro ma typy danych wejściowych, typ elementu danych zasobnika i typ wyniku,
które nie muszą być takie same. Na przykład w polu
jądra findMinAndMax, dane wejściowe
typ long
, typ elementu danych zasobnika MinAndMax
i wynik
typy int2
są różne.
Czego nie możesz przyjąć?
Nie możesz polegać na liczbie elementów danych akumulatora utworzonych przez RenderScript dla w przypadku danego jądra systemu operacyjnego. Nie ma gwarancji, że 2 uruchomienia tego samego jądra systemu te same dane wejściowe spowodują utworzenie tej samej liczby elementów danych akumulatora.
Nie możesz polegać na kolejności, w jakiej RenderScript wywołuje inicjator, zasobnik i funkcje łączące; może nawet wywoływać niektóre z nich równolegle. Nie ma gwarancji, że dwa uruchomienia tego samego jądra z tymi samymi danymi wejściowymi będą wykonywane w tej samej kolejności; Jedyna gwarantuje, że tylko funkcja inicjująca zobaczy niezainicjowany zasobnik elementu danych. Na przykład:
- Nie ma gwarancji, że wszystkie elementy danych akumulatora zostaną zainicjowane przed funkcja akumulatora jest wywoływana, ale tylko w przypadku zainicjowanego akumulatora. elementu danych.
- Nie ma gwarancji kolejności, w jakiej elementy wejściowe są przekazywane do zasobnika .
- Nie ma gwarancji, że funkcja akumulatora została wywołana dla wszystkich elementów wejściowych przed wywołaniem funkcji łączącej.
Jedną z konsekwencji jest to, że funkcja findMinAndMax jądro nie jest deterministyczne: jeśli dane wejściowe zawierają więcej niż jedno wystąpienie tego samego wartości minimalnej czy maksymalnej, nie wiadomo, w którym przypadku jądro znaleźć.
Co musisz zagwarantować?
System RenderScript może zdecydować się na uruchomienie jądra w wielu na różne sposoby, musisz przestrzegać pewnych reguł, aby jądro działało w dobry sposób. Jeśli nie będziesz ich przestrzegać, możesz otrzymać nieprawidłowe wyniki. niedeterministyczne zachowanie lub błędy czasu działania.
Poniższe reguły często wskazują, że dwa elementy danych akumulatora muszą mieć „parametr ta sama wartość”. Co to oznacza? To zależy od tego, co ma robić jądro. Dla: ograniczenie matematyczne, np. addint, zwykle ma sens dla hasła „tego samego” oznacza równość matematyczną. Wybierz dowolne wyszukaj takie jako findMinAndMax („znajdź lokalizację minimalnej i maksymalnych wartości wejściowych”), w których może wystąpić więcej niż jedno wystąpienie identycznej wartości wejściowej. wszystkie lokalizacje o danej wartości wejściowej muszą być uznawane za „takie same”. Możesz napisać podobne jądro do „znajdowania lokalizacji minimalnej i maksymalnej wartości wejściowej po lewej stronie”. gdzie (na przykład) wartość minimalna w lokalizacji 100 jest preferowana zamiast tej samej wartości minimalnej w lokalizacji 200; „to samo” oznacza identyczną lokalizację, a nie tylko identyczną wartość, a funkcje kumulatora i łączącego powinny inne niż w przypadku findMinAndMax.
Funkcja inicjująca musi utworzyć wartość tożsamości. To znaczy, jeśliI
i A
to zainicjowane elementy danych akumulatora
przez funkcję inicjatora, a element I
nigdy nie został przekazany do
funkcji akumulatora (ale A
mogła być), a
combinerName(&A, &I)
musi zostawA
to samocombinerName(&I, &A)
musi zostawI
tego samego coA
Przykład: w polu addint jądro, element danych akumulatora zostaje zainicjowany do zera. Funkcja łącząca tego argumentu jądro wykonuje dodawanie; to wartość tożsamości dla dodania.
Przykład: w polu findMinAndMax
jądro, element danych akumulatora zostaje zainicjowany
do INITVAL
.
fMMCombiner(&A, &I)
pozostawia miejsceA
bez zmian, ponieważI
toINITVAL
.fMMCombiner(&I, &A)
ustawiaI
do:A
, boI
toINITVAL
.
Dlatego INITVAL
jest rzeczywiście wartością tożsamości.
Funkcja łącząca musi być przemienna. To znaczy,
jeśli A
i B
to zainicjowane elementy danych akumulatora
przez funkcję inicjatora i która mogła zostać przekazana do funkcji zasobnika zero
lub więcej razy, combinerName(&A, &B)
musi
ustaw A
na tę samą wartość
która combinerName(&B, &A)
ustawia B
.
Przykład: w polu addint jądro, funkcja łącząca dodaje dwie wartości danych elementu danych akumulatora; dodanie to przemienne.
Przykład: w jądrze findMinAndMax
fMMCombiner(&A, &B)
to ta sama wartość co
Atrybuty A = minmax(A, B)
i minmax
są przemienne, więc
fMMCombiner
również.
Funkcja łącząca musi być osobna. To znaczy,
jeśli A
, B
i C
to
elementów danych zasobnika zainicjowanych przez funkcję inicjatora i które mogły zostać przekazane
do funkcji akumulatora zero lub więcej razy, dwie poniższe sekwencje kodu muszą
ustaw A
na tę samą wartość:
combinerName(&A, &B); combinerName(&A, &C);
combinerName(&B, &C); combinerName(&A, &B);
Przykład: w jądrze addint para klucz-wartość funkcja łącząca dodaje 2 wartości elementu danych zasobnika:
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
Dodawanie jest asocjacyjne, tak samo jak funkcja łącząca.
Przykład: w jądrze findMinAndMax
fMMCombiner(&A, &B)jest taka sama jak
A = minmax(A, B)Te 2 ciągi są więc
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)
Komponent minmax
jest powiązany, więc fMMCombiner
również jest powiązany.
Funkcja zasobnika i funkcja łączącego muszą być zgodne z podstawową funkcją
za pomocą reguły składania. Oznacza to, że jeśli A
i B
to elementy danych akumulatora, A
zostało
zainicjowane przez funkcję inicjatora i mogła zostać przekazana do funkcji zasobnika
0 lub więcej razy, B
nie został zainicjowany, a argumenty to
lista argumentów wejściowych i argumentów specjalnych dla określonego wywołania zasobnika
funkcji, trzy następujące sekwencje kodu muszą ustawić atrybut A
na tę samą wartość:
accumulatorName(&A, args); // statement 1
initializerName(&B); // statement 2 accumulatorName(&B, args); // statement 3 combinerName(&A, &B); // statement 4
Przykład: w jądrze addint dla wartości wejściowej V:
- Polecenie 1 jest takie samo jak
A += V
. - Stwierdzenie 2 jest takie samo jak
B = 0
- Stwierdzenie 3 jest takie samo jak
B += V
, które jest identyczne zB = V
. - Stwierdzenie 4 jest takie samo jak
A += B
, które jest identyczne zA += V
.
Instrukcje 1 i 4 ustawiają A
na tę samą wartość, więc to jądro jest zgodne z
podstawowej reguły składania.
Przykład: w jądrze findMinAndMax jako dane wejściowe wartość V w współrzędnych X:
- Polecenie 1 jest takie samo jak
A = minmax(A, IndexedVal(V, X))
. - Stwierdzenie 2 jest takie samo jak
B = INITVAL
- Stwierdzenie 3 jest identyczne z
B = minmax(B, IndexedVal(V, X))
który, ponieważ B to wartość początkowa, jest równaB = IndexedVal(V, X)
- Stwierdzenie 4 jest takie samo jak
A = minmax(A, B)
czyli tyle samo, coA = minmax(A, IndexedVal(V, X))
Instrukcje 1 i 4 ustawiają A
na tę samą wartość, więc to jądro jest zgodne z
podstawowej reguły składania.
Wywołanie jądra redukcji z kodu Java
W przypadku jądra redukcji o nazwie kernelName zdefiniowanego w
filename.rs
, w pliku danych znajdziesz 3 metody
zajęcia 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);
Oto kilka przykładów wywoływania jądra addint:
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();
Metoda 1 ma jeden argument wejściowy Allocation
dla
każdy argument wejściowy w zasobniku jądra
. Środowisko wykonawcze RenderScript sprawdza, czy wszystkie przydziały wejściowe
mają te same wymiary i że typ Element
każdego z
Przydziały wejściowe pasują do argumentu odpowiedniego argumentu wejściowego zasobnika
prototyp funkcji. Jeśli któraś z tych kontroli zakończy się niepowodzeniem, RenderScript zgłosi wyjątek.
gdy jądro jest wykonywane w przypadku wszystkich współrzędnych w tych wymiarach.
Metoda 2 jest taka sama jak metoda 1, ale metoda 2 wymaga dodatkowej
sc
, którego można użyć do ograniczenia wykonywania jądra do podzbioru
.
Metoda 3 jest taka sama jak metoda 1, z tym że
zamiast korzystać z danych wejściowych alokacji, wykorzystuje tablicowe dane w Javie. To wygoda,
eliminuje konieczność pisania kodu w celu jawnego utworzenia przydziału i skopiowania do niego danych.
z tablicy Java. Zastosowanie metody 3 zamiast metody 1 nie zwiększy jednak
wydajności kodu. Dla każdej tablicy wejściowej metoda 3 tworzy tymczasowy
jednowymiarowa alokacja z odpowiednim typem Element
i
włączono setAutoPadding(boolean)
i skopiuje tablicę do
Przydział tak, jak gdyby za pomocą odpowiedniej metody copyFrom()
Allocation
. Następnie wywołuje metodę 1, przekazując te tymczasowe
Przydziały.
UWAGA: jeśli aplikacja będzie wykonywać wiele wywołań jądra za pomocą tej samej tablicy lub z różnymi tablicami o tych samych wymiarach i typie elementu, a nie przez bezpośrednie tworzenie, wypełnianie i ponowne używanie przydziałów. za pomocą metody 3.
javaFutureType,
typ zwrotu odzwierciedlonych metod redukcji jest odzwierciedlony
statyczna zagnieżdżona klasa w elemencie ScriptC_filename
zajęcia. Przedstawia on przyszły rezultat zmniejszenia
uruchomienia jądra systemu operacyjnego. Aby uzyskać rzeczywisty wynik uruchomienia, wywołaj
metody get()
tej klasy, która zwraca wartość
typu javaResultType. Funkcja get()
jest synchroniczna.
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 jest określany na podstawie parametru resultType parametru funkcji konwertera zewnętrznego. O ile resultType nie jest typ bez znaku (skalarny, wektorowy lub tablica), javaResultType jest bezpośrednio odpowiadającym Typ Java. Jeśli resultType jest typem bez podpisu i istnieje większy typ podpisany w Javie, z kolei javaResultType to większy typ podpisany w języku Java; w przeciwnym razie jest bezpośrednio odpowiedniego typu Java. Na przykład:
- Jeśli resultType to
int
,int2
lubint[15]
, to javaResultType toint
,Int2
, lubint[]
. Dozwolone są wszystkie wartości parametru resultType. autorstwa javaResultType. - Jeśli resultType to
uint
,uint2
lubuint[15]
, to javaResultType tolong
,Long2
, lublong[]
. Dozwolone są wszystkie wartości parametru resultType. autorstwa javaResultType. - Jeśli resultType to
ulong
,ulong2
, lubulong[15]
, a następnie javaResultType. jestlong
,Long2
lublong[]
. Istnieją pewne wartości parametru resultType, którego nie można reprezentować przez javaResultType.
javaFutureType to przyszły typ wyniku, który odpowiada do parametru resultType elementu outconverter, .
- Jeśli resultType nie jest typem tablicy, to javaFutureType.
jest
result_resultType
. - Jeśli resultType jest tablicą o długości Count, zawierającą elementy typu memberType,
to javaFutureType to
resultArrayCount_memberType
.
Na przykład:
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() { … } } }
Jeśli javaResultType jest typem obiektu (wraz z typem tablicy), każde wywołanie
do javaFutureType.get()
w tej samej instancji, zwróci to samo
obiektu.
Jeśli javaResultType nie może reprezentować wszystkich wartości typu resultType i
jądro redukcji generuje niereprezentatywną wartość,
javaFutureType.get()
zgłasza wyjątek.
Metoda 3 i devecSiInXType
devecSiInXType to typ Javy odpowiadający wartość inXType odpowiedniego argumentu funkcji akumulatora. O ile inXType nie jest typ bez znaku lub typ wektorowy, devecSiInXType to bezpośrednio odpowiadający mu typ wektora Java typu. Jeśli inXType jest typem skalarnym bez znaku, to devecSiInXType jest Typ Java bezpośrednio odpowiadający typowi ze znakiem skalarnym tego samego rozmiaru. Jeśli inXType jest typem wektora podpisanego, devecSiInXType jest typem wektora Java odpowiadający typowi komponentu wektorowego. Jeśli inXType jest bez podpisu typ wektora, devecSiInXType jest typem Javy odpowiadającym bezpośrednio funkcji typ skalarny ze znakiem o tym samym rozmiarze co typ komponentu wektora. Na przykład:
- Jeśli inXType ma wartość
int
, to devecSiInXType jestint
. - Jeśli inXType ma wartość
int2
, to devecSiInXType jestint
. Tablica jest reprezentacją spłaszczoną: ma 2 razy wiele elementów skalarnych, ponieważ przydział ma dwuskładnikowy wektor Elementy. W ten sam sposób działają metodycopyFrom()
metodyAllocation
. - Jeśli inXType ma wartość
uint
, to deviceSiInXType. jestint
. Podpisana wartość w tablicy Java jest interpretowana jako wartość bez znaku tego samego wzorca bitowego w alokacji. Działa tak samo jakcopyFrom()
metodyAllocation
. - Jeśli inXType ma wartość
uint2
, to deviceSiInXType. jestint
. To połączenie metodint2
iuint
są obsługiwane: tablica jest spłaszczoną reprezentacją, a wartości podpisane w Javie są interpretowane jako wartości niepodpisanych elementów w języku RenderScript.
Pamiętaj, że w przypadku metody 3 typy danych wejściowych są obsługiwane w inny sposób typy wyników niż:
- Dane wejściowe skryptu są wektorowe po stronie Javy, a wynik wektorowy skryptu – nie.
- Niepodpisane dane wejściowe skryptu są przedstawione w języku Java jako podpisane dane wejściowe o tym samym rozmiarze
a niepodpisany wynik skryptu jest reprezentowany w języku Java jako rozszerzony typ podpisu
(z wyjątkiem przypadku
ulong
).
Więcej przykładowych jąder redukcji
#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]; }
Dodatkowe przykłady kodu
Instrukcja BasicRenderScript, RenderScriptIntrinsic, i Hello Compute W przykładach szczegółowo opisano wykorzystanie interfejsów API opisanych na tej stronie.