Untuk beban kerja yang membutuhkan komputasi GPU, memigrasikan skrip RenderScript ke OpenGL ES (GLES) memungkinkan aplikasi yang ditulis dalam Kotlin, Java, atau menggunakan NDK untuk memanfaatkan hardware GPU. Berikut ini ringkasan umum untuk membantu Anda menggunakan shader komputasi OpenGL ES 3.1 untuk mengganti skrip RenderScript.
Inisialisasi GLES
Daripada membuat objek konteks RenderScript, lakukan langkah-langkah berikut untuk membuat konteks offscreen GLES menggunakan EGL:
Dapatkan tampilan default
Lakukan inisialisasi EGL menggunakan tampilan default, dengan menentukan versi GLES.
Pilih konfigurasi EGL dengan jenis platform
EGL_PBUFFER_BIT
.Gunakan tampilan dan konfigurasi untuk membuat konteks EGL.
Buat platform di luar layar dengan
eglCreatePBufferSurface
. Jika konteksnya hanya akan digunakan untuk komputasi, platform ini dapat berukuran sangat kecil (1x1).Buat thread render, lalu panggil
eglMakeCurrent
di thread render dengan konteks tampilan, platform, dan EGL untuk mengikat konteks GL ke thread.
Aplikasi contoh menunjukkan cara melakukan inisialisasi konteks GLES di GLSLImageProcessor.kt
.
Untuk mempelajari lebih lanjut, lihat
EGLSurfaces dan OpenGL ES.
Output debug GLES
Mendapatkan error yang berguna dari OpenGL akan menggunakan ekstensi untuk mengaktifkan logging debug
yang menyetel callback output debug. Metode untuk melakukannya dari SDK,
glDebugMessageCallbackKHR
, belum pernah diimplementasikan, dan menampilkan
pengecualian. Aplikasi contoh
menyertakan wrapper untuk callback dari kode NDK.
Alokasi GLES
Alokasi RenderScript dapat dimigrasikan ke tekstur penyimpanan yang tidak dapat diubah atau Objek Buffer Penyimpanan Shader. Untuk gambar hanya-baca, Anda dapat menggunakan Objek Sampler, yang memungkinkan pemfilteran.
Resource GLES dialokasikan dalam GLES. Untuk menghindari overhead penyalinan
memori saat berinteraksi dengan komponen Android lainnya, ada
ekstensi untuk Gambar KHR yang memungkinkan pembagian
array 2D data gambar. Ekstensi ini diperlukan untuk perangkat Android
mulai dari Android 8.0. Library
Android Jetpack
inti grafis
mencakup dukungan untuk membuat gambar ini dalam kode terkelola dan memetakannya
ke HardwareBuffer
yang dialokasikan.
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]
)!!
}
Sayangnya, hal ini tidak membuat tekstur penyimpanan yang tidak dapat diubah, yang diperlukan agar
shader komputasi dapat menulis langsung ke buffer. Contoh ini menggunakan
glCopyTexSubImage2D
untuk menyalin tekstur penyimpanan yang digunakan
oleh shader komputasi ke KHR Image
. Jika driver OpenGL mendukung
ekstensi Penyimpanan Gambar EGL, ekstensi tersebut
dapat digunakan untuk membuat tekstur penyimpanan bersama yang tidak dapat diubah guna menghindari penyalinan.
Konversi ke shader komputasi GLSL
Skrip RenderScript Anda dikonversi menjadi shader komputasi GLSL.
Menulis shader komputasi GLSL
Di OpenGL ES,shader komputasi ditulis dalam Bahasa OpenGL Shading (GLSL).
Adaptasi global skrip
Berdasarkan karakteristik global skrip, Anda dapat menggunakan objek buffer seragam untuk global yang tidak diubah dalam shader:
- Buffer yang seragam: Direkomendasikan untuk global skrip yang sering berubah dan lebih besar dari batas konstanta push.
Untuk global yang diubah dalam shader, Anda dapat menggunakan Tekstur penyimpanan yang tidak dapat diubah atau Objek Buffer Penyimpanan Shader.
Menjalankan Komputasi
Shader komputasi bukan bagian dari pipeline grafis, memiliki tujuan umum, dan dirancang untuk mengomputasi tugas yang sangat dapat diparalelkan. Hal ini memungkinkan Anda memiliki kontrol lebih besar atas cara eksekusinya, tetapi juga berarti Anda harus lebih memahami cara tugas Anda diparalelkan.
Membuat dan melakukan inisialisasi program komputasi
Membuat dan menginisialisasi program komputasi memiliki banyak kesamaan dengan menggunakan shader GLES lainnya.
Buat program dan shader komputasi yang terkait dengannya.
Lampirkan sumber shader, kompilasi shader (dan periksa hasil kompilasi).
Lampirkan shader, tautkan program, dan gunakan program.
Buat, lakukan inisialisasi, dan ikat seragam apa pun.
Memulai komputasi
Shader komputasi beroperasi dalam ruang 1D, 2D, atau 3D abstrak pada rangkaian grup kerja yang ditentukan dalam kode sumber shader, dan mewakili ukuran pemanggilan minimum serta geometri shader. Shader berikut berfungsi pada gambar 2D dan menentukan grup kerja dalam dua dimensi:
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;
Grup kerja dapat berbagi memori, yang ditentukan oleh GL_MAX_COMPUTE_SHARED_MEMORY_SIZE
,
berukuran minimal 32 KB, dan dapat menggunakan memoryBarrierShared()
untuk menyediakan
akses memori yang koheren.
Menentukan ukuran grup kerja
Meskipun ruang masalah Anda berfungsi baik dengan ukuran grup kerja 1, menetapkan ukuran grup kerja yang sesuai sangat penting untuk memparalelkan shader komputasi. Misalnya, jika ukurannya terlalu kecil, driver GPU mungkin tidak akan cukup memparalelkan komputasi Anda. Idealnya, ukuran ini harus disesuaikan per GPU, meskipun default yang wajar berfungsi cukup baik di perangkat saat ini, seperti ukuran grup kerja 8x8 dalam cuplikan shader.
Ada GL_MAX_COMPUTE_WORK_GROUP_COUNT
, tetapi ukurannya substansial. Nilainya harus
setidaknya 65535 di ketiga sumbu sesuai dengan spesifikasi.
Mengirim shader
Langkah terakhir dalam menjalankan komputasi adalah mengirim shader menggunakan salah satu
fungsi pengiriman seperti
glDispatchCompute
. Fungsi pengiriman bertanggung jawab
untuk menetapkan jumlah grup kerja untuk setiap sumbu:
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
)
Untuk menampilkan nilai, tunggu terlebih dahulu hingga operasi komputasi selesai menggunakan batasan memori:
GLES31.glMemoryBarrier(GLES31.GL_SHADER_IMAGE_ACCESS_BARRIER_BIT)
Untuk merangkai beberapa kernel bersama-sama,
(misalnya untuk memigrasikan kode menggunakan ScriptGroup
), buat dan kirim
beberapa program serta sinkronkan aksesnya ke output dengan batasan
memori.
Aplikasi contoh mendemonstrasikan dua tugas komputasi:
- Rotasi HUE: Tugas komputasi
dengan satu shader komputasi. Lihat
GLSLImageProcessor::rotateHue
untuk contoh kode. - Pemburaman: Tugas komputasi yang lebih kompleks yang secara berurutan menjalankan dua shader
komputasi. Lihat
GLSLImageProcessor::blur
untuk contoh kode.
Untuk mempelajari lebih lanjut tentang hambatan memori, lihat Memastikan visibilitas dan Variabel bersama kami.