RenderScript adalah framework yang berfungsi untuk menjalankan tugas yang intensif secara terkomputasi dengan performa tinggi di Android. RenderScript secara khusus ditujukan untuk digunakan dengan komputasi paralel data, meskipun juga dapat digunakan pada beban kerja serial. Runtime RenderScript menyejajarkan pekerjaan di seluruh prosesor yang tersedia di perangkat, seperti GPU dan CPU multi-core. Hal ini memungkinkan Anda untuk fokus mengekspresikan algoritme, bukan fokus pada penjadwalan tugas. RenderScript sangat berguna untuk aplikasi yang melakukan pemrosesan gambar, fotografi komputasional, atau computer vision.
Untuk mulai menggunakan RenderScript, ada dua konsep utama yang harus Anda pahami:
- Bahasa RenderScript sendiri adalah bahasa turunan C99 untuk menulis kode komputasi berperforma tinggi. Menulis Kernel RenderScript menjelaskan cara Anda dapat menggunakannya untuk menulis kernel komputasi.
- API kontrol digunakan untuk mengelola masa pakai resource RenderScript dan mengontrol eksekusi kernel. API ini tersedia dalam tiga bahasa: Java, C++ di Android NDK, dan bahasa kernel turunan C99 itu sendiri. Menggunakan RenderScript dari Kode Java dan RenderScript Sumber Tunggal masing-masing menjelaskan opsi pertama dan ketiga.
Menulis Kernel RenderScript
Kernel RenderScript biasanya berada dalam file .rs
di dalam
direktori <project_root>/src/rs
; dan setiap file .rs
disebut dengan skrip. Setiap skrip berisi kumpulan kernel, fungsi, dan variabelnya sendiri. Skrip dapat
berisi:
- Deklarasi pragma (
#pragma version(1)
) yang mendeklarasikan versi bahasa kernel RenderScript yang digunakan dalam skrip ini. Saat ini, 1 adalah satu-satunya nilai yang valid. - Deklarasi pragma (
#pragma rs java_package_name(com.example.app)
) yang mendeklarasikan nama paket class Java yang ditampilkan dari skrip ini. Perlu diketahui bahwa file.rs
harus menjadi bagian dari paket aplikasi Anda, bukan dalam project library. - Nol atau beberapa fungsi yang dapat dipanggil. Fungsi yang dapat dipanggil adalah fungsi RenderScript thread tunggal yang dapat Anda panggil dari kode Java dengan argumen sembarang. Fungsi ini sering kali berguna untuk penyiapan awal atau komputasi serial dalam pipeline pemrosesan yang lebih besar.
Nol atau beberapa global skrip. Global skrip mirip dengan variabel global di C. Anda dapat mengakses global skrip dari kode Java, dan skrip ini sering digunakan untuk meneruskan parameter ke kernel RenderScript. Global skrip dijelaskan secara lebih mendetail di sini.
Nol atau beberapa kernel komputasi. Kernel komputasi adalah fungsi atau kumpulan fungsi yang dapat Anda eksekusi secara paralel pada sekumpulan data menggunakan runtime RenderScript. Terdapat dua jenis kernel komputasi: kernel pemetaan (juga disebut kernel foreach) dan kernel pengurangan.
Kernel pemetaan adalah fungsi paralel yang beroperasi pada sekumpulan
Allocations
dengan dimensi yang sama. Secara default, kernel ini dieksekusi sekali untuk setiap koordinat dalam dimensi tersebut. Kernel ini biasanya (tetapi tidak secara eksklusif) digunakan untuk mengubah kumpulanAllocations
input menjadiAllocation
output, per satuElement
.Berikut adalah contoh sederhana kernel pemetaan:
uchar4 RS_KERNEL invert(uchar4 in, uint32_t x, uint32_t y) { uchar4 out = in; out.r = 255 - in.r; out.g = 255 - in.g; out.b = 255 - in.b; return out; }
Secara umum, fungsi ini sama dengan fungsi C standar. Properti
RS_KERNEL
yang diterapkan pada prototipe fungsi menetapkan bahwa fungsi tersebut adalah kernel pemetaan RenderScript, bukan fungsi yang dapat dipanggil. Argumenin
otomatis diisi berdasarkanAllocation
input yang diteruskan ke peluncuran kernel. Argumenx
dany
dibahas di bawah ini. Nilai yang dihasilkan dari kernel tersebut otomatis ditulis ke lokasi yang sesuai dalamAllocation
output. Secara default, kernel ini dijalankan di seluruhAllocation
inputnya, dengan satu eksekusi fungsi kernel perElement
dalamAllocation
.Kernel pemetaan dapat memiliki satu atau beberapa
Allocations
input, satuAllocation
output, atau keduanya. Runtime RenderScript akan melakukan pemeriksaan untuk memastikan bahwa semua Alokasi input dan output memiliki dimensi yang sama, dan bahwa jenisElement
Alokasi input dan output cocok dengan prototipe kernel; jika salah satu pemeriksaan ini gagal, RenderScript akan menampilkan pengecualian.CATATAN: Sebelum Android 6.0 (API level 23), kernel pemetaan tidak dapat memiliki lebih dari satu
Allocation
input.Jika membutuhkan lebih banyak
Allocations
input atau output dari yang dimiliki kernel, objek tersebut harus terikat ke global skriprs_allocation
dan diakses dari kernel atau fungsi yang dapat dipanggil melaluirsGetElementAt_type()
ataursSetElementAt_type()
.CATATAN:
RS_KERNEL
adalah makro yang otomatis ditetapkan oleh RenderScript untuk memudahkan Anda:#define RS_KERNEL __attribute__((kernel))
Kernel pengurangan adalah kumpulan fungsi yang beroperasi pada sekumpulan
Allocations
input dengan dimensi yang sama. Secara default, fungsi akumulatornya dieksekusi sekali untuk setiap koordinat dalam dimensi tersebut. Kernel ini umumnya (tetapi tidak secara eksklusif) digunakan untuk "mengurangi" kumpulanAllocations
input menjadi nilai tunggal.Berikut adalah contoh kernel pengurangan sederhana yang melengkapi
Elements
inputnya:#pragma rs reduce(addint) accumulator(addintAccum) static void addintAccum(int *accum, int val) { *accum += val; }
Kernel pengurangan terdiri dari satu atau beberapa fungsi yang ditulis pengguna.
#pragma rs reduce
digunakan untuk menentukan kernel dengan menetapkan namanya (dalam contoh ini adalahaddint
), serta nama dan peran fungsi yang membentuk kernel (dalam contoh ini adalah fungsiaccumulator
addintAccum
). Semua fungsi tersebut harus bersifatstatic
. Kernel pengurangan selalu membutuhkan fungsiaccumulator
; dan mungkin juga memiliki fungsi lain, bergantung apa yang Anda inginkan darinya.Fungsi akumulator kernel pengurangan harus mengembalikan
void
dan harus memiliki sedikitnya dua argumen. Argumen pertama (dalam contoh ini adalahaccum
) merupakan pointer ke item data akumulator, dan yang kedua otomatis (dalam contoh ini adalahval
) diisi berdasarkanAllocation
input yang diteruskan ke peluncuran kernel. Item data akumulator dibuat oleh runtime RenderScript; secara default, item ini diinisialisasi ke nol. Secara default, kernel ini dijalankan di seluruhAllocation
inputnya, dengan satu eksekusi fungsi akumulator perElement
dalamAllocation
. Secara default, nilai akhir item data akumulator diperlakukan sebagai hasil dari pengurangan, dan dikembalikan ke Java. Runtime RenderScript akan melakukan pemeriksaan untuk memastikan bahwa jenisElement
Alokasi input cocok dengan prototipe fungsi akumulator; jika tidak cocok, RenderScript akan menampilkan pengecualian.Kernel pengurangan memiliki satu atau beberapa
Allocations
input, tetapi tidak memilikiAllocations
output.Kernel pengurangan dijelaskan secara lebih mendetail di sini.
Kernel pengurangan didukung di Android 7.0 (API level 24) dan yang lebih baru.
Fungsi kernel pemetaan atau fungsi akumulator kernel pengurangan dapat mengakses koordinat eksekusi saat ini menggunakan argumen khusus
x
,y
, danz
, yang harus berjenisint
atauuint32_t
. Argumen ini bersifat opsional.Fungsi kernel pemetaan atau fungsi akumulator kernel pengurangan juga dapat menggunakan argumen khusus opsional
context
dengan jenis rs_kernel_context. Argumen tersebut diperlukan oleh rangkaian API runtime yang digunakan untuk meminta properti tertentu dari eksekusi saat ini; misalnya rsGetDimX. (Argumencontext
tersedia di Android 6.0 (API level 23) dan yang lebih baru.)- Fungsi
init()
opsional. Fungsiinit()
adalah jenis khusus dari fungsi yang dapat dipanggil, yang dijalankan oleh RenderScript saat instance skrip dibuat untuk kali pertama. Hal ini memungkinkan beberapa komputasi otomatis terjadi pada saat pembuatan skrip. - Nol atau beberapa fungsi dan global skrip statis. Global skrip statis setara dengan
global skrip, tetapi tidak dapat diakses dari kode Java. Fungsi statis adalah fungsi C
standar yang dapat dipanggil dari kernel atau fungsi apa pun yang dapat dipanggil dalam skrip, tetapi tidak diekspos
ke Java API. Jika global skrip atau fungsi tidak perlu diakses dari kode Java, kami
sangat merekomendasikan Anda untuk mendeklarasikannya sebagai
static
.
Menentukan presisi floating point
Anda dapat mengontrol level presisi floating point yang diperlukan dalam sebuah skrip. Hal ini berguna jika standar IEEE 754-2008 lengkap (digunakan secara default) tidak diperlukan. Pragma berikut dapat menetapkan level presisi floating point yang berbeda:
#pragma rs_fp_full
(default jika tidak ada yang ditentukan): Untuk aplikasi yang memerlukan presisi floating point seperti yang diuraikan oleh standar IEEE 754-2008.#pragma rs_fp_relaxed
: Untuk aplikasi yang tidak memerlukan kepatuhan IEEE 754-2008 yang ketat dan dapat menerima lebih sedikit presisi. Mode ini memungkinkan flush-to-zero untuk denorm dan round-towards-zero.#pragma rs_fp_imprecise
: Untuk aplikasi yang tidak memiliki persyaratan presisi yang ketat. Mode ini memungkinkan semua hal dalamrs_fp_relaxed
beserta hal-hal berikut:- Operasi yang menghasilkan -0.0 dapat mengembalikan +0.0.
- Operasi pada INF dan NAN tidak ditentukan.
Sebagian besar aplikasi dapat menggunakan rs_fp_relaxed
tanpa efek samping. Hal ini mungkin sangat
bermanfaat pada beberapa arsitektur karena pengoptimalan tambahan hanya tersedia dengan
presisi yang tidak ketat (seperti petunjuk CPU SIMD).
Mengakses RenderScript API dari Java
Saat mengembangkan aplikasi Android yang menggunakan RenderScript, Anda dapat mengakses API-nya dari Java dengan salah satu dari dua cara berikut:
android.renderscript
- API dalam paket class ini tersedia di perangkat yang menjalankan Android 3.0 (API level 11) dan yang lebih baru.android.support.v8.renderscript
- API dalam paket ini tersedia melalui Support Library, yang memungkinkan Anda untuk menggunakannya di perangkat yang menjalankan Android 2.3 (API level 9) dan yang lebih baru.
Berikut konsekuensinya:
- Jika menggunakan Support Library API, bagian RenderScript aplikasi akan kompatibel
dengan perangkat yang menjalankan Android 2.3 (API level 9) dan yang lebih baru, terlepas dari fitur
RenderScript yang Anda gunakan. Hal ini memungkinkan aplikasi Anda berfungsi di lebih banyak perangkat dibandingkan jika Anda menggunakan API
native (
android.renderscript
). - Fitur RenderScript tertentu tidak tersedia melalui Support Library API.
- Jika menggunakan Support Library API, APK Anda akan menjadi lebih besar (mungkin secara signifikan) dibandingkan
jika Anda menggunakan API native (
android.renderscript
).
Menggunakan RenderScript Support Library API
Untuk menggunakan Support Library RenderScript API, Anda harus mengonfigurasi lingkungan pengembangan agar dapat mengaksesnya. Android SDK Tools berikut diperlukan untuk menggunakan API ini:
- Android SDK Tools revisi 22.2 atau yang lebih tinggi
- Alat Build Android SDK revisi 18.1.0 atau yang lebih tinggi
Perlu diketahui bahwa mulai dari Alat Build Android SDK 24.0.0, Android 2.2 (API level 8) tidak lagi didukung.
Anda dapat memeriksa dan mengupdate versi terinstal alat ini di Android SDK Manager.
Untuk menggunakan Support Library RenderScript API:
- Pastikan Anda telah menginstal versi Android SDK yang diperlukan.
- Update setelan proses build Android agar menyertakan setelan RenderScript:
- Buka file
build.gradle
dalam folder aplikasi modul aplikasi Anda. - Tambahkan setelan RenderScript berikut ke file tersebut:
Groovy
android { compileSdkVersion 33 defaultConfig { minSdkVersion 9 targetSdkVersion 19 renderscriptTargetApi 18 renderscriptSupportModeEnabled true } }
Kotlin
android { compileSdkVersion(33) defaultConfig { minSdkVersion(9) targetSdkVersion(19) renderscriptTargetApi = 18 renderscriptSupportModeEnabled = true } }
Setelan yang tercantum di atas mengontrol perilaku tertentu dalam proses build Android:
renderscriptTargetApi
- Menentukan versi bytecode yang akan dibuat. Sebaiknya tetapkan nilai ini ke API level terendah yang dapat memberikan semua fungsionalitas yang Anda gunakan dan tetapkanrenderscriptSupportModeEnabled
ketrue
. Nilai yang valid untuk setelan ini adalah nilai bilangan bulat dari 11 sampai API level terbaru yang dirilis. Jika versi SDK minimum yang ditentukan dalam manifes aplikasi Anda ditetapkan ke nilai lain, nilai tersebut akan diabaikan dan nilai target dalam file build akan digunakan untuk menetapkan versi SDK minimum.renderscriptSupportModeEnabled
- Menetapkan bahwa bytecode yang dihasilkan harus dikembalikan ke versi yang kompatibel jika perangkat tempatnya berjalan tidak mendukung versi target.
- Buka file
- Dalam class aplikasi yang menggunakan RenderScript, tambahkan impor untuk class Support
Library:
Kotlin
import android.support.v8.renderscript.*
Java
import android.support.v8.renderscript.*;
Menggunakan RenderScript dari Kode Java atau Kotlin
Menggunakan RenderScript dari kode Java atau Kotlin bergantung pada class API yang
ada dalam paket android.renderscript
atau android.support.v8.renderscript
. Sebagian besar
aplikasi mengikuti pola penggunaan dasar yang sama:
- Menginisialisasi konteks RenderScript. Konteks
RenderScript
, yang dibuat dengancreate(Context)
, memastikan bahwa RenderScript dapat digunakan dan menyediakan objek untuk mengontrol masa pakai semua objek RenderScript berikutnya. Anda harus mengupayakan pembuatan konteks agar menjadi operasi yang dapat berjalan lama, karena mungkin proses ini akan membuat resource di bagian hardware lainnya; proses ini sebisa mungkin tidak boleh terjadi di lokasi penting aplikasi. Biasanya, aplikasi hanya akan memiliki satu konteks RenderScript dalam satu waktu. - Membuat sedikitnya satu
Allocation
untuk diteruskan ke skrip.Allocation
adalah objek RenderScript yang menyediakan penyimpanan untuk data dalam jumlah tertentu. Kernel dalam skrip menggunakan objekAllocation
sebagai input dan output-nya, dan objekAllocation
dapat diakses dalam kernel menggunakanrsGetElementAt_type()
danrsSetElementAt_type()
ketika terikat sebagai global skrip. ObjekAllocation
memungkinkan array diteruskan dari kode Java ke kode RenderScript, juga sebaliknya. ObjekAllocation
biasanya dibuat menggunakancreateTyped()
ataucreateFromBitmap()
. - Membuat skrip apa pun yang diperlukan. Terdapat dua jenis skrip yang tersedia
untuk Anda saat menggunakan RenderScript:
- ScriptC: Ini adalah skrip yang ditentukan pengguna, seperti yang dijelaskan dalam Menulis Kernel RenderScript di atas. Setiap skrip memiliki class Java
yang ditampilkan oleh compiler RenderScript untuk memudahkan akses skrip dari kode Java;
class ini memiliki nama
ScriptC_filename
. Sebagai contoh, jika kernel pemetaan di atas berada diinvert.rs
dan konteks RenderScript sudah ada dimRenderScript
, kode Java atau Kotlin untuk membuat instance skrip akan seperti berikut:Kotlin
val invert = ScriptC_invert(renderScript)
Java
ScriptC_invert invert = new ScriptC_invert(renderScript);
- ScriptIntrinsic: Ini adalah kernel RenderScript bawaan untuk operasi umum,
seperti pemburaman Gaussian, konvolusi, dan perpaduan gambar. Untuk informasi selengkapnya, lihat subclass
ScriptIntrinsic
.
- ScriptC: Ini adalah skrip yang ditentukan pengguna, seperti yang dijelaskan dalam Menulis Kernel RenderScript di atas. Setiap skrip memiliki class Java
yang ditampilkan oleh compiler RenderScript untuk memudahkan akses skrip dari kode Java;
class ini memiliki nama
- Mengisi Alokasi dengan data. Kecuali untuk Alokasi yang dibuat dengan
createFromBitmap()
, Alokasi akan diisi dengan data kosong pada saat pertama kali dibuat. Untuk mengisi Alokasi, gunakan salah satu metode “salin” dalamAllocation
. Metode "salin" berjalan secara sinkron. - Menetapkan setiap global skrip yang diperlukan. Anda dapat menetapkan global menggunakan metode
dalam class
ScriptC_filename
serupa bernamaset_globalname
. Sebagai contoh, untuk menetapkan variabelint
yang bernamathreshold
, gunakan metode Javaset_threshold(int)
; dan untuk menetapkan variabelrs_allocation
bernamalookup
, gunakan metode Javaset_lookup(Allocation)
. Metodeset
berjalan secara asinkron. - Meluncurkan kernel yang sesuai dan fungsi yang dapat dipanggil.
Metode untuk meluncurkan kernel tertentu ditampilkan dalam class
ScriptC_filename
yang sama dengan metode bernamaforEach_mappingKernelName()
ataureduce_reductionKernelName()
. Peluncuran ini dilakukan secara asinkron. Bergantung pada argumen ke kernel, metode tersebut membutuhkan satu atau beberapa Alokasi, yang semuanya harus memiliki dimensi yang sama. Secara default, kernel dieksekusi pada setiap koordinat dalam dimensi tersebut; untuk mengeksekusi kernel pada subset koordinat tersebut, teruskanScript.LaunchOptions
yang sesuai sebagai argumen terakhir pada metodeforEach
ataureduce
.Jalankan fungsi yang dapat dipanggil menggunakan metode
invoke_functionName
yang ditampilkan dalam classScriptC_filename
yang sama. Peluncuran ini dilakukan secara asinkron. - Mengambil data dari objek
Allocation
dan objek javaFutureType. Untuk mengakses data dariAllocation
dari kode Java, Anda harus menyalin data tersebut kembali ke Java menggunakan salah satu metode "salin" dalamAllocation
. Untuk mendapatkan hasil dari kernel pengurangan, Anda harus menggunakan metodejavaFutureType.get()
. Metode "salin" danget()
berjalan secara sinkron. - Menghilangkan konteks RenderScript. Anda dapat menghilangkan konteks
RenderScript dengan
destroy()
atau dengan mengizinkan objek konteks RenderScript untuk dibersihkan dari memori. Jika dilakukan, setiap penggunaan objek yang termasuk dalam konteks tersebut selanjutnya akan menampilkan pengecualian.
Model eksekusi asinkron
Metode forEach
, invoke
, reduce
,
dan set
yang ditampilkan berjalan secara asinkron; masing-masing dapat kembali ke Java sebelum menyelesaikan tindakan
yang diminta. Namun, tindakan individualnya diserialisasi sesuai urutan peluncurannya.
Class Allocation
menyediakan metode "salin" untuk menyalin data ke
dan dari Alokasi. Metode "salin" berjalan secara sinkron, dan diserialisasi sehubungan dengan setiap
tindakan asinkron di atas yang menggunakan Alokasi yang sama.
Class javaFutureType yang ditampilkan
menyediakan metode get()
untuk mendapatkan hasil pengurangan. get()
berjalan
secara sinkron, dan diserialisasi sehubungan dengan pengurangan tersebut (yang berjalan secara asinkron).
RenderScript Sumber Tunggal
Android 7.0 (API level 24) memperkenalkan fitur pemrograman baru yang disebut dengan RenderScript
Sumber Tunggal, dengan kernel yang diluncurkan dari skrip tempatnya ditentukan, bukan
dari Java. Metode tersebut saat ini terbatas pada kernel pemetaan, yang secara singkat disebut sebagai "kernel"
di bagian ini agar lebih ringkas. Fitur baru ini juga mendukung pembuatan alokasi berjenis
rs_allocation
dari dalam skrip. Anda kini dapat
menerapkan seluruh algoritme hanya dalam skrip, meskipun beberapa peluncuran kernel diperlukan.
Hal ini memberikan dua keuntungan: kode menjadi lebih mudah dibaca karena penerapan algoritme hanya
menggunakan satu bahasa; dan kode menjadi lebih cepat karena transisi antara Java dan
RenderScript berkurang lintas peluncuran kernel.
Dalam RenderScript Sumber Tunggal, Anda dapat menulis kernel seperti yang dijelaskan dalam
Menulis Kernel RenderScript. Kemudian, tulis fungsi yang dapat dipanggil
yang akan memanggil
rsForEach()
untuk meluncurkannya. API tersebut menggunakan fungsi kernel sebagai parameter
pertama, diikuti dengan alokasi input dan output. API yang serupa,
rsForEachWithOptions()
, menggunakan argumen tambahan dari jenis
rs_script_call_t
, yang menetapkan subset elemen dari alokasi input dan
output agar fungsi kernel dapat diproses.
Untuk memulai komputasi RenderScript, panggil fungsi yang dapat dipanggil dari Java.
Ikuti langkah-langkah dalam Menggunakan RenderScript dari Kode Java.
Pada langkah meluncurkan kernel yang sesuai, panggil
fungsi yang dapat dipanggil menggunakan invoke_function_name()
, yang akan
memulai seluruh komputasi, termasuk meluncurkan kernel.
Alokasi biasanya diperlukan untuk menyimpan dan meneruskan
hasil menengah dari satu peluncuran kernel ke peluncuran lainnya. Anda dapat membuatnya menggunakan
rsCreateAllocation(). Salah satu bentuk yang mudah digunakan dari API tersebut adalah
rsCreateAllocation_<T><W>(…)
, dengan T adalah jenis data untuk
sebuah elemen, dan W adalah lebar vektor untuk elemen tersebut. API tersebut menggunakan
ukuran dalam dimensi X, Y, dan Z sebagai argumen. Untuk alokasi 1D atau 2D, ukuran untuk dimensi Y atau Z
dapat dihilangkan. Sebagai contoh, rsCreateAllocation_uchar4(16384)
akan membuat alokasi 1D
dari 16384 elemen, yang masing-masing berjenis uchar4
.
Alokasi dikelola otomatis oleh sistem. Anda
tidak perlu melepaskan atau mengosongkannya secara eksplisit. Namun, Anda dapat memanggil
rsClearObject(rs_allocation* alloc)
untuk menunjukkan bahwa Anda tidak lagi memerlukan pengendali
alloc
ke alokasi yang ditetapkan
sehingga sistem dapat membebaskan resource sesegera mungkin.
Bagian Menulis Kernel RenderScript memuat contoh
kernel yang membalik gambar. Contoh di bawah menjelaskannya secara lebih luas untuk menerapkan lebih dari satu efek ke gambar,
menggunakan RenderScript Sumber Tunggal. Contoh ini juga mencakup kernel lain, greyscale
, yang mengubah
gambar berwarna menjadi hitam putih. Fungsi process()
yang dapat dipanggil kemudian menerapkan dua kernel
tersebut secara berturutan ke gambar input, dan menghasilkan gambar output. Alokasi untuk input dan
output diteruskan sebagai argumen dengan
jenis
rs_allocation
.
// File: singlesource.rs #pragma version(1) #pragma rs java_package_name(com.android.rssample) static const float4 weight = {0.299f, 0.587f, 0.114f, 0.0f}; uchar4 RS_KERNEL invert(uchar4 in, uint32_t x, uint32_t y) { uchar4 out = in; out.r = 255 - in.r; out.g = 255 - in.g; out.b = 255 - in.b; return out; } uchar4 RS_KERNEL greyscale(uchar4 in) { const float4 inF = rsUnpackColor8888(in); const float4 outF = (float4){ dot(inF, weight) }; return rsPackColorTo8888(outF); } void process(rs_allocation inputImage, rs_allocation outputImage) { const uint32_t imageWidth = rsAllocationGetDimX(inputImage); const uint32_t imageHeight = rsAllocationGetDimY(inputImage); rs_allocation tmp = rsCreateAllocation_uchar4(imageWidth, imageHeight); rsForEach(invert, inputImage, tmp); rsForEach(greyscale, tmp, outputImage); }
Anda dapat memanggil fungsi process()
dari Java atau Kotlin seperti berikut:
Kotlin
val RS: RenderScript = RenderScript.create(context) val script = ScriptC_singlesource(RS) val inputAllocation: Allocation = Allocation.createFromBitmapResource( RS, resources, R.drawable.image ) val outputAllocation: Allocation = Allocation.createTyped( RS, inputAllocation.type, Allocation.USAGE_SCRIPT or Allocation.USAGE_IO_OUTPUT ) script.invoke_process(inputAllocation, outputAllocation)
Java
// File SingleSource.java RenderScript RS = RenderScript.create(context); ScriptC_singlesource script = new ScriptC_singlesource(RS); Allocation inputAllocation = Allocation.createFromBitmapResource( RS, getResources(), R.drawable.image); Allocation outputAllocation = Allocation.createTyped( RS, inputAllocation.getType(), Allocation.USAGE_SCRIPT | Allocation.USAGE_IO_OUTPUT); script.invoke_process(inputAllocation, outputAllocation);
Contoh ini menunjukkan bagaimana algoritme yang melibatkan dua peluncuran kernel dapat diterapkan sepenuhnya dalam bahasa RenderScript sendiri. Tanpa RenderScript Sumber Tunggal, Anda harus meluncurkan kedua kernel tersebut dari kode Java, memisahkan peluncuran kernel dari definisi kernel, dan akan lebih sulit untuk memahami keseluruhan algoritmenya. Kode RenderScript Sumber Tunggal tidak hanya lebih mudah dibaca, tetapi juga mengeliminasi transisi antara Java dan skrip lintas peluncuran kernel. Beberapa algoritme iteratif mungkin meluncurkan kernel ratusan kali sehingga membuat overhead transisi tersebut menjadi cukup besar.
Global Skrip
Global skrip adalah variabel global non-static
biasa dalam file skrip (.rs
). Untuk global skrip
bernama var yang ditentukan dalam
file filename.rs
, akan ada metode
get_var
yang ditampilkan dalam
class ScriptC_filename
. Kecuali global
berupa const
, akan tersedia juga metode
set_var
.
Global skrip yang ditentukan memiliki dua nilai terpisah: satu nilai Java dan satu nilai skrip. Nilai ini berperilaku seperti berikut:
- Jika var memiliki penginisialisasi statis dalam skrip, penginisialisasi ini akan menentukan nilai awal var dalam Java dan skrip. Jika tidak, nilai awal tersebut adalah nol.
- Akses ke var dalam skrip akan membaca dan menulis nilai skripnya.
- Metode
get_var
akan membaca nilai Java-nya. - Metode
set_var
(jika ada) akan menulis nilai Java secara langsung, dan menulis nilai skripnya secara asinkron.
CATATAN: Kecuali untuk setiap penginisialisasi statis dalam skrip, hal ini berarti nilai yang ditulis ke global dari dalam skrip tidak akan terlihat di Java.
Kernel Pengurangan dalam Kedalaman
Pengurangan adalah proses penggabungan kumpulan data menjadi satu nilai. Pengurangan merupakan primitif yang berguna dalam pemrograman paralel, dengan penerapan seperti berikut:
- menghitung jumlah atau produk di seluruh data
- menghitung operasi logis (
and
,or
,xor
) pada semua data - menemukan nilai minimum atau maksimum dalam data
- menelusuri nilai tertentu atau untuk koordinat nilai tertentu dalam data
Di Android 7.0 (API level 24) dan yang lebih baru, RenderScript mendukung kernel pengurangan untuk memungkinkan efisiensi algoritme pengurangan yang ditulis oleh pengguna. Anda dapat meluncurkan kernel pengurangan pada input dengan 1, 2, atau 3 dimensi.
Contoh di atas menunjukkan kernel pengurangan addint sederhana.
Berikut adalah kernel pengurangan findMinAndMax yang lebih rumit,
yang menemukan lokasi nilai long
minimum dan maksimum dalam
Allocation
1 dimensi:
#define LONG_MAX (long)((1UL << 63) - 1) #define LONG_MIN (long)(1UL << 63) #pragma rs reduce(findMinAndMax) \ initializer(fMMInit) accumulator(fMMAccumulator) \ combiner(fMMCombiner) outconverter(fMMOutConverter) // Either a value and the location where it was found, or INITVAL. typedef struct { long val; int idx; // -1 indicates INITVAL } IndexedVal; typedef struct { IndexedVal min, max; } MinAndMax; // In discussion below, this initial value { { LONG_MAX, -1 }, { LONG_MIN, -1 } } // is called INITVAL. static void fMMInit(MinAndMax *accum) { accum->min.val = LONG_MAX; accum->min.idx = -1; accum->max.val = LONG_MIN; accum->max.idx = -1; } //---------------------------------------------------------------------- // In describing the behavior of the accumulator and combiner functions, // it is helpful to describe hypothetical functions // IndexedVal min(IndexedVal a, IndexedVal b) // IndexedVal max(IndexedVal a, IndexedVal b) // MinAndMax minmax(MinAndMax a, MinAndMax b) // MinAndMax minmax(MinAndMax accum, IndexedVal val) // // The effect of // IndexedVal min(IndexedVal a, IndexedVal b) // is to return the IndexedVal from among the two arguments // whose val is lesser, except that when an IndexedVal // has a negative index, that IndexedVal is never less than // any other IndexedVal; therefore, if exactly one of the // two arguments has a negative index, the min is the other // argument. Like ordinary arithmetic min and max, this function // is commutative and associative; that is, // // min(A, B) == min(B, A) // commutative // min(A, min(B, C)) == min((A, B), C) // associative // // The effect of // IndexedVal max(IndexedVal a, IndexedVal b) // is analogous (greater . . . never greater than). // // Then there is // // MinAndMax minmax(MinAndMax a, MinAndMax b) { // return MinAndMax(min(a.min, b.min), max(a.max, b.max)); // } // // Like ordinary arithmetic min and max, the above function // is commutative and associative; that is: // // minmax(A, B) == minmax(B, A) // commutative // minmax(A, minmax(B, C)) == minmax((A, B), C) // associative // // Finally define // // MinAndMax minmax(MinAndMax accum, IndexedVal val) { // return minmax(accum, MinAndMax(val, val)); // } //---------------------------------------------------------------------- // This function can be explained as doing: // *accum = minmax(*accum, IndexedVal(in, x)) // // This function simply computes minimum and maximum values as if // INITVAL.min were greater than any other minimum value and // INITVAL.max were less than any other maximum value. Note that if // *accum is INITVAL, then this function sets // *accum = IndexedVal(in, x) // // After this function is called, both accum->min.idx and accum->max.idx // will have nonnegative values: // - x is always nonnegative, so if this function ever sets one of the // idx fields, it will set it to a nonnegative value // - if one of the idx fields is negative, then the corresponding // val field must be LONG_MAX or LONG_MIN, so the function will always // set both the val and idx fields static void fMMAccumulator(MinAndMax *accum, long in, int x) { IndexedVal me; me.val = in; me.idx = x; if (me.val <= accum->min.val) accum->min = me; if (me.val >= accum->max.val) accum->max = me; } // This function can be explained as doing: // *accum = minmax(*accum, *val) // // This function simply computes minimum and maximum values as if // INITVAL.min were greater than any other minimum value and // INITVAL.max were less than any other maximum value. Note that if // one of the two accumulator data items is INITVAL, then this // function sets *accum to the other one. static void fMMCombiner(MinAndMax *accum, const MinAndMax *val) { if ((accum->min.idx < 0) || (val->min.val < accum->min.val)) accum->min = val->min; if ((accum->max.idx < 0) || (val->max.val > accum->max.val)) accum->max = val->max; } static void fMMOutConverter(int2 *result, const MinAndMax *val) { result->x = val->min.idx; result->y = val->max.idx; }
CATATAN: Tersedia lebih banyak contoh kernel pengurangan di sini.
Untuk menjalankan kernel pengurangan, runtime RenderScript akan membuat satu atau beberapa
variabel yang disebut item data
akumulator untuk mempertahankan kondisi proses pengurangan. Runtime RenderScript
akan mengambil jumlah item data akumulator dengan cara tertentu untuk memaksimalkan performa. Jenis
item data akumulator (accumType) ditentukan oleh fungsi
akumulator kernel; argumen pertama ke fungsi tersebut adalah pointer ke item data
akumulator. Secara default, setiap item data akumulator diinisialisasi ke nol (seolah-olah
oleh memset
); namun, Anda dapat menulis fungsi penginisialisasi untuk melakukan
hal lain.
Contoh: Dalam kernel addint,
item data akumulator (dengan jenis int
) digunakan untuk menambahkan nilai
input. Tidak ada fungsi penginisialisasi di sini sehingga setiap item data akumulator diinisialisasi
ke nol.
Contoh: Dalam
kernel findMinAndMax, item data akumulator
(dengan jenis MinAndMax
) digunakan untuk melacak nilai minimum dan maksimum yang ditemukan
sejauh ini. Terdapat fungsi penginisialisasi untuk menetapkannya masing-masing ke LONG_MAX
dan
LONG_MIN
; serta untuk menetapkan lokasi nilai ini ke -1,
yang menunjukkan bahwa nilai tersebut sebenarnya tidak ada di bagian (kosong) input yang
telah diproses.
RenderScript memanggil fungsi akumulator Anda satu kali untuk setiap koordinat dalam input. Biasanya, fungsi akan mengupdate item data akumulator dengan cara tertentu menurut input yang ada.
Contoh: Dalam kernel addint, fungsi akumulator menambahkan nilai Elemen input ke item data akumulator.
Contoh: Dalam kernel findMinAndMax, fungsi akumulator akan memeriksa apakah nilai Elemen input kurang dari atau sama dengan nilai minimum yang dicatat dalam item data akumulator, dan/atau lebih besar dari atau sama dengan nilai maksimum yang dicatat dalam item data akumulator, lalu mengupdate item data akumulator sesuai dengan hasilnya.
Setelah fungsi akumulator dipanggil satu kali untuk setiap koordinat dalam input, RenderScript harus menggabungkan item data akumulator menjadi satu item data akumulator. Anda dapat menulis fungsi penggabung untuk melakukannya. Jika fungsi akumulator memiliki satu input tanpa argumen khusus, Anda tidak perlu menulis fungsi penggabung; RenderScript akan menggunakan fungsi akumulator untuk menggabungkan item data akumulator. (Anda masih dapat menulis fungsi penggabung jika perilaku default ini bukanlah yang Anda inginkan.)
Contoh: Dalam kernel addint, tidak ada fungsi penggabung sehingga fungsi akumulator akan digunakan. Ini adalah perilaku yang benar, karena jika kita membagi kumpulan nilai menjadi dua bagian lalu menambahkan nilai dalam keduanya secara terpisah, menambahkan kedua jumlah tersebut akan sama dengan menambahkan seluruh kumpulan.
Contoh: Dalam
kernel findMinAndMax, fungsi penggabung
akan memeriksa apakah nilai minimum yang terekam dalam item data akumulator
"sumber" *val
kurang dari nilai minimum yang terekam dalam item data akumulator
“tujuan" *accum
, lalu mengupdate *accum
sesuai hasilnya. Fungsi ini akan melakukan tugas yang serupa untuk nilai maksimum. Tindakan ini akan mengupdate *accum
ke kondisi yang akan dimilikinya apabila semua nilai input telah diakumulasi ke dalam
*accum
, bukan sebagian ke *accum
dan sebagian
lainnya ke *val
.
Setelah semua item data akumulator digabungkan, RenderScript menentukan hasil pengurangan untuk dikembalikan ke Java. Anda dapat menulis fungsi outconverter untuk melakukannya. Anda tidak perlu menulis fungsi outconverter jika ingin menjadikan nilai akhir item data akumulator gabungan sebagai hasil pengurangan.
Contoh: Dalam kernel addint, tidak ada fungsi outconverter. Nilai akhir item data gabungan adalah jumlah dari semua Elemen input, yang merupakan nilai yang ingin kita kembalikan.
Contoh: Dalam
kernel findMinAndMax, fungsi outconverter
menginisialisasi nilai hasil int2
untuk mempertahankan lokasi nilai minimum dan
maksimum yang dihasilkan dari penggabungan semua item data akumulator.
Menulis kernel pengurangan
#pragma rs reduce
menentukan kernel pengurangan dengan
menetapkan namanya serta nama dan peran fungsi yang membentuk
kernel tersebut. Semua fungsi tersebut harus bersifat
static
. Kernel pengurangan selalu membutuhkan fungsi accumulator
;
Anda dapat menghilangkan beberapa atau semua fungsi lainnya, bergantung pada apa yang Anda ingin kernel
lakukan.
#pragma rs reduce(kernelName) \ initializer(initializerName) \ accumulator(accumulatorName) \ combiner(combinerName) \ outconverter(outconverterName)
Arti item dalam #pragma
adalah sebagai berikut:
reduce(kernelName)
(wajib): Menentukan bahwa kernel pengurangan sedang ditentukan. Metode Java yang ditampilkan,reduce_kernelName
, akan meluncurkan kernel.initializer(initializerName)
(opsional): Menentukan nama fungsi penginisialisasi bagi kernel pengurangan ini. Saat Anda meluncurkan kernel, RenderScript akan memanggil fungsi ini satu kali untuk setiap item data akumulator. Fungsi ini harus ditentukan seperti berikut:static void initializerName(accumType *accum) { … }
accum
adalah pointer ke item data akumulator yang perlu diinisialisasi oleh fungsi ini.Jika Anda tidak menyediakan fungsi penginisialisasi, RenderScript akan menginisialisasi setiap item data akumulator ke nol (seolah-olah oleh
memset
), dan berperilaku seolah-olah terdapat fungsi penginisialisasi yang terlihat seperti berikut:static void initializerName(accumType *accum) { memset(accum, 0, sizeof(*accum)); }
accumulator(accumulatorName)
(wajib): Menetapkan nama fungsi akumulator untuk kernel pengurangan ini. Saat Anda meluncurkan kernel, RenderScript akan memanggil fungsi ini satu kali untuk setiap koordinat dalam input, untuk mengupdate item data akumulator dengan cara tertentu sesuai dengan input tersebut. Fungsi ini harus ditentukan seperti berikut:static void accumulatorName(accumType *accum, in1Type in1, …, inNType inN [, specialArguments]) { … }
accum
adalah pointer ke item data akumulator yang perlu diubah oleh fungsi ini.in1
hinggainN
adalah satu atau beberapa argumen yang otomatis diisi berdasarkan input yang diteruskan ke peluncuran kernel, satu argumen untuk setiap input. Fungsi akumulator dapat secara opsional menggunakan salah satu dari argumen khusus yang ada.Contoh kernel dengan beberapa input adalah
dotProduct
.combiner(combinerName)
(opsional): Menetapkan nama fungsi penggabung untuk kernel pengurangan ini. Setelah memanggil fungsi akumulator satu kali untuk setiap koordinat dalam input, RenderScript akan memanggil fungsi ini sebanyak yang diperlukan untuk menggabungkan semua item data akumulator menjadi satu item data akumulator. Fungsi ini harus ditentukan sebagai berikut:
static void combinerName(accumType *accum, const accumType *other) { … }
accum
adalah pointer ke item data akumulator "tujuan" yang perlu diubah oleh fungsi ini.other
adalah pointer ke item data akumulator "sumber" yang perlu "digabungkan" oleh fungsi ini menjadi*accum
.CATATAN: Terdapat kemungkinan bahwa
*accum
,*other
, atau keduanya telah diinisialisasi, tetapi belum pernah diteruskan ke fungsi akumulator; yaitu, salah satu atau keduanya tidak pernah diupdate menurut data input apa pun. Misalnya, dalam kernel findMinAndMax, fungsi penggabungfMMCombiner
akan secara eksplisit memeriksaidx < 0
karena menunjukkan item data akumulator, yang nilainya adalah INITVAL.Jika Anda tidak menyediakan fungsi penggabung, RenderScript akan menggunakan fungsi akumulator sebagai gantinya, berperilaku seolah-olah ada fungsi penggabung yang terlihat seperti berikut:
static void combinerName(accumType *accum, const accumType *other) { accumulatorName(accum, *other); }
Fungsi penggabung bersifat wajib jika kernel memiliki lebih dari satu input, jika jenis data input tidak sama dengan jenis data akumulator, atau jika fungsi akumulator menggunakan satu atau beberapa argumen khusus.
outconverter(outconverterName)
(opsional): Menetapkan nama fungsi outconverter untuk kernel pengurangan ini. Setelah menggabungkan semua item data akumulator, RenderScript akan memanggil fungsi ini untuk menentukan hasil pengurangan yang akan dikembalikan ke Java. Fungsi ini harus ditentukan seperti berikut:static void outconverterName(resultType *result, const accumType *accum) { … }
result
adalah pointer ke item data hasil (dialokasikan, tetapi tidak diinisialisasi oleh runtime RenderScript) yang perlu diinisialisasi oleh fungsi ini dengan hasil pengurangan. resultType adalah jenis item data tersebut, yang tidak harus sama dengan accumType.accum
adalah pointer ke item data akumulator akhir yang dihitung oleh fungsi penggabung.Jika Anda tidak menyediakan fungsi outconverter, RenderScript akan menyalin item data akumulator akhir ke item data hasil, yang berperilaku seolah-olah ada fungsi outconverter yang terlihat seperti berikut:
static void outconverterName(accumType *result, const accumType *accum) { *result = *accum; }
Jika Anda menginginkan jenis hasil yang berbeda dari jenis data akumulator, fungsi outconverter bersifat wajib.
Perlu diketahui bahwa kernel memiliki jenis input, jenis item data akumulator,
dan jenis hasil yang semuanya tidak harus sama. Sebagai contoh, dalam
kernel findMinAndMax, jenis
input long
, jenis item data akumulator MinAndMax
, dan jenis
hasil int2
semuanya berbeda.
Apa yang tidak boleh Anda asumsikan?
Anda tidak boleh mengandalkan jumlah item data akumulator yang dibuat oleh RenderScript untuk peluncuran kernel tertentu. Tidak ada jaminan bahwa dua peluncuran kernel yang sama dengan input yang sama akan membuat jumlah item data akumulator yang sama.
Anda tidak boleh bergantung pada urutan pemanggilan fungsi penginisialisasi, akumulator, dan penggabung oleh RenderScript; beberapa di antaranya bahkan dapat dipanggil oleh RenderScript secara paralel. Tidak ada jaminan bahwa dua peluncuran kernel yang sama dengan input yang sama akan mengikuti urutan yang sama. Satu-satunya jaminan adalah bahwa hanya fungsi penginisialisasi yang akan menganggap item data akumulator yang belum diinisialisasi. Contoh:
- Tidak ada jaminan bahwa semua item data akumulator akan diinisialisasi sebelum fungsi akumulator dipanggil, meskipun fungsi ini hanya akan dipanggil pada item data akumulator yang sudah diinisialisasi.
- Tidak ada jaminan terkait urutan penerusan Elemen input ke fungsi akumulator.
- Tidak ada jaminan bahwa fungsi akumulator telah dipanggil untuk semua Elemen input sebelum fungsi penggabung dipanggil.
Salah satu konsekuensinya adalah bahwa kernel findMinAndMax tidak bersifat deterministik; jika input berisi lebih dari satu kemunculan nilai minimum atau maksimum yang sama, tidak ada cara untuk mengetahui kemunculan mana yang akan ditemukan oleh kernel.
Apa yang harus Anda jamin?
Sistem RenderScript dapat memilih untuk mengeksekusi kernel dalam berbagai cara. Oleh karena itu, Anda harus mengikuti aturan tertentu untuk memastikan kernel berperilaku sesuai yang Anda inginkan. Jika tidak mengikuti aturan ini, Anda mungkin mendapatkan hasil yang salah, perilaku nondeterministik, atau error runtime.
Aturan di bawah sering kali menyatakan bahwa dua item data akumulator harus memiliki "nilai yang sama". Apa maksudnya? Jawabannya bergantung pada apa yang Anda inginkan dari kernel tersebut. Untuk pengurangan matematis seperti addint, biasanya akan cukup logis untuk mengartikan "sama" sebagai kesetaraan matematika. Untuk penelusuran “pilih apa saja” seperti findMinAndMax ("cari lokasi nilai input minimum dan maksimum") dengan kemungkinan kemunculan nilai input yang sama lebih dari satu kali, semua lokasi dari nilai input yang ditentukan harus dianggap "sama". Anda dapat menulis kernel yang serupa untuk "menemukan lokasi nilai input minimum dan maksimum paling kiri", dengan (katakanlah) nilai minimum di lokasi 100 lebih diutamakan daripada nilai minimum identik di lokasi 200; untuk kernel ini, "sama" berarti lokasi identik, bukan hanya nilai identik, dan fungsi akumulator serta penggabung harus berbeda dari yang ditujukan untuk findMinAndMax.
Fungsi penginisialisasi harus membuat nilai identitas. Artinya, jikaI
dan A
adalah item data akumulator yang diinisialisasi
oleh fungsi penginisialisasi, dan I
belum pernah diteruskan ke
fungsi akumulator (tetapi A
mungkin sudah pernah), maka
combinerName(&A, &I)
harus membiarkanA
tetap samacombinerName(&I, &A)
harus membiarkanI
tetap sama sepertiA
Contoh: Dalam kernel addint, item data akumulator diinisialisasi ke nol. Fungsi penggabung untuk kernel ini melakukan penambahan; nol adalah nilai identitas untuk penambahan.
Contoh: Dalam kernel findMinAndMax,
item data akumulator diinisialisasi
ke INITVAL
.
fMMCombiner(&A, &I)
membiarkanA
tetap sama, karenaI
adalahINITVAL
.fMMCombiner(&I, &A)
menetapkanI
keA
, karenaI
adalahINITVAL
.
Oleh karena itu, INITVAL
merupakan nilai identitas.
Fungsi penggabung harus bersifat komutatif. Artinya,
jika A
dan B
merupakan item data akumulator yang diinisialisasi
oleh fungsi penginisialisasi, dan mungkin telah diteruskan ke fungsi akumulator sebanyak nol
atau beberapa kali, maka combinerName(&A, &B)
harus
menetapkan A
ke nilai yang sama
yang ditetapkan oleh
combinerName(&B, &A)
untuk B
.
Contoh: Dalam kernel addint, fungsi penggabung menambahkan dua nilai item data akumulator; penambahan bersifat komutatif.
Contoh: Dalam kernel findMinAndMax,
fMMCombiner(&A, &B)
sama dengan
A = minmax(A, B)
, dan minmax
bersifat komutatif sehingga
fMMCombiner
juga demikian.
Fungsi penggabung harus bersifat asosiatif. Artinya,
jika A
, B
, dan C
adalah
item data akumulator yang diinisialisasi oleh fungsi penginisialisasi, dan mungkin telah diteruskan
ke fungsi akumulator sebanyak nol atau beberapa kali, dua urutan kode berikut harus
menetapkan A
ke nilai yang sama:
combinerName(&A, &B); combinerName(&A, &C);
combinerName(&B, &C); combinerName(&A, &B);
Contoh: Dalam kernel addint, fungsi penggabung menambahkan kedua nilai item data akumulator:
A = A + B A = A + C // Same as // A = (A + B) + C
B = B + C A = A + B // Same as // A = A + (B + C) // B = B + C
Penambahan bersifat asosiatif sehingga fungsi penggabung juga demikian.
Contoh: Dalam kernel findMinAndMax,
fMMCombiner(&A, &B)
A = minmax(A, B)
A = minmax(A, B) A = minmax(A, C) // Same as // A = minmax(minmax(A, B), C)
B = minmax(B, C) A = minmax(A, B) // Same as // A = minmax(A, minmax(B, C)) // B = minmax(B, C)
minmax
bersifat asosiatif sehingga fMMCombiner
juga demikian.
Bersama-sama, fungsi akumulator dan fungsi penggabung harus mematuhi aturan
folding dasar. Artinya, jika A
dan B
adalah item data akumulator, kemudian A
telah
diinisialisasi oleh fungsi penginisialisasi dan mungkin telah diteruskan ke fungsi akumulator
sebanyak nol atau beberapa kali, B
belum diinisialisasi, dan args adalah
daftar argumen input dan argumen khusus untuk panggilan tertentu ke fungsi
akumulator, maka dua urutan kode berikut harus menetapkan A
ke nilai yang sama:
accumulatorName(&A, args); // statement 1
initializerName(&B); // statement 2 accumulatorName(&B, args); // statement 3 combinerName(&A, &B); // statement 4
Contoh: Dalam kernel addint, untuk nilai input V:
- Pernyataan 1 sama dengan
A += V
- Pernyataan 2 sama dengan
B = 0
- Pernyataan 3 sama dengan
B += V
, yang sama denganB = V
- Pernyataan 4 sama dengan
A += B
, yang sama denganA += V
Pernyataan 1 dan 4 menetapkan A
ke nilai yang sama, sehingga kernel ini mematuhi
aturan folding dasar.
Contoh: Dalam kernel findMinAndMax, untuk nilai input V pada koordinat X:
- Pernyataan 1 sama dengan
A = minmax(A, IndexedVal(V, X))
- Pernyataan 2 sama dengan
B = INITVAL
- Pernyataan 3 sama dengan
yang, karena B adalah nilai awal, sama denganB = minmax(B, IndexedVal(V, X))
B = IndexedVal(V, X)
- Pernyataan 4 sama dengan
yang sama denganA = minmax(A, B)
A = minmax(A, IndexedVal(V, X))
Pernyataan 1 dan 4 menetapkan A
ke nilai yang sama, sehingga kernel ini mematuhi
aturan folding dasar.
Memanggil kernel pengurangan dari kode Java
Untuk kernel pengurangan dengan nama kernelName yang ditentukan
dalam file filename.rs
, terdapat tiga metode yang ditampilkan dalam
class ScriptC_filename
:
Kotlin
// Function 1 fun reduce_kernelName(ain1: Allocation, …, ainN: Allocation): javaFutureType // Function 2 fun reduce_kernelName(ain1: Allocation, …, ainN: Allocation, sc: Script.LaunchOptions): javaFutureType // Function 3 fun reduce_kernelName(in1: Array<devecSiIn1Type>, …, inN: Array<devecSiInNType>): javaFutureType
Java
// Method 1 public javaFutureType reduce_kernelName(Allocation ain1, …, Allocation ainN); // Method 2 public javaFutureType reduce_kernelName(Allocation ain1, …, Allocation ainN, Script.LaunchOptions sc); // Method 3 public javaFutureType reduce_kernelName(devecSiIn1Type[] in1, …, devecSiInNType[] inN);
Berikut adalah beberapa contoh untuk memanggil kernel addint:
Kotlin
val script = ScriptC_example(renderScript) // 1D array // and obtain answer immediately val input1 = intArrayOf(…) val sum1: Int = script.reduce_addint(input1).get() // Method 3 // 2D allocation // and do some additional work before obtaining answer val typeBuilder = Type.Builder(RS, Element.I32(RS)).apply { setX(…) setY(…) } val input2: Allocation = Allocation.createTyped(RS, typeBuilder.create()).also { populateSomehow(it) // fill in input Allocation with data } val result2: ScriptC_example.result_int = script.reduce_addint(input2) // Method 1 doSomeAdditionalWork() // might run at same time as reduction val sum2: Int = result2.get()
Java
ScriptC_example script = new ScriptC_example(renderScript); // 1D array // and obtain answer immediately int input1[] = …; int sum1 = script.reduce_addint(input1).get(); // Method 3 // 2D allocation // and do some additional work before obtaining answer Type.Builder typeBuilder = new Type.Builder(RS, Element.I32(RS)); typeBuilder.setX(…); typeBuilder.setY(…); Allocation input2 = createTyped(RS, typeBuilder.create()); populateSomehow(input2); // fill in input Allocation with data ScriptC_example.result_int result2 = script.reduce_addint(input2); // Method 1 doSomeAdditionalWork(); // might run at same time as reduction int sum2 = result2.get();
Metode 1 memiliki satu argumen
Allocation
input untuk setiap argumen input dalam fungsi
akumulator kernel. Runtime RenderScript akan melakukan pemeriksaan untuk memastikan bahwa semua Alokasi input
memiliki dimensi yang sama, dan bahwa jenis Element
dari
setiap Alokasi input cocok dengan argumen input yang sesuai dari prototipe
fungsi akumulator. Jika salah satu pemeriksaan ini gagal, RenderScript akan menampilkan pengecualian. Kernel
dieksekusi pada setiap koordinat dalam dimensi tersebut.
Metode 2 sama dengan Metode 1, kecuali bahwa Metode 2 menggunakan argumen
tambahan sc
yang dapat digunakan untuk membatasi eksekusi kernel pada
sebuah subset koordinat.
Metode 3 sama dengan Metode 1, tetapi metode ini menggunakan input array Java,
bukan menggunakan input Alokasi. Dengan demikian, Anda tidak perlu
lagi menulis kode untuk membuat Alokasi secara eksplisit dan menyalin data
kepadanya dari array Java. Namun, menggunakan Metode 3, bukan Metode 1, tidak akan meningkatkan
performa kode. Untuk setiap array input, Metode 3 akan membuat
Alokasi 1 dimensi sementara dengan jenis Element
yang sesuai dan
setAutoPadding(boolean)
yang diaktifkan, lalu menyalin array tersebut ke
Alokasi seolah-olah menggunakan metode copyFrom()
yang sesuai dari Allocation
. Metode ini kemudian memanggil Metode 1, yang meneruskan Alokasi
sementara tersebut.
CATATAN: Jika aplikasi Anda akan melakukan beberapa panggilan kernel dengan array yang sama, atau dengan array yang berbeda dari dimensi dan jenis Elemen yang sama, Anda dapat meningkatkan performa dengan membuat, mengisi, dan menggunakan kembali Alokasi sendiri secara eksplisit, bukan menggunakan Metode 3.
javaFutureType,
jenis pengembalian metode pengurangan yang ditampilkan, adalah
class bertingkat statis yang ditampilkan dalam class
ScriptC_filename
. Jenis ini merepresentasikan hasil eksekusi kernel pengurangan
selanjutnya. Untuk mendapatkan hasil aktual eksekusi, panggil
metode get()
class tersebut, yang mengembalikan nilai
berjenis javaResultType. get()
berjalan secara sinkron.
Kotlin
class ScriptC_filename(rs: RenderScript) : ScriptC(…) { object javaFutureType { fun get(): javaResultType { … } } }
Java
public class ScriptC_filename extends ScriptC { public static class javaFutureType { public javaResultType get() { … } } }
javaResultType ditentukan dari resultType milik fungsi outconverter. Kecuali resultType adalah jenis yang tidak ditandatangani (skalar, vektor, atau array), javaResultType adalah jenis Java yang sesuai secara langsung. Jika resultType adalah jenis yang tidak ditandatangani dan ada jenis bertanda tangan Java yang lebih besar, maka javaResultType adalah jenis bertanda tangan Java yang lebih besar tersebut; jika tidak, jenis Java yang sesuai secara langsung akan ditetapkan. Contoh:
- Jika resultType adalah
int
,int2
, atauint[15]
, maka javaResultType adalahint
,Int2
, atauint[]
. Semua nilai resultType dapat direpresentasikan oleh javaResultType. - Jika resultType adalah
uint
,uint2
, atauuint[15]
, maka javaResultType adalahlong
,Long2
, ataulong[]
. Semua nilai resultType dapat direpresentasikan oleh javaResultType. - Jika resultType adalah
ulong
,ulong2
, atauulong[15]
, javaResultType adalahlong
,Long2
, ataulong[]
. Ada beberapa nilai tertentu dari resultType yang tidak dapat direpresentasikan oleh javaResultType.
javaFutureType adalah jenis hasil selanjutnya yang sesuai dengan resultType milik fungsi outconverter.
- Jika resultType bukan merupakan jenis array, maka javaFutureType
adalah
result_resultType
. - Jika resultType merupakan array panjang Count dengan anggota dari jenis memberType,
maka javaFutureType adalah
resultArrayCount_memberType
.
Contoh:
Kotlin
class ScriptC_filename(rs: RenderScript) : ScriptC(…) { // for kernels with int result object result_int { fun get(): Int = … } // for kernels with int[10] result object resultArray10_int { fun get(): IntArray = … } // for kernels with int2 result // note that the Kotlin type name "Int2" is not the same as the script type name "int2" object result_int2 { fun get(): Int2 = … } // for kernels with int2[10] result // note that the Kotlin type name "Int2" is not the same as the script type name "int2" object resultArray10_int2 { fun get(): Array<Int2> = … } // for kernels with uint result // note that the Kotlin type "long" is a wider signed type than the unsigned script type "uint" object result_uint { fun get(): Long = … } // for kernels with uint[10] result // note that the Kotlin type "long" is a wider signed type than the unsigned script type "uint" object resultArray10_uint { fun get(): LongArray = … } // for kernels with uint2 result // note that the Kotlin type "Long2" is a wider signed type than the unsigned script type "uint2" object result_uint2 { fun get(): Long2 = … } // for kernels with uint2[10] result // note that the Kotlin type "Long2" is a wider signed type than the unsigned script type "uint2" object resultArray10_uint2 { fun get(): Array<Long2> = … } }
Java
public class ScriptC_filename extends ScriptC { // for kernels with int result public static class result_int { public int get() { … } } // for kernels with int[10] result public static class resultArray10_int { public int[] get() { … } } // for kernels with int2 result // note that the Java type name "Int2" is not the same as the script type name "int2" public static class result_int2 { public Int2 get() { … } } // for kernels with int2[10] result // note that the Java type name "Int2" is not the same as the script type name "int2" public static class resultArray10_int2 { public Int2[] get() { … } } // for kernels with uint result // note that the Java type "long" is a wider signed type than the unsigned script type "uint" public static class result_uint { public long get() { … } } // for kernels with uint[10] result // note that the Java type "long" is a wider signed type than the unsigned script type "uint" public static class resultArray10_uint { public long[] get() { … } } // for kernels with uint2 result // note that the Java type "Long2" is a wider signed type than the unsigned script type "uint2" public static class result_uint2 { public Long2 get() { … } } // for kernels with uint2[10] result // note that the Java type "Long2" is a wider signed type than the unsigned script type "uint2" public static class resultArray10_uint2 { public Long2[] get() { … } } }
Jika javaResultType adalah jenis objek (termasuk jenis array), setiap panggilan
ke javaFutureType.get()
pada instance yang sama akan mengembalikan objek
yang sama.
Jika javaResultType tidak dapat merepresentasikan semua nilai dari jenis resultType, dan
kernel pengurangan menghasilkan nilai yang tidak dapat direpresentasikan,
javaFutureType.get()
akan menampilkan pengecualian.
Metode 3 dan devecSiInXType
devecSiInXType adalah jenis Java yang sesuai dengan inXType dari argumen fungsi akumulator yang sesuai. Kecuali inXType adalah jenis yang tidak ditandatangani atau jenis vektor, devecSiInXType adalah jenis Java yang sesuai secara langsung. Jika inXType adalah jenis skalar yang tidak ditandatangani, maka devecSiInXType adalah jenis Java yang sesuai secara langsung dengan jenis skalar bertanda tangan yang berukuran sama. Jika inXType adalah jenis vektor yang ditandatangani, maka devecSiInXType adalah jenis Java yang sesuai secara langsung dengan jenis komponen vektor. Jika inXType adalah jenis vektor yang tidak ditandatangani, maka devecSiInXType adalah jenis Java yang sesuai secara langsung dengan jenis skalar bertanda tangan yang berukuran sama dengan jenis komponen vektor. Contoh:
- Jika inXType adalah
int
, maka devecSiInXType adalahint
. - Jika inXType adalah
int2
, maka devecSiInXType adalahint
. Array adalah representasi yang disatukan: Array memiliki Elemen skalar dua kali lebih banyak sebagaimana Alokasi memiliki Elemen vektor 2 komponen. Cara ini sama dengan cara kerja metodecopyFrom()
dariAllocation
. - Jika inXType adalah
uint
, maka deviceSiInXType adalahint
. Nilai yang ditandatangani dalam array Java diinterpretasikan sebagai nilai yang tidak ditandatangani dari bitpattern yang sama dalam Alokasi. Cara ini sama dengan cara kerja metodecopyFrom()
dariAllocation
. - Jika inXType adalah
uint2
, maka deviceSiInXType adalahint
. Cara ini merupakan kombinasi dari penangananint2
danuint
: Array merupakan representasi yang disatukan, dan nilai yang ditandatangani array Java diinterpretasikan sebagai nilai Elemen RenderScript yang tidak ditandatangani.
Perlu diketahui bahwa untuk Metode 3, jenis input ditangani secara berbeda dari jenis hasil:
- Input vektor skrip disatukan pada sisi Java, sedangkan hasil vektor skrip tidak.
- Input skrip yang tidak ditandatangani direpresentasikan sebagai input bertanda tangan yang berukuran sama di sisi
Java, sedangkan hasil skrip yang tidak ditandatangani direpresentasikan sebagai jenis bertanda tangan yang diperluas di sisi
Java (kecuali untuk
ulong
).
Contoh lain kernel pengurangan
#pragma rs reduce(dotProduct) \ accumulator(dotProductAccum) combiner(dotProductSum) // Note: No initializer function -- therefore, // each accumulator data item is implicitly initialized to 0.0f. static void dotProductAccum(float *accum, float in1, float in2) { *accum += in1*in2; } // combiner function static void dotProductSum(float *accum, const float *val) { *accum += *val; }
// Find a zero Element in a 2D allocation; return (-1, -1) if none #pragma rs reduce(fz2) \ initializer(fz2Init) \ accumulator(fz2Accum) combiner(fz2Combine) static void fz2Init(int2 *accum) { accum->x = accum->y = -1; } static void fz2Accum(int2 *accum, int inVal, int x /* special arg */, int y /* special arg */) { if (inVal==0) { accum->x = x; accum->y = y; } } static void fz2Combine(int2 *accum, const int2 *accum2) { if (accum2->x >= 0) *accum = *accum2; }
// Note that this kernel returns an array to Java #pragma rs reduce(histogram) \ accumulator(hsgAccum) combiner(hsgCombine) #define BUCKETS 256 typedef uint32_t Histogram[BUCKETS]; // Note: No initializer function -- // therefore, each bucket is implicitly initialized to 0. static void hsgAccum(Histogram *h, uchar in) { ++(*h)[in]; } static void hsgCombine(Histogram *accum, const Histogram *addend) { for (int i = 0; i < BUCKETS; ++i) (*accum)[i] += (*addend)[i]; } // Determines the mode (most frequently occurring value), and returns // the value and the frequency. // // If multiple values have the same highest frequency, returns the lowest // of those values. // // Shares functions with the histogram reduction kernel. #pragma rs reduce(mode) \ accumulator(hsgAccum) combiner(hsgCombine) \ outconverter(modeOutConvert) static void modeOutConvert(int2 *result, const Histogram *h) { uint32_t mode = 0; for (int i = 1; i < BUCKETS; ++i) if ((*h)[i] > (*h)[mode]) mode = i; result->x = mode; result->y = (*h)[mode]; }
Contoh kode lainnya
Contoh BasicRenderScript, RenderScriptIntrinsic, dan Hello Compute secara lebih lanjut menunjukkan cara menggunakan API yang dibahas di halaman ini.