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 funktionieren mit den auf einem Gerät verfügbaren Prozessoren, z. B. Mehrkern-CPUs und GPUs. Dadurch können Sie sich darauf zu konzentrieren, Algorithmen auszudrücken, anstatt Arbeit zu planen. RenderScript ist besonders nützlich für Anwendungen, die Bildverarbeitung, computergestützte Fotografie oder maschinelles Sehen.
Wenn Sie mit RenderScript beginnen, sollten Sie sich mit zwei Grundkonzepten vertraut machen:
- Die Sprache selbst ist eine von C99 abgeleitete Sprache zum Schreiben von Hochleistungs-Computing. Code. Unter Schreiben eines RenderScript-Kernels wird beschrieben, und wie damit Compute-Kernel geschrieben werden.
- 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 Skript enthält seine eigenen Kernel, Funktionen und Variablen. Ein Skript kann
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 deklariert den Paketnamen der von diesem Skript reflektierten Java-Klassen. Die Datei.rs
muss Teil des Anwendungspakets sein und darf nicht in einem Bibliotheksprojekts an. - Null oder mehr aufrufbare Funktionen. Eine unveränderliche Funktion ist ein Single-Threaded-RenderScript -Funktion, die Sie über Ihren Java-Code mit beliebigen Argumenten 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. Ein globales Skript ähnelt einer globalen Variablen in C. Sie können Globale Zugriffsskripte über Java-Code. Diese werden häufig für die Parameterübergabe an RenderScript verwendet. Kernel. Hier finden Sie weitere Informationen zu globalen Skripts.
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 Computing-Ressourcen Kernel: mapping-Kernel (auch foreach-Kernel genannt) und Reduktionskerne.
Ein Zuordnungs-Kernel ist eine parallele Funktion, die mit einer Sammlung von
Allocations
derselben Dimensionen arbeitet. Standardmäßig führt sie einmal für jede Koordinate in diesen Dimensionen. 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
, mit einer Ausführung der Kernel-Funktion proElement
in derAllocation
.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 Ein- oder Ausgabe-
Allocations
benötigen als des Kernels, sollten diese Objekte anrs_allocation
-globale Skripte gebunden sein und über einen Kernel oder eine aufrufbare Funktion aufgerufen werden, überrsGetElementAt_type()
oderrsSetElementAt_type()
.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 seine Akkumulator-Funktion einmal für jede in diesen Dimensionen Koordinaten angeben. Sie wird normalerweise (aber nicht ausschließlich) dazu verwendet, eine Sammlung von Eingabe-Allocations
in eine einzelne Wert.Hier ist ein Beispiel für eine einfache Reduktion Kernel, der die
Elements
seiner Eingabe:#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. Ein Reduktions-Kernel ist immer erfordert eineaccumulator
-Funktion. kann es auch andere Funktionen haben, je nachdem, was der Kernel tun soll.Eine Akkumulatorfunktion für Reduktionskernel muss
void
zurückgeben und mindestens zwei Argumente. Das erste Argument (in diesem Beispielaccum
) ist ein Zeiger auf ein Akkumulator-Datenelement, und das zweite (in diesem Beispielval
) ist automatisch ausgefüllt anhand der EingabeAllocation
, die an den Start des Kernels. Das Akkumulator-Datenelement wird von der RenderScript-Laufzeit erstellt. von ist er 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 Eingabezuordnung mit dem der Akkumulatorfunktion übereinstimmt Prototyp erstellen; Falls sie nicht übereinstimmen, löst RenderScript eine Ausnahme aus.Ein Reduktions-Kernel hat eine oder mehrere Eingabe-
Allocations
, aber keine Ausgabe-Allocations
.Reduktions-Kernel werden hier ausführlicher erläutert.
Reduktions-Kernel 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. Es wird von einer Familie von Laufzeit-APIs benötigt, die für Abfragen von bestimmte Attribute der aktuellen Ausführung, z. B. rsGetDimX. Das Argumentcontext
ist ab Android 6.0 (API-Level 23) verfügbar.- Eine optionale
init()
-Funktion. Dieinit()
-Funktion ist eine spezielle Art von eine unveränderliche Funktion, die RenderScript bei der ersten Instanziierung des Skripts ausführt. Dies ermöglicht einige automatisch bei der Skripterstellung erfolgen. - 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 ein Standard-C
Funktion, die von jedem Kernel oder jeder Aufruffunktion im Skript aufgerufen werden kann, aber nicht offengelegt wird
mit dem Java-API. 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 Skript festlegen. Dies ist nützlich, wenn Der vollständige IEEE 754-2008-Standard (standardmäßig verwendet) ist nicht erforderlich. Mit den folgenden Pragmas kann ein Gleitkommagenauigkeit:
#pragma rs_fp_full
(Standard, wenn nichts angegeben ist): Für Apps, die einen Gleitkommagenauigkeit gemäß IEEE 754-2008-Standard.#pragma rs_fp_relaxed
: Für Apps, die nicht den strengen IEEE 754-2008-Standard 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 mit geringer Genauigkeit Anforderungen. In diesem Modus werden alle Funktionen inrs_fp_relaxed
aktiviert, Folgendes: <ph type="x-smartling-placeholder">- </ph>
- Vorgänge, die zu -0.0 führen, können stattdessen +0.0 zurückgeben.
- Operationen für INF und NAN sind nicht definiert.
Die meisten Apps können rs_fp_relaxed
ohne Nebenwirkungen verwenden. Dies kann sehr
bei einigen Architekturen vorteilhaft, da zusätzliche Optimierungen nur mit
Precision (z. B. SIMD-CPU-Anweisungen)
Über Java auf RenderScript-APIs 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 einen Support Bibliothek für die Nutzung auf Geräten mit Android 2.3 (API-Level 9) und höher liegen.
Hier sind die Vor- und Nachteile:
- Wenn Sie die APIs der Support Library verwenden, wird der RenderScript-Teil Ihrer Anwendung
ist kompatibel mit Geräten mit Android 2.3 (API-Level 9) und höher, unabhängig davon, welches RenderScript-
die Sie verwenden. Dadurch kann Ihre App auf mehr Geräten ausgeführt werden als mit dem
native APIs (
android.renderscript
) APIs. - Bestimmte RenderScript-Funktionen sind nicht über die APIs der Support Library verfügbar.
- Wenn Sie die APIs der Support Library verwenden, erhalten Sie (möglicherweise erheblich) größere APKs als
wenn Sie die nativen APIs (
android.renderscript
) verwenden.
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
Beachten Sie, dass ab Android SDK Build-tools 24.0.0, Android 2.2 (API-Level 8) wird 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-Build-Prozess so, dass sie die RenderScript-Einstellungen enthalten:
<ph type="x-smartling-placeholder">
- </ph>
- Öffnen Sie die Datei
build.gradle
im App-Ordner Ihres Anwendungsmoduls. - Fügen Sie der Datei die folgenden RenderScript-Einstellungen hinzu:
Cool
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 } }
Die oben aufgeführten Einstellungen steuern ein bestimmtes Verhalten im Android-Build-Prozess:
renderscriptTargetApi
– Gibt die zu verwendende Bytecode-Version an generiert. Wir empfehlen, diesen Wert auf die niedrigste API-Ebene festzulegen, die bereitgestellt werden kann alle von dir verwendeten Funktionen aus und legerenderscriptSupportModeEnabled
fest. antrue
. 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 die generierte Bytecode sollte auf eine kompatible Version zurückgreifen, wenn das Gerät, auf dem es läuft, auf unterstützt die Zielversion nicht.
- Öffnen Sie die Datei
- Fügen Sie Ihren Anwendungsklassen, die RenderScript verwenden, einen Import für die Support Library hinzu.
Klassen:
Kotlin
import android.support.v8.renderscript.*
Java
import android.support.v8.renderscript.*;
RenderScript mit Java- oder Kotlin-Code verwenden
Für die Verwendung von RenderScript aus Java- oder Kotlin-Code sind die API-Klassen im
android.renderscript
- oder android.support.v8.renderscript
-Paket. 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
zur Übergabe an einen . EinAllocation
ist ein RenderScript-Objekt, das für eine feste Datenmenge. Kernel in Skripts benötigenAllocation
-Objekte als Eingabe und Ausgabe verwenden, undAllocation
-Objekte können Zugriff in Kerneln mitrsGetElementAt_type()
undrsSetElementAt_type()
, wenn sie als globale Skripte gebunden sind.Allocation
-Objekte ermöglichen die Übergabe von Arrays vom Java-Code an RenderScript und umgekehrt.Allocation
-Objekte werden normalerweise mitcreateTyped()
odercreateFromBitmap()
. - Erstellen Sie alle erforderlichen Skripts. Es gibt zwei Arten von Skripts
wenn Sie RenderScript verwenden:
<ph type="x-smartling-placeholder">
- </ph>
- ScriptC: Dies sind die benutzerdefinierten Skripts, wie oben im Abschnitt RenderScript-Kernel schreiben beschrieben. Jedes Skript verfügt über eine Java-Klasse.
wird vom RenderScript-Compiler widergespiegelt, um den Zugriff auf das Skript aus dem Java-Code zu erleichtern.
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:Kotlin
val invert = ScriptC_invert(renderScript)
Java
ScriptC_invert invert = new ScriptC_invert(renderScript);
- 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 abgeleiteten Klassen von
ScriptIntrinsic
- ScriptC: Dies sind die benutzerdefinierten Skripts, wie oben im Abschnitt RenderScript-Kernel schreiben beschrieben. Jedes Skript verfügt über eine Java-Klasse.
wird vom RenderScript-Compiler widergespiegelt, um den Zugriff auf das Skript aus dem Java-Code zu erleichtern.
Diese Klasse hat den Namen
- Zuweisungen mit Daten füllen. Mit Ausnahme von Zuweisungen, die mit
createFromBitmap()
erstellt wurden, enthält eine Zuordnung leere Daten, wenn sie erstellt wurde. Um eine Zuweisung zu erstellen, verwenden Sie Methoden inAllocation
. Die „Kopie“ sind synchron. - Legen Sie alle erforderlichen globalen Skripte fest. Sie können globale Werte mithilfe der Methoden in der
dieselbe
ScriptC_filename
-Klasse mit dem Namenset_globalname
. 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. - Die entsprechenden Kernel und Aufruffunktionen starten.
Methoden zum Starten eines bestimmten Kernels in derselben
ScriptC_filename
-Klasse mit Methoden namensforEach_mappingKernelName()
oderreduce_reductionKernelName()
. 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 der Kernel für alle Koordinaten in diesen Dimensionen ausgeführt wird. um einen Kernel für eine Teilmenge dieser Koordinaten auszuführen, ein entsprechendesScript.LaunchOptions
als letztes Argument an die MethodeforEach
oderreduce
übergeben.Aufgerufene Funktionen mit den
invoke_functionName
-Methoden starten in derselbenScriptC_filename
-Klasse wider. Diese Starts sind asynchron. - Daten aus
Allocation
-Objekten abrufen und javaFutureType. Um auf Daten aus einemAllocation
aus Java-Code zugreifen, müssen Sie diese Daten kopieren zurück zu Java mit einer der „copy“- inAllocation
. 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 forEach
, invoke
, reduce
,
und set
sind asynchron. Jede kann vor Abschluss der
die angeforderte Aktion. Die einzelnen Aktionen werden jedoch in der Reihenfolge serialisiert, in der sie gestartet werden.
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 in Bezug auf die (asynchrone) Reduktion serialisiert.
Single Source RenderScript
Mit Android 7.0 (API-Level 24) wird eine neue Programmierfunktion namens Single-Source
RenderScript, bei der Kernel über das Skript gestartet werden, in dem sie definiert sind,
aus Java. Dieser Ansatz ist derzeit auf die Zuordnung von Kerneln beschränkt, die einfach als "Kernel" bezeichnet werden.
der Präzision dieses Abschnitts. Diese neue Funktion unterstützt auch das Erstellen von Zuweisungen vom Typ
<ph type="x-smartling-placeholder"></ph>
rs_allocation
aus dem Skript heraus. Es ist jetzt möglich,
Implementierung eines ganzen Algorithmus ausschließlich innerhalb eines Skripts, selbst wenn mehrere Kernel-Starts erforderlich sind.
Der Vorteil ist zweierlei: besser lesbarer Code, da die Implementierung eines Algorithmus
eine Sprache; und potenziell schnelleren Code, da weniger Übergänge zwischen Java und
RenderScript über mehrere Kernel-Starts hinweg
In Single-Source-RenderScript schreiben Sie Kernel wie in
RenderScript-Kernel schreiben Dann schreiben Sie eine unveränderliche Funktion, die
<ph type="x-smartling-placeholder"></ph>
rsForEach()
, um sie zu starten. Diese API verwendet eine Kernel-Funktion als erste
gefolgt von Eingabe- und Ausgabezuordnungen. Eine ähnliche API
<ph type="x-smartling-placeholder"></ph>
rsForEachWithOptions()
verwendet ein zusätzliches Argument des Typs
<ph type="x-smartling-placeholder"></ph>
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 auf,
die aufgerufene Funktion mit invoke_function_name()
, die den
einschließlich dem Starten von Kerneln.
Zuweisungen werden häufig benötigt, um
Zwischenergebnisse zwischen dem Start eines Kernels und einem anderen. Sie können sie mit
<ph type="x-smartling-placeholder"></ph>
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 nimmt die Größen
die Dimensionen X, Y und Z als Argumente. Bei 1D- oder 2D-Zuweisungen kann die Größe der Dimension Y oder Z
ausgelassen werden. Beispielsweise erstellt rsCreateAllocation_uchar4(16384)
eine 1D-Zuordnung von
16.384 Elemente, von denen jedes den Typ uchar4
hat
Zuweisungen werden automatisch vom System verwaltet. Ich
sie nicht ausdrücklich freigeben oder freigeben. Sie können jedoch
<ph type="x-smartling-placeholder"></ph>
rsClearObject(rs_allocation* alloc)
, wenn du den Alias nicht mehr benötigst
alloc
auf die zugrunde liegende Zuweisung,
damit das System so früh wie möglich Ressourcen freigibt.
Der Abschnitt Writing a RenderScript Kernel enthält ein Beispiel
der ein Image 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. Die aufgerufene Funktion process()
wendet dann diese beiden Kernel an.
nacheinander zu einem Eingabebild und
erzeugt ein Ausgabebild. Zuweisungen für die Eingabe- und
Die Ausgabe wird als Argumente des Typs übergeben.
<ph type="x-smartling-placeholder"></ph>
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:
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);
Dieses Beispiel zeigt, wie ein Algorithmus, der zwei Kernel-Starts umfasst, vollständig implementiert werden kann. in der RenderScript-Sprache selbst. Ohne Single Source RenderScript ist, müssten Sie beide Kernel aus dem Java-Code starten, um die Kernel-Starts voneinander zu trennen. Kernel-Definitionen entfernt, wodurch es schwerer wird, den gesamten Algorithmus zu verstehen. Nicht nur die Single-Source-RenderScript-Code ist einfacher zu lesen und eliminiert Übergänge. zwischen Java und dem Skript über Kernel-Einführungen hinweg. Einige iterative Algorithmen starten möglicherweise Kernel und führt dazu, dass der Aufwand für eine solche Umstellung beträchtlich wird.
Script-Globals
Ein script global ist eine gewöhnliche Nicht-static
-
globale Variable in einer Skriptdatei (.rs
). Für ein Skript
mit dem Namen var definiert.
Datei filename.rs
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 verhalten sich folgendermaßen:
- 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
Reduktion ist der Prozess, bei dem eine Sammlung von Daten in einem Wert. 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 - Ermitteln des Mindest- oder Höchstwerts in den Daten
- Suchen nach einem bestimmten Wert oder der Koordinate eines bestimmten Werts in den Daten
Unter Android 7.0 (API-Level 24) und höher unterstützt RenderScript Reduktions-Kernel, um von Nutzenden geschriebene Reduktionsalgorithmen. Sie können Reduktions-Kernel bei Eingaben mit 1-, 2- oder 3-Dimensionen.
Das obige Beispiel zeigt einen einfachen Kernel für die addint-Reduktion.
Hier ist ein komplizierterer findMinAndMax-Reduktionskernel.
mit der die Positionen der Mindest- und Höchstwerte für long
in einem
Eindimensionales 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; }
HINWEIS:Es gibt weitere Beispiele für Reduzierungen 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. RenderScript-Laufzeit
wählt die Anzahl der Akkumulationsdatenelemente 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.
ein. Standardmäßig wird jedes Akkumulator-Datenelement auf null initialisiert (als
von memset
); Sie können jedoch eine Initialisierungsfunktion schreiben, um
unterscheiden.
Beispiel:Im addint
Kernel werden die Akkumulator-Datenelemente (vom Typ int
) verwendet, um
Werte. 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, um sie auf LONG_MAX
und
bzw. LONG_MIN
; und die Positionen dieser Werte auf -1 setzen, was bedeutet,
die Werte nicht tatsächlich im (leeren) Teil der Eingabe vorhanden sind,
verarbeitet werden.
RenderScript ruft die Akkumulatorfunktion einmal für jede Koordinate im Eingabe(n). In der Regel sollte Ihre Funktion das Akkumulator-Datenelement in irgendeiner Weise aktualisieren entsprechend der Eingabe.
Beispiel:Im addint Kernel addiert die Akkumulatorfunktion den Wert eines Eingabeelements zum Akkumulator Datenelement.
Beispiel:In den Kernel findMinAndMax, die Akkumulatorfunktion Prüft, ob der Wert eines Eingabeelements kleiner oder gleich dem Minimum ist Wert, der im Akkumulator-Datenelement aufgezeichnet wurde und/oder größer oder gleich dem Maximum im Akkumulator-Datenelement erfasst und aktualisiert das Akkumulator-Datenelement entsprechend anpassen.
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 addint gibt es keine Kombinatorfunktion, daher wird die Akkumulator-Funktion 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. Ähnliches gilt für den Maximalwert. 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ückzugeben. 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 Endwert der kombinierten Datenelemente ist die Summe alle Elemente der Eingabe (der Wert, der zurückgegeben werden soll).
Beispiel:In
den Kernel findMinAndMax, die Funktion „outconverter“
initialisiert einen int2
-Ergebniswert, der die Positionen des Minimums enthält und
Höchstwerte, die sich aus der Kombination aller Akkumulator-Datenelemente 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 Elemente im #pragma
haben folgende Bedeutung:
reduce(kernelName)
(erforderlich): Gibt an, dass ein Reduktions-Kernel ist definiert wird. Eine angepasste Java-Methodereduce_kernelName
startet die Kernel.initializer(initializerName)
(optional): Gibt den Namen des Initialisierungsfunktion für diesen Reduktions-Kernel verwendet. Beim Starten des Kernels ruft RenderScript diese Funktion einmal für jedes Akkumulator-Datenelement einmal. Die muss wie folgt definiert werden:static void initializerName(accumType *accum) { … }
accum
ist ein Zeiger auf ein Akkumulator-Datenelement, damit diese Funktion Initialisieren.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)
(Obligatorisch): Gibt den Namen der Akkumulatorfunktion für dieses Attribut an. Reduktions-Kernel. 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 folgendermaßen definiert werden: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 jedes der speziellen Argumente annehmen.Ein Beispiel für einen Kernel mit mehreren Eingaben ist
dotProduct
.combiner(combinerName)
(optional): Gibt den Namen der Kombinatorfunktion für diese Reduktions-Kernel. 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 wie folgt definiert werden:
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 eine „Quelle“ Akkumulator-Datenelement dass diese Funktion „combine“ in*accum
ein.HINWEIS:Es ist möglich,
*accum
,*other
oder beide wurden initialisiert, an die Akkumulatorfunktion übergeben wurden; d. h., mindestens eines davon wurde nie aktualisiert je nach Eingabedaten. Beispiel: den findMinAndMax-Kernel, den Kombinator prüft die FunktionfMMCombiner
explizit aufidx < 0
, zeigt ein solches Akkumulator-Datenelement an, dessen Wert INITVAL ist.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 Kombinatorfunktion ist obligatorisch, wenn der Kernel mehr als eine Eingabe hat, wenn die Eingabedaten Typ nicht mit dem Akkumulator-Datentyp identisch ist, oder wenn die Akkumulatorfunktion Sonderargumente.
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 Zeiger auf ein Ergebnisdatenelement (zugewiesen, aber nicht initialisiert) durch die RenderScript-Laufzeit), damit diese Funktion mit dem Ergebnis des reduzieren. resultType ist der Typ dieses Datenelements, der nicht identisch sein muss. accumType festlegen.accum
ist ein Zeiger auf das endgültige Akkumulator-Datenelement die von der Kombinatorfunktion 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 Akkumulator-Datentyp haben möchten, ist die Outconverter-Funktion obligatorisch.
Beachten Sie, dass ein Kernel über Eingabetypen, einen Akkumulator-Datenelementtyp und einen Ergebnistyp verfügt.
müssen nicht identisch sein. Beispiel:
den findMinAndMax-Kernel, die Eingabe
Typ long
, Akkumulator-Datenelementtyp MinAndMax
und Ergebnis
Typ int2
sind alle unterschiedlich.
Wovon kannst du nicht ausgehen?
Sie dürfen sich nicht auf die Anzahl der Akkumulationsdatenelemente verlassen, die von RenderScript für ein nach dem Start des Kernels. 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 dafür, dass alle Akkumulator-Datenelemente initialisiert werden, bevor 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 Konsequenz daraus ist, dass die Funktion findMinAndMax Kernel ist nicht deterministisch: Wenn die Eingabe mehr als ein Vorkommen des gleichen haben Sie keine Möglichkeit zu wissen, in welchem Fall der Kernel finden.
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 befolgen, erhalten Sie möglicherweise falsche Ergebnisse, nicht deterministisches Verhalten oder Laufzeitfehler.
Die folgenden Regeln besagen häufig, dass zwei Akkumulationsdatenelemente „die gleicher Wert“. 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. Für die Auswahl suchen wie als findMinAndMax („findet den Standort des Minimums und maximale Eingabewerte“), bei denen identische Eingaben mehrmals vorkommen können. müssen alle Positionen eines bestimmten Eingabewerts als "gleich" angesehen 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 Initialisierungsfunktion muss einen Identitätswert erstellen. Das heißt: wennI
und A
Akkumulator-Datenelemente sind
von der Initialisierungsfunktion verwendet und I
wurde nie an den
Akkumulatorfunktion (aber A
gewesen sein kann), dann
<ph type="x-smartling-placeholder">- </ph>
combinerName(&A, &I)
mussA
nicht änderncombinerName(&I, &A)
muss Behalten Sie fürI
den gleichen Wert wieA
.
Beispiel:Im addint Akkumulator-Datenelement auf null initialisiert wird. 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)
lässtA
unverändert, weilI
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
fMMCombiner(&A, &B)
ist identisch mit
"A = minmax(A, B)
" und "minmax
" sind kommutativ, daher
fMMCombiner
auch.
Die Kombinationsfunktion muss assoziativ sein. Das heißt:
wenn A
, B
und C
sind:
Akkumulator-Datenelemente, die von der Initialisierungsfunktion initialisiert wurden, und möglicherweise
kein- oder öfter an die Akkumulatorfunktion übergeben werden, müssen die folgenden beiden Codesequenzen
Setzen Sie A
auf denselben Wert:
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)ist identisch mit
A = minmax(A, B). Die beiden Sequenzen sind also
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
Funktion 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 identisch mit
B = 0
- Anweisung 3 ist identisch mit
B += V
undB = V
. - Anweisung 4 ist identisch mit
A += B
undA += V
.
Die Anweisungen 1 und 4 legen A
auf denselben Wert fest, sodass dieser Kernel den
grundlegende 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 identisch mit
B = INITVAL
- Anweisung 3 ist mit
B = minmax(B, IndexedVal(V, X))
da B der Ausgangswert ist undB = IndexedVal(V, X)
- Anweisung 4 ist dasselbe wie
A = minmax(A, B)
Das ist dasselbe wieA = minmax(A, IndexedVal(V, X))
Die Anweisungen 1 und 4 legen A
auf denselben Wert fest, sodass dieser Kernel den
grundlegende Faltregel.
Reduktions-Kernel über Java-Code aufrufen
Für einen Reduktions-Kernel mit dem Namen kernelName, definiert im
filename.rs
gibt es drei Methoden, die in der
Klasse 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);
Hier sind einige Beispiele für den Aufruf des addint-Kernels:
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();
Methode 1 hat ein Allocation
-Eingabeargument für
jedes Eingabeargument im Akkumulator des Kernels
Funktion. Die RenderScript-Laufzeit prüft, ob alle eingegebenen Zuweisungen
dieselben Abmessungen haben und dass der Element
-Typ
Die Eingabezuweisungen stimmen mit denen des entsprechenden Eingabearguments des Akkumulators überein
den Prototyp einer Funktion. Wenn eine dieser Prüfungen fehlschlägt, löst RenderScript eine Ausnahme aus. Die
Kernel für alle Koordinaten in diesen Dimensionen ausgeführt.
Methode 2 entspricht Methode 1, mit der Ausnahme, dass Methode 2
sc
, das verwendet werden kann, um die Kernel-Ausführung auf eine Teilmenge der
Koordinaten.
Methode 3 entspricht der Methode 1, mit folgenden Ausnahmen:
statt Allocation-Eingaben zu verwenden,
benötigt er Java-Array-Eingaben. Dies ist ein Komfort,
Sie müssen keinen Code schreiben, um eine Zuordnung explizit zu erstellen und Daten dorthin zu kopieren
aus einem Java-Array. Die Verwendung von Methode 3 anstelle von Methode 1 erhöht jedoch nicht die
Leistung des Codes. Für jedes Eingabearray erstellt Methode 3 eine temporäre
Eindimensionale Zuordnung mit dem entsprechenden Element
-Typ und
setAutoPadding(boolean)
aktiviert und kopiert das Array in
Die Zuordnung erfolgt gemäß der entsprechenden copyFrom()
-Methode von Allocation
. Anschließend wird Methode 1 aufgerufen und die temporären
Zuweisungen.
HINWEIS:Wenn Ihre Anwendung mehrere Kernel-Aufrufe mit dasselbe Array oder mit verschiedenen Arrays mit denselben Dimensionen und Elementtyp indem Sie Zuweisungen selbst explizit erstellen, ausfüllen und wiederverwenden, mithilfe von Methode 3.
javaFutureType
der Rückgabetyp der reflektierten Reduktionsmethoden
statisch verschachtelte Klasse innerhalb von 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 Methode get()
dieser Klasse, die einen Wert zurückgibt
javaResultType ausgewählt. get()
ist synchron.
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 wird vom resultType des Objekts outconverter-Funktion. Sofern resultType kein vorzeichenloser Typ (Skalar, Vektor oder Array) 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 dargestellt werden. durch javaResultType. - Wenn resultType
ulong
,ulong2
ist, oderulong[15]
, dann javaResultType istlong
,Long2
oderlong[]
. Es gibt bestimmte Werte, von resultType, die nicht durch javaResultType dargestellt werden können.
javaFutureType ist der zukünftige Ergebnistyp, der dem zum resultType von outconverter .
- Wenn resultType kein Arraytyp ist, dann gilt: javaFutureType
ist
result_resultType
. - Wenn resultType ein Array der Länge Count mit Mitgliedern des Typs memberType ist,
ist javaFutureType auf
resultArrayCount_memberType
gesetzt.
Beispiel:
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() { … } } }
Wenn javaResultType ein Objekttyp (einschließlich Arraytyp) ist, wird bei jedem Aufruf
die Einstellung javaFutureType.get()
für dieselbe Instanz,
-Objekt enthält.
Wenn javaResultType nicht alle Werte des Typs resultType darstellen kann und ein
Reduktionskernel erzeugt
einen nicht repräsentierbaren Wert,
gibt javaFutureType.get()
eine Ausnahme aus.
Methode 3 und devecSiInXType
devecSiInXType ist der Java-Typ für inXType des entsprechenden Arguments von die Akkumulatorfunktion. Sofern inXType kein vorzeichenloser 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, dann ist devecSiInXType der Java-Code -Typ direkt dem Vektorkomponententyp entspricht. Wenn inXType ein nicht signierter Vektortyp an, dann ist devecSiInXType der Java-Typ, der direkt dem vorzeichenbehafteter skalarer Typ mit derselben Größe wie der Vektorkomponententyp. Beispiel:
- Wenn inXType den Wert
int
hat, dann gilt: devecSiInXType istint
. - Wenn inXType den Wert
int2
hat, dann gilt: devecSiInXType istint
. Das Array ist eine abgeflachte Darstellung: Es hat doppelt viele skalare Elemente, da die Zuordnung einen 2-Komponenten-Vektor hat Elemente. Dies entspricht dencopyFrom()
-Methoden vonAllocation
. - Wenn inXType den Wert
uint
hat, dann gilt: deviceSiInXType istint
. Ein vorzeichenbehafteter Wert im Java-Array wird als vorzeichenloser Wert von Bitmuster der Zuordnung verwendet. 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.
Beachten Sie, dass bei Methode 3 die Eingabetypen anders gehandhabt werden. als Ergebnistypen:
- Die Vektoreingabe eines Skripts wird auf der Java-Seite vereinfacht, das Vektorergebnis eines Skripts 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 Reduktionskernel
#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.