Für Arbeitslasten, bei denen GPU-Computing ideal ist, werden RenderScript-Skripts OpenGL ES (GLES) ermöglicht Anwendungen, die in Kotlin, Java oder NDK geschrieben wurden damit Sie die Vorteile von GPU-Hardware nutzen können. Es folgt eine allgemeine Übersicht, die Ihnen bei der Verwendung von OpenGL ES 3.1-Rechen-Shadern hilft, RenderScript-Skripte ersetzen.
GLES-Initialisierung
Anstatt ein RenderScript-Kontextobjekt zu erstellen, führen Sie die folgenden Schritte aus: um mit EGL einen GLES-Offscreen-Kontext zu erstellen:
Standarddisplay verwenden
Initialisieren Sie EGL über die Standardanzeige und geben Sie die GLES-Version an.
Wählen Sie eine EGL-Konfiguration mit dem Oberflächentyp
EGL_PBUFFER_BIT
Verwenden Sie „display“ und „config“, um einen EGL-Kontext zu erstellen.
Erstellen Sie die Offscreen-Oberfläche mit
eglCreatePBufferSurface
Wenn der Kontext nur für Computing-Ressourcen verwendet wird, Oberfläche.Erstellen Sie den Rendering-Thread und rufen Sie
eglMakeCurrent
im Renderingthread mit den Optionen „display“, „oberfläche“ und und EGL-Kontext, um den GL-Kontext an den Thread zu binden.
Die Beispiel-App zeigt, wie der GLES-Kontext in
GLSLImageProcessor.kt
Weitere Informationen finden Sie unter
EGLSurfaces und OpenGL ES
GLES-Debug-Ausgabe
Wenn du nützliche Fehler von OpenGL erhältst, wird eine Erweiterung verwendet, um die Debugging-Protokollierung zu aktivieren
der einen Callback für die Ausgabe der Fehlerbehebung festlegt. Die Methode dafür über das SDK,
glDebugMessageCallbackKHR
, wurde nie implementiert und es wird ein
Ausnahme. Die Beispiel-App
enthält einen Wrapper für den Callback vom NDK-Code.
GLES-Zuweisungen
Eine RenderScript-Zuordnung kann zu einer Unveränderliche Speichertextur oder Shader Storage Buffer Object. Bei schreibgeschützten Bildern ein Sampler-Objekt verwenden, das eine Filterung.
GLES-Ressourcen werden innerhalb von GLES zugewiesen. Um das Kopieren von Erinnerungen zu vermeiden
bei der Interaktion mit anderen Android-Komponenten
für KHR-Images, die das Teilen
aus 2D-Arrays von Bilddaten. Diese Erweiterung wurde für Android-Geräte erforderlich
ab Android 8.0. Die
Grafikkern
Android Jetpack-Bibliothek
beinhaltet Unterstützung für die Erstellung dieser Images in verwaltetem Code und Mapping
sie einem zugewiesenen HardwareBuffer
zu:
val outputBuffers = Array(numberOfOutputImages) {
HardwareBuffer.create(
width, height, HardwareBuffer.RGBA_8888, 1,
HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
)
}
val outputEGLImages = Array(numberOfOutputImages) { i ->
androidx.opengl.EGLExt.eglCreateImageFromHardwareBuffer(
display,
outputBuffers[i]
)!!
}
Leider wird dadurch nicht die unveränderliche Speichertextur erstellt, die für
einen Compute-Shader zum direkten Schreiben in den Zwischenspeicher. Im Beispiel werden
glCopyTexSubImage2D
zum Kopieren der verwendeten Speichertextur
vom Compute-Shader in KHR Image
übergeben. Falls der OpenGL-Treiber die
EGL Image Storage erweitert,
kann verwendet werden, um eine gemeinsame unveränderliche Speichertextur zu erstellen, um das Kopieren zu vermeiden.
Konvertierung in GLSL-Compute-Shader
Ihre RenderScript-Skripts werden in GLSL-Compute-Shader konvertiert.
GLSL-Compute-Shader schreiben
In OpenGL ES werden Rechen-Shader in der OpenGL-Schattierungssprache (GLSL).
Anpassung von globalen Skripten
Basierend auf den Eigenschaften des globalen Skriptes kannst du entweder Uniformen verwenden oder Uniform buffer-Objekte für globale Objekte, die nicht innerhalb des Shaders geändert werden:
- Einheitlicher Zwischenspeicher: Empfohlen für häufig geänderte globale Scripts, deren Größe größer als die konstanten Grenzwert zu setzen.
Für globale Werte, die innerhalb des Shaders geändert werden, können Sie einen Unveränderliche Speichertextur oder Shader Storage Buffer Object.
Berechnungen ausführen
Compute-Shader sind nicht Teil der Grafikpipeline. Sie sind für allgemeine Zwecke und zum Berechnen hoch parallelisierbarer Jobs konzipiert. So haben Sie die Möglichkeit, haben mehr Kontrolle über ihre Ausführung, aber es bedeutet auch, wie Ihr Job parallelisiert wird.
Computing-Programm erstellen und initialisieren
Das Erstellen und Initialisieren des Computing-Programms mit einem anderen GLES-Shader zu arbeiten.
Erstellen Sie das Programm und den zugehörigen Compute-Shader.
Hängen Sie die Shader-Quelle an, kompilieren Sie den Shader und prüfen Sie die Ergebnisse der Zusammenstellung).
Hängen Sie den Shader an, verknüpfen Sie das Programm und verwenden Sie es.
Erstelle, initialisieren und binde alle Uniformen.
Berechnung starten
Rechen-Shader arbeiten in einem abstrakten 1D-, 2D- oder 3D-Raum in einer Reihe von Arbeitsgruppen, die im Shader-Quellcode definiert sind und die für die Mindestaufrufgröße sowie die Geometrie des Shaders. Der folgende Shader arbeitet an einem 2D-Bild und definiert die Arbeitsgruppen in zwei Dimensionen:
private const val WORKGROUP_SIZE_X = 8
private const val WORKGROUP_SIZE_Y = 8
private const val ROTATION_MATRIX_SHADER =
"""#version 310 es
layout (local_size_x = $WORKGROUP_SIZE_X, local_size_y = $WORKGROUP_SIZE_Y, local_size_z = 1) in;
Arbeitsgruppen können den von GL_MAX_COMPUTE_SHARED_MEMORY_SIZE
definierten Arbeitsspeicher teilen.
die mindestens 32 KB groß ist und memoryBarrierShared()
kohärenten Speicherzugriff.
Größe der Arbeitsgruppe definieren
Auch wenn Ihr Problembereich gut mit Arbeitsgruppengrößen von 1 funktioniert, sollten Sie dass die richtige Arbeitsgruppengröße für die Parallelisierung des Compute-Shaders wichtig ist. Wenn die Größe zu klein ist, kann der GPU-Treiber Ihre Berechnung möglicherweise nicht parallelisieren. ausreichend. Idealerweise sollten diese Größen pro GPU angepasst werden, vernünftige Standardeinstellungen gut genug auf aktuellen Geräten, wie z. B. der Arbeitsgruppe, funktionieren. 8 x 8 im Shader-Snippet ein.
Es gibt ein GL_MAX_COMPUTE_WORK_GROUP_COUNT
, aber es ist erheblich. das muss sein
gemäß der Spezifikation mindestens 65.535 in allen drei Achsen an.
Shader senden
Der letzte Schritt beim Ausführen der Berechnungen besteht darin, den Shader mit einem
der Weiterleitungsfunktionen wie
glDispatchCompute
Die Weiterleitungsfunktion ist für
zum Einstellen der Anzahl der Arbeitsgruppen für jede Achse:
GLES31.glDispatchCompute(
roundUp(inputImage.width, WORKGROUP_SIZE_X),
roundUp(inputImage.height, WORKGROUP_SIZE_Y),
1 // Z workgroup size. 1 == only one z level, which indicates a 2D kernel
)
Warten Sie zum Zurückgeben des Werts, bis der Rechenvorgang beendet ist. Verwenden Sie dazu eine Speicherbarriere:
GLES31.glMemoryBarrier(GLES31.GL_SHADER_IMAGE_ACCESS_BARRIER_BIT)
Um mehrere Kernel miteinander zu verketten,
(z. B. um Code mit ScriptGroup
zu migrieren), erstellen und weiterleiten
und synchronisieren ihren Zugriff auf die Ausgabe mit dem Arbeitsspeicher,
und Hindernisse zu beseitigen.
Die Beispiel-App zeigt zwei Computing-Aufgaben:
- HUE-Rotation: Eine Rechenaufgabe mit einem einzelnen Rechen-Shader. Weitere Informationen finden Sie unter
GLSLImageProcessor::rotateHue
für das Codebeispiel. - Unkenntlichmachung: Eine komplexere Rechenaufgabe, die nacheinander zwei Rechenvorgänge ausführt
Shader. Das Codebeispiel finden Sie unter
GLSLImageProcessor::blur
.
Weitere Informationen zu Gedächtnisbarrieren finden Sie unter Sichtbarkeit sicherstellen sowie Gemeinsam genutzte Variablen .