RenderScript ist ein Framework zum Ausführen rechenintensiver Aufgaben bei hoher Leistung auf Android RenderScript ist primär für die datenparallele Berechnung geeignet, obwohl serielle von Arbeitslasten profitieren. Die RenderScript-Laufzeit parallelisiert die Arbeit auf allen auf einem Gerät verfügbaren Prozessoren, z. B. auf Multi-Core-CPUs und GPUs. So können Sie sich darauf konzentrieren, Algorithmen auszudrücken, anstatt Arbeit zu planen. RenderScript ist besonders nützlich für Anwendungen, die Bildverarbeitung, computergestützte Fotografie oder maschinelles Sehen.
Zu Beginn sollten Sie sich mit zwei wichtigen Konzepten von RenderScript vertraut machen:
- Die Sprache selbst ist eine von C99 abgeleitete Sprache zum Schreiben von Hochleistungs-Computing. Code. Im Artikel RenderScript-Kernel schreiben wird beschrieben, wie Sie damit Compute-Kernel schreiben.
- Mit der control API wird die Lebensdauer von RenderScript-Ressourcen verwaltet. und die Kernel-Ausführung gesteuert. Es ist in drei verschiedenen Sprachen verfügbar: Java, C++ für Android. NDK und die von C99 abgeleitete Kernelsprache selbst. RenderScript aus Java-Code verwenden und Single-Source RenderScript beschreiben das erste und das dritte Optionen.
RenderScript-Kernel schreiben
Ein RenderScript-Kernel befindet sich normalerweise in einer .rs
-Datei im
<project_root>/src/rs
-Verzeichnis; wird jede .rs
-Datei als
script. Jedes Script enthält eigene Kernel, Funktionen und Variablen. Ein Script kann Folgendes enthalten:
- Eine Pragma-Deklaration (
#pragma version(1)
), in der die Version der In diesem Skript verwendete RenderScript-Kernelsprache. Derzeit ist „1“ der einzige gültige Wert. - Eine Pragma-Deklaration (
#pragma rs java_package_name(com.example.app)
), die den Paketnamen der Java-Klassen angibt, die aus diesem Script gespiegelt werden. Die Datei.rs
muss Teil des Anwendungspakets sein und darf nicht in einem Bibliotheksprojekts an. - Null oder mehr aufrufbare Funktionen. Eine aufrufbare Funktion ist eine einzeilige RenderScript-Funktion, die Sie mit beliebigen Argumenten aus Ihrem Java-Code aufrufen können. Diese sind häufig nützlich für Ersteinrichtung oder serielle Berechnungen in einer größeren Verarbeitungspipeline auszuführen.
Null oder mehr globale Scripts. Eine Script-Globale Variable ähnelt einer globalen Variablen in C. Sie können über Java-Code auf Script-Globale zugreifen. Diese werden häufig für die Parameterübergabe an RenderScript-Kernel verwendet. Hier finden Sie weitere Informationen zu Script-Globalen.
Null oder mehr Compute-Kernel. Ein Compute-Kernel ist eine Funktion oder eine Sammlung von Funktionen, die die RenderScript-Laufzeit parallel ausführen soll in einer Sammlung von Daten. Es gibt zwei Arten von Compute-Kerneln: Mapping-Kernel (auch foreach-Kernel genannt) und Reduktion-Kernel.
Ein Zuordnungs-Kernel ist eine parallele Funktion, die mit einer Sammlung von
Allocations
derselben Dimensionen arbeitet. Standardmäßig wird er einmal für jede Koordinate in diesen Dimensionen ausgeführt. Sie wird normalerweise (aber nicht ausschließlich) verwendet, um eine Sammlung von Eingabe-Allocations
in ein AusgabeAllocation
einElement
nach dem .Hier ist ein Beispiel für einen einfachen zuordnenden Kernel:
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; }
Dies entspricht größtenteils einem Standard-C- . Die Eigenschaft
RS_KERNEL
, die auf den Funktion „Prototype“ gibt an, dass die Funktion ein RenderScript-Mapping-Kernel anstelle eines die unveränderliche Funktion verwenden. Das Argumentin
wird basierend auf dem Parameter EingabeAllocation
, die an den Kernel-Start übergeben wurde. Die Die Argumentex
undy
sind wie unten beschrieben. Der vom Kernel zurückgegebene Wert ist automatisch an die entsprechende Stelle im Ausgabe-Allocation
geschrieben. Standardmäßig wird dieser Kernel über die gesamte EingabeAllocation
ausgeführt, wobei die Kernelfunktion einmal proElement
in derAllocation
ausgeführt wird.Ein Zuordnungs-Kernel kann eine oder mehrere Eingabe-
Allocations
, eine einzelne Ausgabe-Allocation
oder beides haben. Die RenderScript-Laufzeitprüfungen, um sicherzustellen, dass alle Ein- und Ausgabezuweisungen dieselben sind und dass dieElement
-Typen der Ein- und Ausgabe Zuweisungen stimmen mit dem Prototyp des Kernels überein. Schlägt eine dieser Prüfungen fehl, löst eine Ausnahme aus.HINWEIS:Vor Android 6.0 (API-Level 23) kann ein Zuordnungs-Kernel nicht mehr als eine Eingabe-
Allocation
haben.Wenn Sie mehr Eingabe- oder Ausgabe-
Allocations
benötigen als der Kernel hat, sollten diese Objekte anrs_allocation
-Script-Globale gebunden und überrsGetElementAt_type()
oderrsSetElementAt_type()
von einem Kernel oder einer aufrufbaren Funktion aus aufgerufen werden.HINWEIS:
RS_KERNEL
ist ein Makro. automatisch von RenderScript definiert wird:#define RS_KERNEL __attribute__((kernel))
Ein Reduktionskernel besteht aus einer Familie von Funktionen, die auf einer Sammlung von Eingaben ausgeführt werden.
Allocations
derselben Abmessungen. Standardmäßig wird die Akkumulierungsfunktion einmal für jede Koordinate in diesen Dimensionen ausgeführt. Sie wird normalerweise (aber nicht ausschließlich) dazu verwendet, eine Sammlung von Eingabe-Allocations
in eine einzelne Wert.Hier ist ein Beispiel für einen einfachen Reduktionskern, der die
Elements
seiner Eingabe addiert:#pragma rs reduce(addint) accumulator(addintAccum) static void addintAccum(int *accum, int val) { *accum += val; }
Ein Reduktionskernel besteht aus einer oder mehreren vom Nutzer geschriebenen Funktionen. Mit
#pragma rs reduce
wird der Kernel durch Angabe seines Namens definiert (in diesem Beispieladdint
) sowie die Namen und Rollen der Funktionen, (dieaccumulator
-FunktionaddintAccum
, in dieser ) Alle diese Funktionen müssenstatic
sein. Für einen Reduktionskernel ist immer eineaccumulator
-Funktion erforderlich. Je nach gewünschter Funktion kann er auch andere Funktionen haben.Eine Akkumulatorfunktion für den Reduktionskern muss
void
zurückgeben und mindestens zwei Argumente haben. Das erste Argument (in diesem Beispielaccum
) ist ein Verweis auf ein Akkumulatordatenelement und das zweite (in diesem Beispielval
) wird automatisch basierend auf der EingabeAllocation
ausgefüllt, die an den Kernelstart übergeben wird. Das Akkumulator-Datenelement wird von der RenderScript-Laufzeit erstellt und standardmäßig auf null initialisiert. Standardmäßig wird dieser Kernel über die gesamte EingabeAllocation
, mit einer Ausführung der Akkumulatorfunktion proElement
inAllocation
. Von wird der Endwert des Akkumulator-Datenelements und an Java zurückgegeben. Die RenderScript-Laufzeit prüft, ob derElement
-Typ der Eingabezuweisung mit dem Prototyp der Akkumulatorfunktion übereinstimmt. Andernfalls wirft RenderScript eine Ausnahme.Ein Reduktions-Kernel hat eine oder mehrere Eingabe-
Allocations
, aber keine Ausgabe-Allocations
.Reduktions-Kernel werden hier ausführlicher erläutert.
Reduzierungskerne werden ab Android 7.0 (API-Level 24) unterstützt.
Eine Zuordnungs-Kernel-Funktion oder eine Reduktions-Kernel-Akkumulatorfunktion kann auf die Koordinaten zugreifen. der aktuellen Ausführung mit den Sonderargumenten
x
,y
undz
, die vom Typint
oderuint32_t
sein müssen. Diese Argumente sind optional.Eine Zuordnungs-Kernel-Funktion oder ein Reduktions-Kernel-Akkumulator kann auch das optionale Sonderargument
context
vom Typ rs_kernel_context. Sie wird von einer Reihe von Laufzeit-APIs benötigt, mit denen bestimmte Eigenschaften der aktuellen Ausführung abgefragt werden, z. B. rsGetDimX. Das Argumentcontext
ist ab Android 6.0 (API-Level 23) verfügbar.- Eine optionale
init()
-Funktion. Die Funktioninit()
ist eine spezielle Art von aufrufbarer Funktion, die von RenderScript ausgeführt wird, wenn das Script zum ersten Mal instanziiert wird. So können einige Berechnungen beim Erstellen des Scripts automatisch ausgeführt werden. - Null oder mehr statische Skriptglobale und -funktionen. Ein globales statisches Skript entspricht einem
mit dem Unterschied, dass der Zugriff über Java-Code nicht möglich ist. Eine statische Funktion ist eine Standard-C-Funktion, die von jedem Kernel oder jeder aufrufbaren Funktion im Script aufgerufen werden kann, aber nicht für die Java API freigegeben ist. Wenn kein Zugriff auf ein globales Skript oder eine Funktion über Java-Code erforderlich ist,
wird dringend empfohlen, sie als
static
zu deklarieren.
Gleitkommagenauigkeit festlegen
Sie können die erforderliche Gleitkommagenauigkeit in einem Script steuern. Dies ist nützlich, wenn der vollständige IEEE 754-2008-Standard (standardmäßig verwendet) nicht erforderlich ist. Mit den folgenden Pragmas kann eine andere Gleitkommagenauigkeit festgelegt werden:
#pragma rs_fp_full
(Standardwert, wenn nichts angegeben ist): Für Apps, die eine Gleitkommagenauigkeit gemäß dem IEEE 754-2008-Standard erfordern.#pragma rs_fp_relaxed
: Für Apps, die den strengen IEEE 754-2008-Standard nicht erfordern Compliance und können weniger Präzision tolerieren. Dieser Modus ermöglicht eine Leerung auf null für Denorme und gegen Null.#pragma rs_fp_imprecise
: Für Apps, die keine strengen Genauigkeitsanforderungen haben. In diesem Modus werden alle Funktionen inrs_fp_relaxed
aktiviert, Folgendes: <ph type="x-smartling-placeholder">- </ph>
- Bei Vorgängen, die zu -0,0 führen, kann stattdessen +0,0 zurückgegeben werden.
- Vorgänge mit INF und NAN sind nicht definiert.
Die meisten Anwendungen können rs_fp_relaxed
ohne Nebenwirkungen verwenden. Dies kann sehr
vorteilhaft für einige Architekturen aufgrund zusätzlicher Optimierungen, die nur mit gelockerten Abfragen verfügbar sind
Precision (z. B. SIMD-CPU-Anweisungen)
Auf RenderScript APIs aus Java zugreifen
Bei der Entwicklung einer Android-App, die RenderScript verwendet, können Sie von Java in haben Sie zwei Möglichkeiten:
android.renderscript
: Die APIs in diesem Klassenpaket sind verfügbar auf Geräten mit Android 3.0 (API-Level 11) und höher.android.support.v8.renderscript
: Die APIs in diesem Paket sind über eine Supportbibliothek verfügbar. Sie können sie auf Geräten mit Android 2.3 (API-Level 9) und höher verwenden.
Hier sind die Vor- und Nachteile:
- Wenn Sie die APIs der Support Library verwenden, wird der RenderScript-Teil Ihrer Anwendung
kompatibel mit Geräten mit Android 2.3 (API Level 9) und höher, unabhängig davon, welches RenderScript
die Sie verwenden. So kann Ihre Anwendung auf mehr Geräten ausgeführt werden als bei Verwendung der nativen APIs (
android.renderscript
). - Bestimmte RenderScript-Funktionen sind nicht über die APIs der Support Library verfügbar.
- Wenn Sie die Support Library APIs verwenden, erhalten Sie (möglicherweise deutlich) größere APKs als bei Verwendung der nativen APIs (
android.renderscript
).
APIs der RenderScript Support Library verwenden
Um die RenderScript-APIs der Support Library zu verwenden, müssen Sie Ihre Entwicklungskonfiguration um darauf zugreifen zu können. Die folgenden Android SDK-Tools sind zur Verwendung von diesen APIs:
- Android SDK-Tools Version 22.2 oder höher
- Android SDK Build-Tools, Version 18.1.0 oder höher
Ab den Android SDK Build-Tools 24.0.0 wird Android 2.2 (API-Level 8) nicht mehr unterstützt.
Sie können die installierte Version dieser Tools in der Android SDK Manager
So verwenden Sie die RenderScript APIs der Support Library:
- Prüfen Sie, ob die erforderliche Android SDK-Version installiert ist.
- Aktualisieren Sie die Einstellungen für den Android-Buildprozess, um die RenderScript-Einstellungen aufzunehmen:
- Öffnen Sie die Datei
build.gradle
im App-Ordner Ihres Anwendungsmoduls. - Fügen Sie der Datei die folgenden RenderScript-Einstellungen hinzu:
android { compileSdkVersion 33 defaultConfig { minSdkVersion 9 targetSdkVersion 19 renderscriptTargetApi 18 renderscriptSupportModeEnabled true } }
android { compileSdkVersion(33) defaultConfig { minSdkVersion(9) targetSdkVersion(19) renderscriptTargetApi = 18 renderscriptSupportModeEnabled = true } }
Die oben aufgeführten Einstellungen steuern ein bestimmtes Verhalten im Android-Build-Prozess:
renderscriptTargetApi
– Gibt die zu generierende Bytecodeversion an. Wir empfehlen, diesen Wert auf die niedrigste API-Ebene festzulegen, die alle von Ihnen verwendeten Funktionen bereitstellen kann. Legen SierenderscriptSupportModeEnabled
auftrue
fest. Gültige Werte für diese Einstellung sind beliebige Ganzzahlwerte von 11 auf das zuletzt veröffentlichte API-Level. Wenn Ihre SDK-Mindestversion in Ihrem App-Manifest auf einen anderen Wert festgelegt ist, ignoriert und der Zielwert in der Build-Datei wird verwendet, um den SDK-Version.renderscriptSupportModeEnabled
– Gibt an, dass der generierte Bytecode auf eine kompatible Version zurückfallen soll, wenn das Gerät, auf dem er ausgeführt wird, die Zielversion nicht unterstützt.
- Öffnen Sie die Datei
- Fügen Sie in Ihren Anwendungsklassen, die RenderScript verwenden, einen Import für die Klassen der Supportbibliothek hinzu:
RenderScript aus Java- oder Kotlin-Code verwenden
Wenn Sie RenderScript aus Java- oder Kotlin-Code verwenden möchten, müssen Sie die API-Klassen im Paket android.renderscript
oder android.support.v8.renderscript
verwenden. Meiste
Anwendungen folgen demselben grundlegenden Nutzungsmuster:
- RenderScript-Kontext initialisieren Der mit
create(Context)
erstellte KontextRenderScript
sorgt dafür, dass RenderScript verwendet werden kann, und bietet eine -Objekt zur Steuerung der Lebensdauer aller nachfolgenden RenderScript-Objekte. Den Kontext berücksichtigen zu einem Vorgang mit potenziell langer Ausführungszeit, da er Ressourcen auf verschiedenen Hardwarekomponenten, Sie sollte sich überhaupt nicht im kritischen Pfad einer Anwendung befinden möglich. Normalerweise verfügt eine Anwendung immer nur über einen einzigen RenderScript-Kontext. - Erstellen Sie mindestens eine
Allocation
, die an ein Script übergeben werden soll. EinAllocation
ist ein RenderScript-Objekt, das Speicherplatz für eine feste Datenmenge bietet. Kernel in Scripts nehmenAllocation
-Objekte als Eingabe und Ausgabe an. AufAllocation
-Objekte kann in Kerneln überrsGetElementAt_type()
undrsSetElementAt_type()
zugegriffen werden, wenn sie als Script-Globale gebunden sind. MitAllocation
-Objekten können Arrays von Java-Code an RenderScript-Code und umgekehrt übergeben werden.Allocation
-Objekte werden in der Regel mitcreateTyped()
odercreateFromBitmap()
erstellt. - Erstellen Sie alle erforderlichen Skripts. Bei der Verwendung von RenderScript stehen zwei Arten von Scripts zur Verfügung:
- ScriptC: Dies sind die benutzerdefinierten Scripts, die oben unter RenderScript-Kernel schreiben beschrieben wurden. Jedes Script hat eine Java-Klasse, die vom RenderScript-Compiler berücksichtigt wird, um den Zugriff auf das Script über Java-Code zu vereinfachen. Diese Klasse hat den Namen
ScriptC_filename
. Wenn z. B. der Mapping-Kernel oben befanden sich ininvert.rs
und ein RenderScript-Kontext befand sich bereits inmRenderScript
ist, lautet der Java- oder Kotlin-Code zum Instanziieren des Skripts: - ScriptIntrinsic: Dies sind integrierte RenderScript-Kernel für gängige Vorgänge,
wie z. B. den Gaußschen Weichzeichner,
Faltung und Bildeinblendung. Weitere Informationen finden Sie in den Unterklassen von
ScriptIntrinsic
.
- ScriptC: Dies sind die benutzerdefinierten Scripts, die oben unter RenderScript-Kernel schreiben beschrieben wurden. Jedes Script hat eine Java-Klasse, die vom RenderScript-Compiler berücksichtigt wird, um den Zugriff auf das Script über Java-Code zu vereinfachen. Diese Klasse hat den Namen
- Zuweisungen mit Daten füllen. Mit Ausnahme von Zuordnungen, die mit
createFromBitmap()
erstellt wurden, werden Zuordnungen beim Erstellen mit leeren Daten ausgefüllt. Verwenden Sie eine der Kopiermethoden inAllocation
, um eine Zuweisung zu erstellen. Die „Kopie“ sind synchron. - Legen Sie alle erforderlichen globalen Skripte fest. Sie können Globals mithilfe von Methoden in derselben
ScriptC_filename
-Klasse mit dem Namenset_globalname
festlegen. Für Um beispielsweise eineint
-Variable namensthreshold
festzulegen, verwenden Sie die Methode Java-Methodeset_threshold(int)
; und um die einers_allocation
-Variable namenslookup
haben, verwenden Sie die Java-set_lookup(Allocation)
-Methode. Dieset
-Methoden sind asynchron. - Starten Sie die entsprechenden Kernel und aufrufbaren Funktionen.
Methoden zum Starten eines bestimmten Kernels sind in derselben
ScriptC_filename
-Klasse mit Methoden namensforEach_mappingKernelName()
oderreduce_reductionKernelName()
enthalten. Diese Starts sind asynchron. Abhängig von den Argumenten für den Kernel nimmt eine oder mehrere Zuweisungen an, die alle dieselben Dimensionen haben müssen. Standardmäßig wird ein Kernel auf allen Koordinaten in diesen Dimensionen ausgeführt. Wenn Sie einen Kernel auf einer Teilmenge dieser Koordinaten ausführen möchten, übergeben Sie der MethodeforEach
oderreduce
als letztes Argument ein geeignetesScript.LaunchOptions
.Sie können aufrufbare Funktionen mit den
invoke_functionName
-Methoden starten, die in derselbenScriptC_filename
-Klasse enthalten sind. Diese Einführungen sind asynchron. - Daten aus
Allocation
-Objekten und javaFutureType-Objekten abrufen Wenn Sie über Java-Code auf Daten aus einerAllocation
zugreifen möchten, müssen Sie diese Daten mit einer der „copy“-Methoden inAllocation
zurück in Java kopieren. Um das Ergebnis eines Reduktions-Kernels zu erhalten, müssen Sie diejavaFutureType.get()
-Methode verwenden. Die „Kopie“ undget()
sind synchron. - Entfernen Sie den RenderScript-Kontext. Sie können den RenderScript-Kontext zerstören,
mit
destroy()
oder durch Zulassen des RenderScript-Kontexts Objekt für die automatische Speicherbereinigung. Dies führt zur weiteren Verwendung von Objekten, die zu dieser Kontext zum Auslösen einer Ausnahme.
Asynchrones Ausführungsmodell
Die reflektierten Methoden forEach
, invoke
, reduce
und set
sind asynchron. Sie können zu Java zurückkehren, bevor die angeforderte Aktion abgeschlossen ist. Die einzelnen Aktionen werden jedoch in der Reihenfolge ihrer Ausführung serialisiert.
Die Klasse Allocation
stellt „Kopie“ bereit Methoden zum Kopieren von Daten
und aus Zuweisungen. Eine „Kopie“ ist synchron und wird in Bezug auf jegliche
der obigen asynchronen Aktionen, die dieselbe Zuordnung betreffen.
Die reflektierten Klassen javaFutureType bieten
Eine get()
-Methode, um das Ergebnis einer Reduktion zu erhalten. get()
ist synchron und wird bezogen auf die Reduzierung (die asynchron ist) serialisiert.
RenderScript mit einer einzelnen Quelle
Android 7.0 (API-Level 24) führt eine neue Programmierfunktion namens Single-Source-RenderScript ein. Dabei werden Kernel nicht über Java, sondern über das Script gestartet, in dem sie definiert sind. Dieser Ansatz ist derzeit auf Mapping-Kernel beschränkt, die in diesem Abschnitt aus Gründen der Übersichtlichkeit einfach als „Kernel“ bezeichnet werden. Mit dieser neuen Funktion können Sie auch Zuweisungen vom Typ
rs_allocation
direkt im Script erstellen. Es ist jetzt möglich,
Implementierung eines ganzen Algorithmus ausschließlich in einem Skript, selbst wenn mehrere Kernel-Starts erforderlich sind.
Der Vorteil ist zweifach: besser lesbarer Code, da die Implementierung eines Algorithmus in einer Sprache erfolgt, und potenziell schnellerer Code, da bei mehreren Kernelstarts weniger Übergänge zwischen Java und RenderScript erforderlich sind.
In Single-Source-RenderScript schreiben Sie Kernel wie in
Schreiben eines RenderScript-Kernels Sie schreiben dann eine aufrufbare Funktion, die rsForEach()
aufruft, um sie zu starten. Diese API nimmt eine Kernelfunktion als ersten Parameter an, gefolgt von Eingabe- und Ausgabezuweisungen. Eine ähnliche API
rsForEachWithOptions()
verwendet ein zusätzliches Argument des Typs
rs_script_call_t
, mit dem eine Teilmenge der Elemente aus Eingabe- und
Ausgabezuweisungen für die zu verarbeitende Kernel-Funktion
Um die RenderScript-Berechnung zu starten, rufen Sie die aufrufbare Funktion aus Java auf.
Führen Sie die Schritte unter RenderScript aus Java-Code verwenden aus.
Rufen Sie im Schritt Die entsprechenden Kernel starten die aufrufbare Funktion mit invoke_function_name()
auf. Dadurch wird die gesamte Berechnung gestartet, einschließlich des Startens der Kernel.
Zuweisungen sind oft erforderlich, um Zwischenergebnisse von einem Kernelstart an einen anderen zu speichern und weiterzugeben. Sie können sie mit
rsCreateAllocation() auf. Eine nutzerfreundliche Form dieser API ist
rsCreateAllocation_<T><W>(…)
, wobei T der Datentyp für ein
-Element und W ist die Vektorbreite für das Element. Die API akzeptiert die Größen in den Dimensionen X, Y und Z als Argumente. Bei 1D- oder 2D-Zuordnungen kann die Größe für die Dimension Y oder Z weggelassen werden. Mit rsCreateAllocation_uchar4(16384)
wird beispielsweise eine 1D-Zuordnung von 16.384 Elementen erstellt, die alle vom Typ uchar4
sind.
Die Zuweisungen werden automatisch vom System verwaltet. Ich
sie nicht ausdrücklich freigeben oder freigeben. Sie können jedoch rsClearObject(rs_allocation* alloc)
aufrufen, um anzugeben, dass Sie den Handle alloc
für die zugrunde liegende Zuweisung nicht mehr benötigen, damit das System die Ressourcen so früh wie möglich freigeben kann.
Der Abschnitt RenderScript-Kernel schreiben enthält ein Beispiel für einen Kernel, der ein Bild invertiert. Im folgenden Beispiel werden mehrere Effekte auf ein Bild angewendet.
mit Single-Source-RenderScript. Sie enthält einen weiteren Kernel, greyscale
, der einen
Farbbild in Schwarz-Weiß umwandeln. Eine aufrufbare Funktion process()
wendet diese beiden Kernel dann nacheinander auf ein Eingabebild an und erzeugt ein Ausgabebild. Zuweisungen für die Eingabe- und
Die Ausgabe wird als Argumente des Typs übergeben.
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); }
So rufen Sie die Funktion process()
in Java oder Kotlin auf:
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);
In diesem Beispiel wird gezeigt, wie ein Algorithmus mit zwei Kernelausführungen vollständig in der RenderScript-Sprache implementiert werden kann. Ohne Single-Source-RenderScript müssten Sie beide Kernel aus dem Java-Code starten, was die Kernelstarts von den Kerneldefinitionen trennt und das Verständnis des gesamten Algorithmus erschwert. Der Single-Source-RenderScript-Code ist nicht nur leichter zu lesen, sondern es entfällt auch die Umstellung zwischen Java und dem Script bei Kernelstarts. Einige iterative Algorithmen können Kernel hunderte Male starten, was den Overhead solcher Übergänge erheblich erhöht.
Script-Globale Variablen
Ein script global ist eine gewöhnliche Nicht-static
-
globale Variable in einer Skriptdatei (.rs
). Für ein Skript
mit dem Namen var definiert.
filename.rs
enthält, wird ein
get_var
in den
Klasse ScriptC_filename
. Sofern die globalen
const
ist, wird auch ein
set_var
-Methode.
Ein bestimmtes globales Skript hat zwei separate Werte: ein Java- und einem script-Wert. Diese Werte haben folgende Auswirkungen:
- Wenn die var-Variable einen statischen Initialisierer im Skript hat, gibt sie gibt den Anfangswert von var sowohl in Java als auch im . Andernfalls ist dieser Anfangswert null.
- Zugriff auf var innerhalb des Skripts, Lese- und Schreibzugriff Script-Wert.
- Die Methode
get_var
liest die Java- Wert. - Die Methode
set_var
(sofern vorhanden) schreibt den Java-Wert sofort und schreibt den Skriptwert. asynchron erfolgen.
HINWEIS:Dies bedeutet, dass mit Ausnahme von mit dem statischen Initialisierer im Skript die Werte in einen globalen in einem Skript sind für Java nicht sichtbar.
Tiefenreduktionskerne
Bei der Reduzierung werden mehrere Daten zu einem einzelnen Wert kombiniert. Dies ist ein nützliches Primitiv bei der parallelen Programmierung, bei dem Anwendungen wie die Folgendes:
- Berechnen der Summe oder des Produkts über alle Daten
- Logische Vorgänge berechnen (
and
,or
,xor
) für alle Daten - den Mindest- oder Maximalwert in den Daten ermitteln
- nach einem bestimmten Wert oder nach der Koordinate eines bestimmten Werts in den Daten suchen
Unter Android 7.0 (API-Level 24) und höher unterstützt RenderScript Reduktions-Kernel, um von Nutzenden geschriebene Reduktionsalgorithmen. Sie können Kernel zur Datenreduktion auf Eingaben mit 1, 2 oder 3 Dimensionen anwenden.
Das obige Beispiel zeigt einen einfachen Kernel für die addint-Reduktion.
Hier ist ein etwas komplizierterer findMinAndMax-Reduktionskern, der die Positionen der minimalen und maximalen long
-Werte in einem eindimensionalen Allocation
ermittelt:
#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; }
HINWEIS: Weitere Beispielkerne für die Reduktion finden Sie hier.
Zum Ausführen eines Reduktions-Kernels erstellt die RenderScript-Laufzeit eine oder mehrere
Variablen, die Akkumulatordaten genannt werden
Elemente für den Status des Reduktionsprozesses. Die RenderScript-Laufzeit wählt die Anzahl der Akkumulatordatenelemente so aus, dass die Leistung maximiert wird. Der Typ
der Akkumulator-Datenelemente (accumType) wird durch den Akkumulator des Kernels bestimmt.
Funktion – das erste Argument dieser Funktion ist ein Zeiger auf Akkumulatordaten.
. Standardmäßig wird jedes Akkumulator-Datenelement auf null initialisiert (als
von memset
); Sie können jedoch eine Initialisierungsfunktion schreiben, um
unterscheiden.
Beispiel: Im Kernel addint werden die Akkumulator-Datenelemente (vom Typ int
) verwendet, um Eingabewerte zu addieren. Es gibt keine Initialisierungsfunktion, daher wird jedes Akkumulator-Datenelement mit
Null.
Beispiel:In
den Kernel findMinAndMax, die Akkumulator-Datenelemente
(vom Typ MinAndMax
) werden verwendet, um die Minimal- und Maximalwerte zu verfolgen
die Sie bisher gefunden haben. Es gibt eine Initialisierungsfunktion, mit der diese Werte auf LONG_MAX
bzw. LONG_MIN
festgelegt und die Positionen dieser Werte auf -1 gesetzt werden können, was bedeutet, dass die Werte nicht im (leeren) Teil der verarbeiteten Eingabe vorhanden sind.
RenderScript ruft Ihre Akkumulatorfunktion einmal für jede Koordinate in den Eingaben auf. Normalerweise sollte Ihre Funktion das Akkumulatordatenelement auf irgendeine Weise entsprechend der Eingabe aktualisieren.
Beispiel: Im Kernel addint fügt die Akkumulatorfunktion dem Akkumulatordatenelement den Wert eines Eingabeelements hinzu.
Beispiel: Im findMinAndMax-Kernel prüft die Akkumulatorfunktion, ob der Wert eines Eingabeelements kleiner oder gleich dem im Akkumulatordatenelement aufgezeichneten Mindestwert und/oder größer oder gleich dem im Akkumulatordatenelement aufgezeichneten Maximalwert ist. Anschließend aktualisiert sie das Akkumulatordatenelement entsprechend.
Nachdem die Akkumulatorfunktion für jede Koordinate in der Eingabe einmal aufgerufen wurde, RenderScript muss den Akkumulator kombinieren. Datenelemente zu einem einzigen Akkumulator-Datenelement zusammengefasst. Sie können eine Kombination verwenden. Wenn die Akkumulatorfunktion eine einzelne Eingabe hat keine speziellen Argumente vorhanden sind, müssen Sie keinen Kombinator schreiben. Funktion; RenderScript verwendet die Akkumulator-Funktion, um die Akkumulatordaten zu kombinieren Elemente. (Sie können trotzdem eine Kombinatorfunktion schreiben, wenn dieses Standardverhalten
Beispiel: Im Kernel addint gibt es keine Kombinatorfunktion. Daher wird die Akkumulatorfunktion verwendet. Dies ist das richtige Verhalten, denn wenn wir eine Sammlung von Werten in zwei Teile aufteilen die Werte in diesen beiden Teilen separat addieren, wobei die Addition dieser beiden Summen die gesamte Sammlung addieren.
Beispiel:In
den Kernel findMinAndMax, die Kombinatorfunktion
Prüft, ob der in der Quelle aufgezeichnete Mindestwert Akkumulatordaten
Der Artikel *val
ist kleiner als der unter „Zielort“ erfasste Mindestwert
Akkumulator-Datenelement *accum
und aktualisiert *accum
entsprechend anpassen. Ähnlich funktioniert es für den Höchstwert. Dadurch wird *accum
aktualisiert
den Zustand erhalten, den sie hatten, wenn alle Eingabewerte in
*accum
statt in *accum
und wieder andere in
*val
.
Nachdem alle Akkumulationsdatenelemente kombiniert wurden, ermittelt RenderScript, das Ergebnis der Reduktion, um an Java zurückzukehren. Sie können einen Outkonverter schreiben, verwenden. Sie müssen keine Outkonverter-Funktion schreiben, der Endwert der kombinierten Akkumulator-Datenelemente, der das Ergebnis der Reduktion sein soll.
Beispiel: Im Kernel addint gibt es keine Outconverter-Funktion. Der endgültige Wert der kombinierten Datenelemente ist die Summe aller Elemente der Eingabe. Dies ist der Wert, den wir zurückgeben möchten.
Beispiel: Im findMinAndMax-Kernel initialisiert die Outconverter-Funktion einen int2
-Ergebniswert, um die Positionen des Minimum- und des Maximumwerts zu speichern, die sich aus der Kombination aller Akkumulatordatenelemente ergeben.
Reduktionskernel schreiben
#pragma rs reduce
definiert einen Reduktions-Kernel durch
indem es seinen Namen und die Namen und Rollen der Funktionen angibt,
um den Kernel zu starten. Alle diese Funktionen müssen
static
Ein Reduktions-Kernel erfordert immer einen accumulator
Funktion; können Sie einige oder alle anderen Funktionen auslassen, je nachdem,
zu erledigen.
#pragma rs reduce(kernelName) \ initializer(initializerName) \ accumulator(accumulatorName) \ combiner(combinerName) \ outconverter(outconverterName)
Die Bedeutung der Elemente in #pragma
ist folgende:
reduce(kernelName)
(erforderlich): Gibt an, dass ein Reduktionskern definiert wird. Eine angepasste Java-Methodereduce_kernelName
startet die Kernel.initializer(initializerName)
(optional): Gibt den Namen des Initialisierungsfunktion für diesen Reduktions-Kernel verwendet. Wenn Sie den Kernel starten, ruft RenderScript diese Funktion einmal für jedes Akkumulatordatenelement auf. Die Funktion muss so definiert sein:static void initializerName(accumType *accum) { … }
accum
ist ein Verweis auf ein Akkumulatordatenelement, das von dieser Funktion initialisiert werden soll.Wenn Sie keine Initialisierungsfunktion angeben, initialisiert RenderScript jeden Akkumulator Datenelement auf null setzen (wie durch
memset
) und sich wie ein Initialisierer verhalten. wie folgt aussieht:static void initializerName(accumType *accum) { memset(accum, 0, sizeof(*accum)); }
accumulator(accumulatorName)
(erforderlich): Gibt den Namen der Akkumulatorfunktion für diesen Reduktionskernel an. Beim Starten des Kernels ruft RenderScript diese Funktion einmal für jede Koordinate in den Eingaben, um ein Akkumulator-Datenelement in irgendeiner Weise der Eingabe(n) entsprechen. Die Funktion muss so definiert sein:static void accumulatorName(accumType *accum, in1Type in1, …, inNType inN [, specialArguments]) { … }
accum
ist ein Zeiger auf ein Akkumulator-Datenelement, damit diese Funktion ändern können.in1
bisinN
sind ein oder mehrere Argumente, die basierend auf den Eingaben, die an den Kernel-Start übergeben werden, automatisch ausgefüllt. Ein Argument pro Eingabe. Die Akkumulatorfunktion kann optional eines der besonderen Argumente annehmen.Ein Beispiel für einen Kernel mit mehreren Eingaben ist
dotProduct
.combiner(combinerName)
(Optional): Gibt den Namen der Kombinationsfunktion für diesen Reduktionskern an. Nachdem RenderScript die Akkumulator-Funktion aufgerufen hat, einmal für jede Koordinate in den Eingaben, wird diese Funktion so oft aufgerufen, wie erforderlich, um alle Akkumulator-Datenelemente zu einem einzigen Akkumulator-Datenelement. Die Funktion muss so definiert sein:
static void combinerName(accumType *accum, const accumType *other) { … }
accum
ist ein Verweis auf ein „Ziel“ Akkumulator-Datenelement für diese zu ändern.other
ist ein Verweis auf ein Akkumulator-Datenelement vom Typ „Quelle“, das von dieser Funktion mit*accum
„kombiniert“ werden soll.HINWEIS: Es ist möglich, dass
*accum
,*other
oder beide initialisiert, aber nie an die Akkumulatorfunktion übergeben wurden. Das bedeutet, dass eine oder beide Variablen nie anhand von Eingabedaten aktualisiert wurden. Im Kernel findMinAndMax wird beispielsweise in der KombinationsfunktionfMMCombiner
explizit nachidx < 0
gesucht, da dies auf ein solches Akkumulatordatenelement mit dem Wert INITVAL hinweist.Wenn Sie keine Kombinatorfunktion angeben, verwendet RenderScript die Akkumulator-Funktion in ihrer und sich so verhält, als gäbe es eine Kombinatorfunktion, die so aussieht:
static void combinerName(accumType *accum, const accumType *other) { accumulatorName(accum, *other); }
Eine Kombinationsfunktion ist erforderlich, wenn der Kernel mehr als eine Eingabe hat, wenn der Datentyp der Eingabe nicht mit dem Datentyp des Akkumulators übereinstimmt oder wenn die Akkumulatorfunktion ein oder mehrere besondere Argumente annimmt.
outconverter(outconverterName)
(optional): Gibt den Namen der Outconverter-Funktion für dieses Reduktions-Kernel. Nachdem RenderScript alle Akkus Datenelemente enthält, wird diese Funktion aufgerufen, um das Ergebnis -Reduktion, um zu Java zurückzukehren. Die Funktion muss folgendermaßen definiert werden: dies:static void outconverterName(resultType *result, const accumType *accum) { … }
result
ist ein Verweis auf ein Ergebnisdatenelement, das von der RenderScript-Laufzeit zugewiesen, aber nicht initialisiert wurde. Diese Funktion initialisiert es mit dem Ergebnis der Reduzierung. resultType ist der Typ dieses Datenelements, der nicht mit accumType übereinstimmen muss.accum
ist ein Zeiger auf das endgültige Akkumulator-Datenelement die von der Combiner-Funktion berechnet werden.Wenn Sie keine outconverter-Funktion bereitstellen, kopiert RenderScript den endgültigen Akkumulator mit dem Ergebnisdatenelement verknüpfen und sich so verhalten, als gäbe es eine Outkonverter-Funktion, sieht so aus:
static void outconverterName(accumType *result, const accumType *accum) { *result = *accum; }
Wenn Sie einen anderen Ergebnistyp als den Accumulator-Datentyp benötigen, ist die Outconverter-Funktion obligatorisch.
Ein Kernel hat Eingabetypen, einen Akkumulatordatenelementtyp und einen Ergebnistyp, die nicht unbedingt identisch sein müssen. Im Kernel findMinAndMax sind beispielsweise der Eingabetyp long
, der Typ des Akkumulatordatenelements MinAndMax
und der Ergebnistyp int2
unterschiedlich.
Wovon kannst du nicht ausgehen?
Sie dürfen sich nicht auf die Anzahl der Akkumulatordatenelemente verlassen, die von RenderScript für einen bestimmten Kernelstart erstellt wurden. Es gibt keine Garantie, dass zwei Starts desselben Kernels mit der Dieselbe Eingabe(n) erzeugt die gleiche Anzahl von Akkumulator-Datenelementen.
Sie dürfen sich nicht auf die Reihenfolge verlassen, in der RenderScript den Initialisierer, Akkumulator und Kombinatorfunktionen; werden möglicherweise sogar einige von ihnen gleichzeitig aufgerufen. Es gibt keine Garantie dafür, zwei Starts desselben Kernels mit derselben Eingabe folgen derselben Reihenfolge. Die einzige Garantie ist, dass nur die Initialisiererfunktion einen nicht initialisierten Akkumulator sehen wird Datenelement. Beispiel:
- Es gibt keine Garantie, dass alle Akkumulator-Datenelemente vor dem Akkumulator-Funktion aufgerufen wird, obwohl sie nur für einen initialisierten Akkumulator aufgerufen wird Datenelement.
- Es gibt keine Garantie für die Reihenfolge, in der Eingabeelemente an den Akkumulator übergeben werden .
- Es gibt keine Garantie, dass die Akkumulatorfunktion für alle Eingabeelemente aufgerufen wurde bevor die Kombinatorfunktion aufgerufen wird.
Eine Folge davon ist, dass der findMinAndMax-Kernel nicht deterministisch ist: Wenn die Eingabe mehr als ein Vorkommen desselben Mindest- oder Höchstwerts enthält, können Sie nicht wissen, welches Vorkommen der Kernel findet.
Was müssen Sie garantieren?
Da das RenderScript-System einen Kernel in vielen verwenden, müssen Sie bestimmte Regeln befolgen, damit sich der Kernel auf ganz nach Ihren Wünschen. Wenn Sie diese Regeln nicht einhalten, erhalten Sie möglicherweise falsche Ergebnisse, nicht deterministisches Verhalten oder Laufzeitfehler.
In den folgenden Regeln wird häufig angegeben, dass zwei Akkumulatordatenelemente denselben Wert haben müssen. Was bedeutet das? Das hängt davon ab, was der Kernel tun soll. Für eine mathematische Reduktion wie addint durchzuführen, für „dasselbe“ um mathematische Gleichheit zu bedeuten. Bei einer „beliebigen“ Suche wie findMinAndMax („Speicherort des minimalen und maximalen Eingabewerts ermitteln“), bei der es mehrere Vorkommen identischer Eingabewerte geben kann, müssen alle Speicherorte eines bestimmten Eingabewerts als „identisch“ betrachtet werden. Sie könnten Folgendes schreiben: einen ähnlichen Kernel wie "Find the location of leftmost Minimum- und Maximum Input Values" wobei (z. B.) ein Minimalwert am Standort 100 gegenüber einem identischen Minimalwert am Standort bevorzugt wird 200; für diesen Kernel bedeutet einen identischen Standort, nicht nur identischer Wert und die Akkumulator- und Kombinatorfunktionen müssten die sich von denen für findMinAndMax unterscheiden.
Die Initialisierfunktion muss einen Identitätswert erstellen. Wenn alsoI
und A
Akkumulatordatenelemente sind, die von der Initialisierungsfunktion initialisiert wurden, und I
nie an die Akkumulatorfunktion übergeben wurde (A
aber möglicherweise), gilt:
combinerName(&A, &I)
mussA
nicht änderncombinerName(&I, &A)
muss Behalten Sie fürI
den gleichen Wert wieA
.
Beispiel: Im Kernel addint wird ein Akkumulatordatenelement auf null initialisiert. Die Kombinatorfunktion für diese Kernel führt Additionen aus. Null ist der Identitätswert für die Addition.
Beispiel:Im Feld findMinAndMax
Kernel wird ein Akkumulator-Datenelement initialisiert,
an INITVAL
.
fMMCombiner(&A, &I)
ändertA
nicht, daI
INITVAL
ist.fMMCombiner(&I, &A)
legtI
fest anA
, weilI
gleichINITVAL
ist.
Daher ist INITVAL
tatsächlich ein Identitätswert.
Die Kombinatorfunktion muss kommutativ sein. Das heißt:
wenn A
und B
Akkumulator-Datenelemente sind
der Initialisiererfunktion und möglicherweise
an die Akkumulatorfunktion Null übergeben,
oder öfter, dann muss combinerName(&A, &B)
A
auf denselben Wert festlegen
combinerName(&B, &A)
legt B
fest.
Beispiel:Im addint Kernel hinzufügen, addiert die Kombinatorfunktion die beiden Akkumulator-Datenelementwerte; Addition ist pendeln.
Beispiel: Im Kernel findMinAndMax ist fMMCombiner(&A, &B)
mit A = minmax(A, B)
identisch und da minmax
kommutativ ist, gilt das auch für fMMCombiner
.
Die Kombinatorfunktion muss assoziativ sein. Wenn also A
, B
und C
Akkumulatordatenelemente sind, die von der Initialisierungsfunktion initialisiert wurden und die der Akkumulatorfunktion null oder mehrmals übergeben wurden, müssen die folgenden beiden Codefolgen A
auf denselben Wert setzen:
combinerName(&A, &B); combinerName(&A, &C);
combinerName(&B, &C); combinerName(&A, &B);
Beispiel:Im Kernel addint wird das Objekt Kombinatorfunktion addiert die beiden Akkumulator-Datenelementwerte:
A = A + B A = A + C // Same as // A = (A + B) + C
B = B + C A = A + B // Same as // A = A + (B + C) // B = B + C
Addition ist assoziativ, also auch die Kombinatorfunktion.
Beispiel:Im Kernel 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
ist assoziativ und fMMCombiner
ist es auch.
Die Akkumulatorfunktion und die Kombinationsfunktion müssen zusammen der grundlegenden Funktion
Faltlinie. Das heißt, wenn A
und B
Akkumulator-Datenelemente sind, wurde A
Von der Initialisierungsfunktion initialisiert und möglicherweise an die Akkumulatorfunktion übergeben
null oder öfter, B
wurde nicht initialisiert und args ist
die Liste der Eingabeargumente und speziellen Argumente für einen bestimmten Aufruf an den Akkumulator
verwenden, müssen die folgenden beiden Codesequenzen A
festlegen
auf denselben Wert:
accumulatorName(&A, args); // statement 1
initializerName(&B); // statement 2 accumulatorName(&B, args); // statement 3 combinerName(&A, &B); // statement 4
Beispiel: Im Kernel addint für den Eingabewert V:
- Anweisung 1 ist mit
A += V
identisch - Anweisung 2 ist mit
B = 0
identisch. - Anweisung 3 ist identisch mit
B += V
undB = V
. - Anweisung 4 ist identisch mit
A += B
undA += V
.
In den Anweisungen 1 und 4 wird A
auf denselben Wert gesetzt. Daher entspricht dieser Kernel der grundlegenden Faltregel.
Beispiel:Im Kernel findMinAndMax für eine Eingabe Wert V bei Koordinate X:
- Anweisung 1 ist mit
A = minmax(A, IndexedVal(V, X))
identisch - Anweisung 2 ist mit
B = INITVAL
identisch. - Anweisung 3 ist dasselbe wie
, was da B der Anfangswert ist, dasselbe ist wieB = minmax(B, IndexedVal(V, X))
B = IndexedVal(V, X)
- Aussage 4 ist dasselbe wie
Das ist dasselbe wieA = minmax(A, B)
A = minmax(A, IndexedVal(V, X))
In den Anweisungen 1 und 4 wird A
auf denselben Wert gesetzt. Daher entspricht dieser Kernel der grundlegenden Faltregel.
Einen Reduktionskern aus Java-Code aufrufen
Für einen Reduktionskern namens kernelName, der in der Datei filename.rs
definiert ist, sind in der Klasse ScriptC_filename
drei Methoden enthalten:
// 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);
Hier sind einige Beispiele für den Aufruf des addint-Kernels:
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();
Methode 1 hat ein Allocation
-Eingabeargument für
jedes Eingabeargument im Akkumulator des Kernels
Funktion. Die RenderScript-Laufzeit prüft, ob alle Eingabezuweisungen dieselben Dimensionen haben und ob der Element
-Typ jeder Eingabezuweisung mit dem des entsprechenden Eingabearguments des Prototyps der Akkumulatorfunktion übereinstimmt. Wenn eine dieser Prüfungen fehlschlägt, löst RenderScript eine Ausnahme aus. Der Kernel wird für jede Koordinate in diesen Dimensionen ausgeführt.
Methode 2 entspricht Methode 1, mit der Ausnahme, dass Methode 2 ein zusätzliches Argument sc
annimmt, mit dem die Kernelausführung auf einen Teil der Koordinaten beschränkt werden kann.
Methode 3 entspricht Methode 1, mit der Ausnahme, dass anstelle von Zuweisungseingaben Java-Array-Eingabewerte verwendet werden. So müssen Sie keinen Code schreiben, um eine Zuweisung explizit zu erstellen und Daten aus einem Java-Array darauf zu kopieren. Die Leistung des Codes wird jedoch nicht erhöht, wenn Methode 3 anstelle von Methode 1 verwendet wird. Bei Methode 3 wird für jedes Eingabearray eine temporäre eindimensionale Zuweisung mit dem entsprechenden Element
-Typ und aktivierter setAutoPadding(boolean)
erstellt. Das Array wird dann in die Zuweisung kopiert, als wäre dies mit der entsprechenden copyFrom()
-Methode von Allocation
geschehen. Anschließend wird Methode 1 aufgerufen und die temporären
Zuweisungen.
HINWEIS: Wenn Ihre Anwendung mehrere Kernelaufrufe mit demselben Array oder mit verschiedenen Arrays mit denselben Dimensionen und demselben Elementtyp ausführt, können Sie die Leistung verbessern, indem Sie Allokationen explizit erstellen, befüllen und wiederverwenden, anstatt Methode 3 zu verwenden.
javaFutureType, der Rückgabetyp der reflektierten Reduzierungsmethoden, ist eine reflektierte statische verschachtelte Klasse innerhalb der Klasse ScriptC_filename
. Er stellt das zukünftige Ergebnis einer Reduzierung dar.
Kernel ausführen. Um das tatsächliche Ergebnis der Ausführung zu erhalten, rufen Sie die get()
-Methode dieser Klasse auf. Sie gibt einen Wert vom Typ javaResultType zurück. get()
ist synchron.
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 wird aus dem resultType der outconverter-Funktion ermittelt. Sofern resultType kein signaturloser Typ (Skalar, Vektor oder Array) ist, ist javaResultType der direkt entsprechende Java-Typ. Wenn resultType ein unsignierter Typ und ein größerer Java-signierter Typ ist, ist javaResultType der größere Java-signierte Typ. Andernfalls ist es der direkte entsprechenden Java-Typ. Beispiel:
- Wenn resultType
int
,int2
oderint[15]
ist, gilt Folgendes: dann ist javaResultTypeint
,Int2
, oderint[]
. Alle Werte von resultType können dargestellt werden. durch javaResultType. - Wenn resultType
uint
,uint2
oderuint[15]
ist, gilt Folgendes: dann ist javaResultTypelong
,Long2
. oderlong[]
. Alle Werte von resultType können durch javaResultType dargestellt werden. - Wenn resultType
ulong
,ulong2
oderulong[15]
ist, ist javaResultTypelong
,Long2
oderlong[]
. Es gibt bestimmte Werte für resultType, die nicht mit javaResultType dargestellt werden können.
javaFutureType ist der zukünftige Ergebnistyp entsprechend zum resultType von outconverter .
- Wenn resultType kein Arraytyp ist, ist javaFutureType
result_resultType
. - Wenn resultType ein Array der Länge Count mit Mitgliedern des Typs memberType ist,
ist javaFutureType auf
resultArrayCount_memberType
gesetzt.
Beispiel:
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() { … } } }
Wenn javaResultType ein Objekttyp (einschließlich Arraytyp) ist, wird bei jedem Aufruf
javaFutureType.get()
für dieselbe Instanz,
-Objekt enthält.
Wenn javaResultType nicht alle Werte vom Typ resultType darstellen kann und ein Reduktionskern einen nicht darstellbaren Wert erzeugt, löst javaFutureType.get()
eine Ausnahme aus.
Methode 3 und devecSiInXType
devecSiInXType ist der Java-Typ, der dem inXType des entsprechenden Arguments der Akkumulatorfunktion entspricht. Sofern inXType kein ungezeichenter Typ oder ein Vektortyp ist, ist devecSiInXType der direkt entsprechende Java-Typ. Wenn inXType ein vorzeichenloser skalarer Typ ist, dann ist devecSiInXType der Java-Typ, der direkt dem signierten skalaren Typ desselben Typs entspricht Größe. Wenn inXType ein signierter Vektortyp ist, ist devecSiInXType der Java-Typ, der direkt dem Vektorkomponententyp entspricht. Wenn inXType ein ungezeichner Vektortyp ist, ist devecSiInXType der Java-Typ, der direkt dem signierten Skalartyp mit derselben Größe wie der Vektorkomponententyp entspricht. Beispiel:
- Wenn inXType den Wert
int
hat, dann gilt: devecSiInXType istint
. - Wenn inXType
int2
ist, ist devecSiInXTypeint
. Das Array ist eine flachte Darstellung: Es enthält doppelt so viele skaläre Elemente wie die Zuweisung Vektor-Elemente mit zwei Komponenten. Dies entspricht dencopyFrom()
-Methoden vonAllocation
. - Wenn inXType den Wert
uint
hat, dann gilt: deviceSiInXType istint
. Ein signierter Wert im Java-Array wird als vorzeichenloser Wert mit demselben Bitmuster in der Zuweisung interpretiert. Das funktioniert genauso wie bei dercopyFrom()
Allocation
funktionieren. - Wenn inXType den Wert
uint2
hat, dann gilt: deviceSiInXType istint
. Dies ist eine Kombination aus der Art und Weise, wieint2
unduint
Das Array ist eine vereinfachte Darstellung und vorzeichenbehaftete Java-Array-Werte sind als unsignierte RenderScript-Elementwerte interpretiert.
Bei Methode 3 werden Eingabetypen anders als Ergebnistypen behandelt:
- Die Vektoreingabe eines Scripts wird auf der Java-Seite flachgelegt, das Vektorergebnis eines Scripts jedoch nicht.
- Die nicht signierte Eingabe eines Skripts wird als signierte Eingabe derselben Größe in der Java-Umgebung dargestellt.
während das unsignierte Ergebnis eines Skripts als erweiterter signierter Typ auf der Java-
(außer im Fall von
ulong
).
Weitere Beispiele für Reduktionskerne
#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]; }
Zusätzliche Codebeispiele
BasicRenderScript RenderScriptIntrinsic und Hello Compute in den Beispielen mehr über die Verwendung der auf dieser Seite behandelten APIs.