Esegui la migrazione degli script a OpenGL ES 3.1

Per i carichi di lavoro in cui il calcolo GPU è ideale, esegui la migrazione degli script RenderScript a OpenGL ES (GLES) consente applicazioni scritte in Kotlin, Java o con l'NDK per sfruttare l'hardware della GPU. Di seguito è riportata una panoramica generale che ti aiuterà a utilizzare gli strumenti 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:

  1. Imposta la visualizzazione predefinita

  2. Inizializza EGL utilizzando la visualizzazione predefinita, specificando la versione GLES.

  3. Scegli una configurazione EGL con un tipo di superficie EGL_PBUFFER_BIT

  4. Utilizza Display and Config per creare un contesto EGL.

  5. Crea la superficie fuori schermo con eglCreatePBufferSurface Se il contesto verrà utilizzato solo per il calcolo, si tratta di un modello banalmente piccolo (1x1) superficie.

  6. Crea il thread di rendering e la chiamata eglMakeCurrent nel thread di rendering con il display, la piattaforma ed EGL per associare il contesto GL al thread.

L'app di esempio mostra come inizializzare il contesto GLES in GLSLImageProcessor.kt Per saperne di più, vedi EGLSurfaces e OpenGL ES.

Output debug GLES

Per ricevere errori utili da OpenGL viene utilizzata un'estensione per attivare il logging di debug che imposta un callback di 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.

Allocazioni GLES

È possibile eseguire la migrazione di un'allocazione di RenderScript texture di archiviazione immutabile o una Oggetto buffer di archiviazione Shader. Per le immagini di sola lettura, puoi utilizzare un Oggetto Sampler, che consente dei filtri.

Le risorse GLES vengono allocate all'interno di GLES. Per evitare di copiare la memoria quando si interagisce con altri componenti Android, c'è 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 grafica-core Libreria Android Jetpack include il supporto per la creazione di queste immagini all'interno del codice gestito e della mappatura a una HardwareBuffer assegnata:

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]
    )!!
}

Purtroppo, ciò non crea la texture di archiviazione immutabile necessaria per in Compute Engine, in modo che scriva direttamente nel buffer. L'esempio utilizza glCopyTexSubImage2D per copiare la texture di archiviazione utilizzata dal Compute Engine all'interno di KHR Image. Se il driver OpenGL supporta EGL Image Storage, poi quella estensione. può essere usato per creare una texture di archiviazione condivisa immutabile per evitare la copia.

Conversione in GLSL Compute Engine

I tuoi script RenderScript vengono convertiti in compute builder GLSL.

Scrivi uno shaker di computing GLSL

In OpenGL ES,gli streamr dei computing sono scritti in Linguaggio di ombreggiatura OpenGL (GLSL).

Adattamento degli script globali

In base alle caratteristiche dei testi globali dello script, puoi usare le uniformi oppure oggetti buffer uniformi per i globali che non vengono modificati all'interno dello Shaper:

  • Buffer uniforme: Consigliato per gli script globali degli script modificati di frequente di dimensioni superiori al il limite costante.

Per i dati globali che vengono modificati all'interno dello streamr, puoi usare texture di archiviazione immutabile o una Oggetto buffer di archiviazione Shader.

Esegui calcoli

I responsabili di computing non fanno parte della pipeline grafica, sono generici e progettato per calcolare job altamente parallelizzabili. In questo modo puoi avere maggiore controllo sulle modalità di esecuzione degli annunci, ma allo stesso tempo significa capire meglio come viene caricato in contemporanea il job.

crea e inizializza il programma di computing

La creazione e l'inizializzazione del programma di computing hanno molto in comune con lavorare con qualsiasi altro shar GLES.

  1. Creare il programma e il computing shaker associato.

  2. Collega il codice sorgente, compila lo shaker (e controlla i risultati) della compilazione).

  3. Allega lo shaker, collega il programma e usalo.

  4. Creare, inizializzare e vincolare eventuali uniformi.

Avvia un calcolo

Gli Shaper Compute operano all'interno di uno spazio 1D, 2D o 3D astratto su una serie di gruppi di lavoro, che sono definiti all'interno del codice sorgente dello streamr e rappresentano la dimensione minima di chiamata e la geometria dello shar. Il seguente shaker lavora 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 è di almeno 32 kB e può utilizzare memoryBarrierShared() per fornire un accesso coerente alla memoria.

Definisci la dimensione del gruppo di lavoro

Anche se lo spazio dedicato ai problemi funziona bene con gruppi di lavoro di dimensioni pari a 1, l'impostazione di un valore è importante creare in contemporanea una dimensione appropriata del gruppo di lavoro per il caricamento in contemporanea. Se la dimensione è troppo piccola, il driver GPU potrebbe non caricare in contemporanea abbastanza, ad esempio. Idealmente, queste dimensioni dovrebbero essere ottimizzate per GPU, anche se valori predefiniti ragionevoli funzionano abbastanza bene sui dispositivi attuali, come il gruppo di lavoro di 8 x 8 nello snippet Shar.

Esiste un GL_MAX_COMPUTE_WORK_GROUP_COUNT, ma è sostanziale; deve essere almeno 65535 in tutti e tre gli assi, in base alle specifiche.

Liberati allo Shar

Il passaggio finale nell'esecuzione dei calcoli consiste nell'inviare lo Shader usando uno delle funzioni di tracciamento, come glDispatchCompute La funzione di invio è responsabile per 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 il completamento dell'operazione di calcolo utilizzando un barriera della memoria:

GLES31.glMemoryBarrier(GLES31.GL_SHADER_IMAGE_ACCESS_BARRIER_BIT)

Per concatenare più kernel, (ad esempio, per eseguire la migrazione del codice utilizzando ScriptGroup), crea e invia più programmi e sincronizza il loro accesso all'output con la memoria barriere.

L'app di esempio illustra due attività di computing:

  • Rotazione HUE: un'attività di computing con un singolo sharder computing. Consulta GLSLImageProcessor::rotateHue per l'esempio di codice.
  • Sfocatura: un'attività di computing più complessa che esegue in modo sequenziale due operazioni Shaper. Vedi GLSLImageProcessor::blur per l'esempio di codice.

Per scoprire di più sulle barriere della memoria, consulta Garanzia di visibilità così come Variabili condivise di Google.