Skripts zu OpenGL ES 3.1 migrieren

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:

  1. Standarddisplay verwenden

  2. Initialisieren Sie EGL über die Standardanzeige und geben Sie die GLES-Version an.

  3. Wählen Sie eine EGL-Konfiguration mit dem Oberflächentyp EGL_PBUFFER_BIT

  4. Verwenden Sie „display“ und „config“, um einen EGL-Kontext zu erstellen.

  5. Erstellen Sie die Offscreen-Oberfläche mit eglCreatePBufferSurface Wenn der Kontext nur für Computing-Ressourcen verwendet wird, Oberfläche.

  6. 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:

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.

  1. Erstellen Sie das Programm und den zugehörigen Compute-Shader.

  2. Hängen Sie die Shader-Quelle an, kompilieren Sie den Shader und prüfen Sie die Ergebnisse der Zusammenstellung).

  3. Hängen Sie den Shader an, verknüpfen Sie das Programm und verwenden Sie es.

  4. 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 .