Per i carichi di lavoro in cui l'elaborazione GPU è ideale, la migrazione degli script RenderScript a OpenID ES (GLES) consente alle applicazioni scritte in Kotlin, Java o che utilizzano NDK di sfruttare l'hardware GPU. Segue una panoramica generale per aiutarti a utilizzare gli mesh di computing OpenGL ES 3.1 per sostituire gli script RenderScript.
Inizializzazione GLES
Anziché creare un oggetto di contesto RenderScript, segui questi passaggi per creare un contesto GLES fuori schermo utilizzando EGL:
Ottieni il display predefinito
Inizializza EGL utilizzando la visualizzazione predefinita, specificando la versione GLES.
Scegli una configurazione EGL con un tipo di piattaforma
EGL_PBUFFER_BIT
.Utilizza gli elementi di visualizzazione e configurazione per creare un contesto EGL.
Crea la superficie fuori schermo con
eglCreatePBufferSurface
. Se il contesto verrà utilizzato solo per il calcolo, la superficie può essere di dimensioni banali (1 x 1).Crea il thread di rendering e chiama
eglMakeCurrent
nel thread di rendering con il contesto di visualizzazione, superficie e EGL per associare il contesto GL al thread.
L'app di esempio mostra come inizializzare il contesto GLES in
GLSLImageProcessor.kt
.
Per scoprire di più, consulta
EGLSurfaces e OpenGL ES.
Output debug GLES
Per ricevere errori utili da OpenGL, utilizza un'estensione per attivare il logging del debug
che imposta un callback dell'output di debug. Il metodo per eseguire questa operazione dall'SDK, glDebugMessageCallbackKHR
, non è mai stato implementato e genera un'eccezione. L'app di esempio
include un wrapper per il callback dal codice NDK.
Assegnazioni GLES
È possibile eseguire la migrazione di un'allocazione di RenderScript a una texture di archiviazione non modificabile o a un oggetto Shader Storage buffer. Per le immagini di sola lettura, puoi utilizzare un oggetto Sampler, che consente l'applicazione di filtri.
Le risorse GLES sono allocate all'interno di GLES. Per evitare l'overhead di copia durante l'interazione con altri componenti Android, è disponibile un'estensione per KHR Images che consente la condivisione di array 2D di dati immagine. Questa estensione è necessaria per i dispositivi Android
a partire da Android 8.0. La libreria
graphics-core
Android Jetpack
include il supporto per la creazione di queste immagini all'interno di codice gestito e la mappatura
alle HardwareBuffer
allocati:
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]
)!!
}
Sfortunatamente, ciò non crea la texture di archiviazione immutabile necessaria per
uno scheduler di computing per scrivere direttamente nel buffer. L'esempio utilizza
glCopyTexSubImage2D
per copiare la texture di archiviazione utilizzata
da Compute Shaper in KHR Image
. Se il driver OpenGL supporta l'estensione EGL Image Storage, questa estensione può essere utilizzata per creare una texture di archiviazione immutabile condivisa per evitare la copia.
Conversione in mesh di computing GLSL
Gli script RenderScript vengono convertiti in mesh di computing GLSL.
Scrivi uno Shader di computing GLSL
In OpenGL ES,gli handle di calcolo sono scritti in GLSL (OpenGL Shading Language).
Adattamento degli script globali
A seconda delle caratteristiche degli script globali, puoi utilizzare uniformi o oggetti buffer uniformi per elementi globali che non vengono modificati all'interno dello strumento:
- Buffer uniforme: consigliato per elementi globali di script modificati di frequente di dimensioni superiori al limite della costante push.
Per le globali che vengono modificate all'interno dello mesh, puoi utilizzare una texture di archiviazione immutabile o un oggetto Shader Storage buffer.
Esegui calcoli
Gli Shader di calcolo non fanno parte della pipeline grafica, ma sono generici e progettati per calcolare job a elevata parallelizzazione. Ciò ti consente di avere un maggiore controllo sul modo in cui vengono eseguiti, ma significa anche che devi comprendere un po' di più su come il job viene parallelizzato.
Crea e inizializza il programma di computing
La creazione e l'inizializzazione del programma di computing hanno molto in comune con qualsiasi altro meshr GLES.
Crea il programma e il prober di computing associato.
Collega il codice sorgente dello mesh, compila lo strumento (e controlla i risultati della compilazione).
Allega lo Shader, collega il programma e utilizzalo.
Crea, inizializza e associa eventuali uniformi.
Avvia un calcolo
Gli handle di calcolo operano all'interno di uno spazio astratto 1D, 2D o 3D su una serie di gruppi di lavoro definiti all'interno del codice sorgente dello mesh e che rappresentano le dimensioni minime delle chiamate e la geometria dello Shader. Il seguente meshr funziona su un'immagine 2D e definisce i gruppi di lavoro in due dimensioni:
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;
I gruppi di lavoro possono condividere la memoria, definita da GL_MAX_COMPUTE_SHARED_MEMORY_SIZE
, che ha una dimensione di almeno 32 kB e può utilizzare memoryBarrierShared()
per fornire un accesso coerente alla memoria.
Definisci le dimensioni del gruppo di lavoro
Anche se lo spazio dei problemi funziona bene con gruppi di lavoro di dimensioni pari a 1, è importante impostare una dimensione appropriata per il gruppo di lavoro per caricare in contemporanea lo Shader di computing. Ad esempio, se le dimensioni sono troppo ridotte, il driver GPU potrebbe non caricare in contemporanea il calcolo in modo sufficiente. Idealmente, queste dimensioni dovrebbero essere ottimizzate per ogni GPU, anche se i valori predefiniti ragionevoli funzionano abbastanza bene sui dispositivi attuali, ad esempio una dimensione del gruppo di lavoro di 8 x 8 nello snippet dello mesh.
Il valore GL_MAX_COMPUTE_WORK_GROUP_COUNT
è elevato; in base alle specifiche, deve essere
almeno 65535 in tutti e tre gli assi.
Invia lo ombreggiatore
Il passaggio finale nell'esecuzione dei calcoli consiste nell'inviare lo meshr utilizzando una delle funzioni di invio, ad esempio glDispatchCompute
. La funzione di invio è responsabile di impostare il numero di gruppi di lavoro per ogni asse:
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
)
Per restituire il valore, attendi prima il completamento dell'operazione di calcolo utilizzando una barriera di memoria:
GLES31.glMemoryBarrier(GLES31.GL_SHADER_IMAGE_ACCESS_BARRIER_BIT)
Per concatenare più kernel (ad esempio per la migrazione del codice utilizzando ScriptGroup
), crea ed esegui più programmi e sincronizza il loro accesso all'output mediante barriere di memoria.
L'app di esempio mostra due attività di computing:
- Rotazione HUE: un'attività di calcolo con un singolo mesh di computing. Vedi
GLSLImageProcessor::rotateHue
per l'esempio di codice. - Sfocatura: un'attività di calcolo più complessa che esegue in sequenza due Shader di calcolo. Visita la pagina
GLSLImageProcessor::blur
per l'esempio di codice.
Per scoprire di più sulle barriere di memoria, consulta Assicurare la visibilità e Variabili condivise.