RenderScript – Übersicht

<ph type="x-smartling-placeholder">

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 Ausgabe Allocation ein Element 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 Argument in wird basierend auf dem Parameter Eingabe Allocation, die an den Kernel-Start übergeben wurde. Die Die Argumente x und y 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 Eingabe Allocation, mit einer Ausführung der Kernel-Funktion pro Element in der Allocation.

      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 die Element-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 an rs_allocation-globale Skripte gebunden sein und über einen Kernel oder eine aufrufbare Funktion aufgerufen werden, über rsGetElementAt_type() oder rsSetElementAt_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 Beispiel addint) sowie die Namen und Rollen der Funktionen, (die accumulator-Funktion addintAccum, in dieser ) Alle diese Funktionen müssen static sein. Ein Reduktions-Kernel ist immer erfordert eine accumulator-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 Beispiel accum) ist ein Zeiger auf ein Akkumulator-Datenelement, und das zweite (in diesem Beispiel val) ist automatisch ausgefüllt anhand der Eingabe Allocation, 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 Eingabe Allocation, mit einer Ausführung der Akkumulatorfunktion pro Element in Allocation. Von wird der Endwert des Akkumulator-Datenelements und an Java zurückgegeben. Die RenderScript-Laufzeit prüft, ob der Element-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 und z, die vom Typ int oder uint32_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 Argument context ist ab Android 6.0 (API-Level 23) verfügbar.

  • Eine optionale init()-Funktion. Die init()-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 in rs_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:

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:

  1. Prüfen Sie, ob die erforderliche Android SDK-Version installiert ist.
  2. 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 lege renderscriptSupportModeEnabled fest. an true. 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.
  3. 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:

  1. RenderScript-Kontext initialisieren Der mit create(Context) erstellte Kontext RenderScript 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.
  2. Erstellen Sie mindestens eine Allocation zur Übergabe an einen . Ein Allocation ist ein RenderScript-Objekt, das für eine feste Datenmenge. Kernel in Skripts benötigen Allocation -Objekte als Eingabe und Ausgabe verwenden, und Allocation-Objekte können Zugriff in Kerneln mit rsGetElementAt_type() und rsSetElementAt_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 mit createTyped() oder createFromBitmap().
  3. 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 in invert.rs und ein RenderScript-Kontext befand sich bereits in mRenderScript 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
  4. 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 in Allocation. Die „Kopie“ sind synchron.
  5. Legen Sie alle erforderlichen globalen Skripte fest. Sie können globale Werte mithilfe der Methoden in der dieselbe ScriptC_filename-Klasse mit dem Namen set_globalname. Für Um beispielsweise eine int-Variable namens threshold festzulegen, verwenden Sie die Methode Java-Methode set_threshold(int); und um die eine rs_allocation-Variable namens lookup haben, verwenden Sie die Java- set_lookup(Allocation)-Methode. Die set-Methoden sind asynchron.
  6. Die entsprechenden Kernel und Aufruffunktionen starten.

    Methoden zum Starten eines bestimmten Kernels in derselben ScriptC_filename-Klasse mit Methoden namens forEach_mappingKernelName() oder reduce_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 entsprechendes Script.LaunchOptions als letztes Argument an die Methode forEach oder reduce übergeben.

    Aufgerufene Funktionen mit den invoke_functionName-Methoden starten in derselben ScriptC_filename-Klasse wider. Diese Starts sind asynchron.

  7. Daten aus Allocation-Objekten abrufen und javaFutureType. Um auf Daten aus einem Allocation aus Java-Code zugreifen, müssen Sie diese Daten kopieren zurück zu Java mit einer der „copy“- in Allocation. Um das Ergebnis eines Reduktions-Kernels zu erhalten, müssen Sie die javaFutureType.get()-Methode verwenden. Die „Kopie“ und get() sind synchron.
  8. 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-Methode reduce_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 bis inN 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 Funktion fMMCombiner explizit auf idx < 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: wenn I 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) muss A nicht ändern
  • combinerName(&I, &A) muss Behalten Sie für I den gleichen Wert wie A.

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ässt A unverändert, weil I INITVAL ist.
  • fMMCombiner(&I, &A) legt I fest an A, weil I gleich INITVAL 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 und B = V.
  • Anweisung 4 ist identisch mit A += B und A += 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 und
    B = IndexedVal(V, X)
    
  • Anweisung 4 ist dasselbe wie
    A = minmax(A, B)
    
    Das ist dasselbe wie
    A = 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 oder int[15] ist, gilt Folgendes: dann ist javaResultType int, Int2, oder int[]. Alle Werte von resultType können dargestellt werden. durch javaResultType.
  • Wenn resultType uint, uint2 oder uint[15] ist, gilt Folgendes: dann ist javaResultType long, Long2. oder long[]. Alle Werte von resultType können dargestellt werden. durch javaResultType.
  • Wenn resultType ulong, ulong2 ist, oder ulong[15], dann javaResultType ist long, Long2 oder long[]. 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 ist int.
  • Wenn inXType den Wert int2 hat, dann gilt: devecSiInXType ist int. Das Array ist eine abgeflachte Darstellung: Es hat doppelt viele skalare Elemente, da die Zuordnung einen 2-Komponenten-Vektor hat Elemente. Dies entspricht den copyFrom()-Methoden von Allocation.
  • Wenn inXType den Wert uint hat, dann gilt: deviceSiInXType ist int. Ein vorzeichenbehafteter Wert im Java-Array wird als vorzeichenloser Wert von Bitmuster der Zuordnung verwendet. Das funktioniert genauso wie bei der copyFrom() Allocation funktionieren.
  • Wenn inXType den Wert uint2 hat, dann gilt: deviceSiInXType ist int. Dies ist eine Kombination aus der Art und Weise, wie int2 und uint 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.