RenderScript, işlem bakımından yoğun görevleri Android'de yüksek performansta çalıştırmak için geliştirilmiş bir çerçevedir. RenderScript, öncelikli olarak veri paralel hesaplamaya yönelik olarak tasarlanmıştır ancak seri iş yükleri de faydalı olabilir. RenderScript çalışma zamanı, bir cihazda mevcut olan işlemciler arasında (ör. çok çekirdekli CPU'lar ve GPU'lar) çalışmayı paralel hale getirir. Bu sayede işleri programlamak yerine algoritmaları ifade etmeye odaklanabilirsiniz. RenderScript özellikle resim işleme, hesaplamalı fotoğrafçılık veya bilgisayar görüşü gerçekleştiren uygulamalar için kullanışlıdır.
RenderScript'i kullanmaya başlamak için anlamanız gereken iki temel kavram vardır:
- Dil, yüksek performanslı işlem kodu yazmak için C99'dan türetilmiş bir dildir. RenderScript Kernel yazma bölümünde bu programın, işlem çekirdeklerini yazmak için nasıl kullanılacağı açıklanmaktadır.
- control API, RenderScript kaynaklarının ömrünü yönetmek ve çekirdek yürütmeyi kontrol etmek için kullanılır. Üç farklı dilde kullanılabilir: Java, Android NDK'da C++ ve C99 ile türetilen çekirdek dilinin kendisi. Java Code'dan RenderScript'in kullanılması ve Single-Source RenderScript'in sırasıyla birinci ve üçüncü seçenekleri açıklanmaktadır.
RenderScript Çekirdeği Yazma
RenderScript çekirdeği genellikle <project_root>/src/rs
dizinindeki bir .rs
dosyasında bulunur; her .rs
dosyası komut dosyası olarak adlandırılır. Her komut dosyası kendi çekirdek, işlev ve değişken kümesini içerir. Bir komut dosyası şunları içerebilir:
- Bu komut dosyasında kullanılan RenderScript çekirdek dilinin sürümünü tanımlayan bir pragma bildirimi (
#pragma version(1)
). Şu anda tek geçerli değer 1'dir. - Bu komut dosyasında yansıtılan Java sınıflarının paket adını tanımlayan bir pragma bildirimi (
#pragma rs java_package_name(com.example.app)
)..rs
dosyanızın bir kitaplık projesinde değil, uygulama paketinizin bir parçası olması gerektiğini unutmayın. - Sıfır veya daha fazla çağrılabilir işlev. Çağırılabilir işlev, rastgele bağımsız değişkenlerle Java kodunuzdan çağırabileceğiniz tek iş parçacıklı bir RenderScript işlevidir. Bunlar genellikle daha büyük bir işleme ardışık düzenindeki ilk kurulum veya seri hesaplamalar için kullanışlıdır.
Sıfır veya daha fazla script globals. Genel komut dosyası, C'deki genel değişkene benzer. Komut dosyası genellerine Java kodundan erişebilirsiniz. Bunlar genellikle RenderScript çekirdeklerine parametre iletmek için kullanılır. Komut dosyası genelleri burada daha ayrıntılı olarak açıklanmıştır.
Sıfır veya daha fazla işlem çekirdeği. İşlem çekirdeği, RenderScript çalışma zamanını bir veri koleksiyonu genelinde paralel olarak yürütülmesi için yönlendirebileceğiniz bir işlev veya işlev koleksiyonudur. İki tür işlem çekirdeği vardır: çekirdekleri eşleme (foreach çekirdekler olarak da adlandırılır) ve azaltma çekirdekleri.
Eşleme çekirdeği, aynı boyutlara sahip
Allocations
koleksiyonu üzerinde çalışan paralel bir işlevdir. Varsayılan olarak, bu boyutlardaki her koordinat için bir kez yürütülür. Genellikle (ayrıca olmamakla birlikte)Allocations
giriş koleksiyonunu, her defasında birElement
girişAllocation
çıkışına dönüştürmek için kullanılır.Basit bir eşleme çekirdeği örneği aşağıda verilmiştir:
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; }
Bu, çoğu açıdan standart C işleviyle aynıdır. İşlev prototipine uygulanan
RS_KERNEL
özelliği, işlevin çağrılabilir bir işlev yerine RenderScript eşleme çekirdeği olduğunu belirtir.in
bağımsız değişkeni, çekirdek başlatmaya iletilenAllocation
girişine göre otomatik olarak doldurulur.x
vey
bağımsız değişkenleri aşağıda ele alınmıştır. Çekirdekten döndürülen değer,Allocation
çıktısında uygun konuma otomatik olarak yazılır. Varsayılan olarak bu çekirdek,Allocation
girişinin tamamında çalışır veAllocation
içindeElement
başına çekirdek işlevi bir kez yürütülür.Bir eşleme çekirdeğinde bir veya daha fazla giriş (
Allocations
), tek bir çıkış (Allocation
) veya her ikisi de olabilir. RenderScript çalışma zamanı, tüm giriş ve çıkış Allocations'larının aynı boyutlara sahip olup olmadığını veElement
giriş ve çıkış türlerinin, çekirdek prototipiyle eşleştiğinden emin olur. Bu kontrollerden biri başarısız olursa RenderScript bir istisna yapar.NOT: Android 6.0'dan (API düzeyi 23) önceki sürümlerde bir eşleme çekirdeğinde birden fazla
Allocation
girişi olamaz.Çekirdektekinden daha fazla giriş veya çıkışa (
Allocations
) ihtiyacınız varsa bu nesnelerrs_allocation
komut dosyası genellerine bağlanmalı versGetElementAt_type()
veyarsSetElementAt_type()
üzerinden bir çekirdekten ya da çağrılabilir işlevden erişilmelidir.NOT:
RS_KERNEL
, size kolaylık sağlamak için RenderScript tarafından otomatik olarak tanımlanan bir makrodur:#define RS_KERNEL __attribute__((kernel))
Azaltma çekirdeği, aynı boyutlardaki
Allocations
girdisi koleksiyonu üzerinde çalışan bir işlev ailesidir. Varsayılan olarak, toplayıcı işlevi bu boyutlardaki her koordinat için bir kez yürütülür. Genellikle (ancak özel olarak değil), bir girdi koleksiyonunuAllocations
tek bir değere "azaltmak" için kullanılır.Burada, girdisinin
Elements
kadarını toplayan basit bir azaltma çekirdeği örneğini görebilirsiniz:#pragma rs reduce(addint) accumulator(addintAccum) static void addintAccum(int *accum, int val) { *accum += val; }
Azaltma çekirdeği, kullanıcı tarafından yazılan bir veya daha fazla işlevden oluşur.
#pragma rs reduce
, adını (bu örnekteaddint
) ve çekirdeği oluşturan işlevlerin adlarını ve rollerini (bu örnekte biraccumulator
işleviaddintAccum
) belirterek çekirdeği tanımlamak için kullanılır. Bu tür işlevlerin tümüstatic
olmalıdır. Azaltma çekirdeği her zaman biraccumulator
işlevi gerektirir. Çekirdeğin ne yapmasını istediğinize bağlı olarak başka işlevleri de olabilir.Kısaltma çekirdek toplayıcısı işlevi,
void
değerini döndürmeli ve en az iki bağımsız değişkene sahip olmalıdır. İlk bağımsız değişken (bu örnekteaccum
), bir toplayıcı veri öğesine işaret eder. İkinci bağımsız değişken (bu örnekteval
), çekirdek başlatmaya iletilenAllocation
girişine göre otomatik olarak doldurulur. Toplayıcı veri öğesi, RenderScript çalışma zamanı tarafından oluşturulur. Varsayılan olarak sıfıra başlatılır. Varsayılan olarak bu çekirdek,Allocation
girişinin tamamında çalışır veAllocation
içindeElement
başına toplayıcı işlevi bir kez yürütülür. Varsayılan olarak, toplayıcı veri öğesinin son değeri azaltma işleminin sonucu olarak değerlendirilir ve Java'ya döndürülür. RenderScript çalışma zamanı, giriş Allocation türününElement
türünün toplayıcı işlevinin prototipiyle eşleştiğinden emin olur. Eşleşmezse RenderScript bir istisna gönderir.Azaltma çekirdeğinin bir veya daha fazla girişi
Allocations
var ancakAllocations
çıkışı yok.Azaltma çekirdekleri burada daha ayrıntılı olarak açıklanmıştır.
Azaltma çekirdekleri, Android 7.0 (API düzeyi 24) ve sonraki sürümlerde desteklenir.
Bir eşleme çekirdek işlevi veya bir azaltma çekirdek toplayıcı işlevi,
int
veyauint32_t
türünde olması gereken özel bağımsız değişkenlerix
,y
vez
kullanarak geçerli yürütmenin koordinatlarına erişebilir. Bu bağımsız değişkenler isteğe bağlıdır.Eşleme çekirdek işlevi veya bir azaltma çekirdek toplayıcı işlevi, rs_kernel_context türündeki isteğe bağlı özel
context
bağımsız değişkenini de alabilir. Mevcut yürütmenin belirli özelliklerini sorgulamak için kullanılan bir çalışma zamanı API'leri ailesi (ör. rsGetDimX) için gereklidir. (context
bağımsız değişkeni, Android 6.0 (API düzeyi 23) ve sonraki sürümlerde kullanılabilir.)- İsteğe bağlı bir
init()
işlevi.init()
işlevi, komut dosyası ilk kez örneklendiğinde RenderScript tarafından çalıştırılan özel bir çağrılabilir işlev türüdür. Bu, komut dosyası oluşturulurken bazı hesaplamaların otomatik olarak yapılmasına olanak tanır. - Sıfır veya daha fazla statik komut dosyası genel ve işlevleri. Statik bir komut dosyası global komut dosyası, Java kodundan erişilememesi dışında global komut dosyasına eşdeğerdir. Statik işlev, komut dosyasındaki herhangi bir çekirdekten veya çağrılabilir işlevden çağrılabilen ancak Java API'ye sunulmayan standart bir C işlevidir. Global bir komut dosyasına veya işleve Java kodundan erişilmesi gerekmiyorsa
static
olarak belirtilmesi önemle tavsiye edilir.
Kayan nokta hassasiyetini ayarlama
Bir komut dosyasında gereken kayan nokta hassasiyet düzeyini kontrol edebilirsiniz. Bu, tam IEEE 754-2008 standardı (varsayılan olarak kullanılır) gerekli olmadığında yararlı olur. Aşağıdaki pragmalar farklı bir kayan nokta hassasiyeti ayarlayabilir:
#pragma rs_fp_full
(hiçbir şey belirtilmezse varsayılan): IEEE 754-2008 standardında belirtildiği şekilde kayan nokta hassasiyeti gerektiren uygulamalar için.#pragma rs_fp_relaxed
: Katı IEEE 754-2008 uygunluğu gerektirmeyen ve daha az hassasiyeti tolere edebilen uygulamalar içindir. Bu mod, denormlar için sıfıra doğru temizlemeye ve sıfıra doğru yuvarlamaya olanak tanır.#pragma rs_fp_imprecise
: Katı hassasiyet gereksinimleri olmayan uygulamalar içindir. Bu mod, aşağıdakilerin yanı sırars_fp_relaxed
içindeki tüm özellikleri etkinleştirir:- -0,0 ile sonuçlanan işlemler +0,0 sonucunu döndürebilir.
- INF ve NAN işlemleri tanımlanmamış.
Çoğu uygulama, rs_fp_relaxed
uygulamasını herhangi bir yan etki olmadan kullanabilir. Bu yöntem, yalnızca esnek hassasiyetle kullanılabilen ek optimizasyonlar (SIMD CPU talimatları gibi) nedeniyle bazı mimariler için çok faydalı olabilir.
Java'dan RenderScript API'lerine erişme
RenderScript kullanan bir Android uygulaması geliştirirken API'ye Java'dan şu iki yöntemden biriyle erişebilirsiniz:
android.renderscript
- Bu sınıf paketindeki API'ler, Android 3.0 (API düzeyi 11) ve sonraki sürümleri çalıştıran cihazlarda kullanılabilir.android.support.v8.renderscript
- Bu paketteki API'ler, Android 2.3 (API düzeyi 9) ve sonraki sürümleri çalıştıran cihazlarda kullanmanıza olanak tanıyan bir Destek Kitaplığı üzerinden sunulur.
Bu işlemin artıları ve eksileri:
- Destek Kitaplığı API'lerini kullanırsanız, uygulamanızın RenderScript bölümü, kullandığınız RenderScript özelliklerinden bağımsız olarak Android 2.3 (API düzeyi 9) ve sonraki sürümleri çalıştıran cihazlarla uyumlu olacaktır. Böylece uygulamanız, yerel (
android.renderscript
) API'lere kıyasla daha fazla cihazda çalışabilir. - Belirli RenderScript özellikleri Destek Kitaplığı API'leri aracılığıyla kullanılamaz.
- Destek Kitaplığı API'lerini kullanırsanız yerel (
android.renderscript
) API'lere kıyasla (muhtemelen önemli ölçüde) daha büyük APK'lar elde edersiniz.
RenderScript Destek Kitaplığı API'larını kullanma
Destek Kitaplığı RenderScript API'lerini kullanabilmek için geliştirme ortamınızı yapılandırmanız gerekir. Bu API'leri kullanmak için aşağıdaki Android SDK araçları gereklidir:
- Android SDK Tools düzeltmesi 22.2 veya sonraki sürümler
- Android SDK Derleme Araçları düzeltmesi 18.1.0 veya üstü
Android SDK Derleme Araçları 24.0.0 sürümünden itibaren Android 2.2 (API düzeyi 8) artık desteklenmemektedir.
Bu araçların yüklü sürümünü Android SDK Yöneticisi'nde kontrol edip güncelleyebilirsiniz.
Destek Kitaplığı RenderScript API'lerini kullanmak için:
- Gerekli Android SDK sürümünün yüklü olduğundan emin olun.
- Android derleme işleminin ayarlarını, RenderScript ayarlarını içerecek şekilde güncelleyin:
- Uygulama modülünüzün uygulama klasöründe
build.gradle
dosyasını açın. - Dosyaya aşağıdaki RenderScript ayarlarını ekleyin:
Modern
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 } }
Yukarıda listelenen ayarlar, Android derleme işlemindeki belirli davranışları kontrol eder:
renderscriptTargetApi
- Oluşturulacak bayt kodu sürümünü belirtir. Bu değeri, kullandığınız tüm işlevleri sağlayabilecek en düşük API düzeyine ayarlamanızı verenderscriptSupportModeEnabled
değerinitrue
olarak ayarlamanızı öneririz. Bu ayar için geçerli değerler, 11 ile en son kullanıma sunulan API düzeyine kadar herhangi bir tam sayı değeridir. Uygulama manifestinizde belirtilen minimum SDK sürümünüz farklı bir değere ayarlanırsa bu değer göz ardı edilir ve derleme dosyasındaki hedef değer, minimum SDK sürümünü ayarlamak için kullanılır.renderscriptSupportModeEnabled
- Çalıştığı cihaz hedef sürümü desteklemiyorsa oluşturulan bayt kodunun uyumlu bir sürüme yedekleneceğini belirtir.
- Uygulama modülünüzün uygulama klasöründe
- RenderScript kullanan uygulama sınıflarınızda, Destek Kitaplığı sınıfları için bir içe aktarma ekleyin:
Kotlin
import android.support.v8.renderscript.*
Java
import android.support.v8.renderscript.*;
Java veya Kotlin Kodundan RenderScript'i kullanma
Java veya Kotlin kodundan RenderScript'in kullanılması, android.renderscript
ya da android.support.v8.renderscript
paketinde bulunan API sınıflarına bağlıdır. Çoğu uygulama aynı temel kullanım düzenini izler:
- Bir RenderScript bağlamını başlatın.
create(Context)
ile oluşturulanRenderScript
bağlamı, RenderScript'in kullanılabilmesini sağlar ve sonraki tüm RenderScript nesnelerinin ömrünü kontrol etmek için bir nesne sağlar. Bağlam oluşturmayı, farklı donanım parçalarında kaynak oluşturabileceğinden ve mümkünse bir uygulamanın kritik yolunda olmamalıdır. Bu nedenle uzun süreli bir işlem olarak düşünebilirsiniz. Genellikle bir uygulamada aynı anda yalnızca tek bir RenderScript bağlamı olur. - Komut dosyasına iletilecek en az bir
Allocation
oluşturun.Allocation
, sabit miktarda veri için depolama alanı sağlayan bir RenderScript nesnesidir. Komut dosyalarındaki çekirdekler, giriş ve çıkış olarakAllocation
nesneleri alır ve komut dosyası genelleri olarak bağlanıldığındarsGetElementAt_type()
versSetElementAt_type()
kullanarak çekirdeklerdekiAllocation
nesnelerine erişilebilir.Allocation
nesneleri, dizilerin Java kodundan RenderScript koduna ve RenderScript kodundan dizilere geçirilmesine izin verir.Allocation
nesneleri genelliklecreateTyped()
veyacreateFromBitmap()
kullanılarak oluşturulur. - Gerekli komut dosyalarını oluşturun. RenderScript'i kullanırken iki tür komut dosyası kullanabilirsiniz:
- ScriptC: Bunlar, yukarıdaki RenderScript Kernelü Yazma bölümünde açıklanan kullanıcı tanımlı komut dosyalarıdır. Her komut dosyasının, Java kodundan erişimi kolaylaştırmak için RenderScript derleyicisi tarafından yansıtılan bir Java sınıfı vardır. Bu sınıfın adı
ScriptC_filename
şeklindedir. Örneğin, yukarıdaki eşleme çekirdeğiinvert.rs
konumundaysa ve bir RenderScript bağlamı zatenmRenderScript
içinde bulunuyorsa komut dosyasını somutlaştırmak için Java veya Kotlin kodu şöyle olur:Kotlin
val invert = ScriptC_invert(renderScript)
Java
ScriptC_invert invert = new ScriptC_invert(renderScript);
- ScriptIntrinsic: Bunlar Gauss bulanıklığı, evrişim ve görüntü harmanlama gibi yaygın işlemler için yerleşik RenderScript çekirdekleridir. Daha fazla bilgi için
ScriptIntrinsic
alt sınıflarını inceleyin.
- ScriptC: Bunlar, yukarıdaki RenderScript Kernelü Yazma bölümünde açıklanan kullanıcı tanımlı komut dosyalarıdır. Her komut dosyasının, Java kodundan erişimi kolaylaştırmak için RenderScript derleyicisi tarafından yansıtılan bir Java sınıfı vardır. Bu sınıfın adı
- Atamaları verilerle doldurun. Ayırma işlemi,
createFromBitmap()
ile oluşturulan Ayırmalar haricinde ilk oluşturulduğunda boş verilerle doldurulur. Bir Ayırma doldurmak içinAllocation
içindeki "kopya" yöntemlerinden birini kullanın. "Kopyalama" yöntemleri eşzamanlıdır. - Gerekli tüm komut dosyası genellerini belirtin. Genelleri,
set_globalname
adlı aynıScriptC_filename
sınıfında yöntemler kullanarak ayarlayabilirsiniz. Örneğin,threshold
adlı birint
değişkeni ayarlamak içinset_threshold(int)
Java yöntemini,lookup
adlı birrs_allocation
değişkeni ayarlamak için deset_lookup(Allocation)
Java yöntemini kullanın.set
yöntemleri eşzamansızdır. - Uygun çekirdekleri ve çağrılabilir işlevleri başlatın.
Belirli bir çekirdeği başlatma yöntemleri,
forEach_mappingKernelName()
veyareduce_reductionKernelName()
adlı yöntemlerle aynıScriptC_filename
sınıfına yansıtılır. Bu lansmanlar eşzamansız yapılır. Çekirdekteki bağımsız değişkenlere bağlı olarak yöntem, hepsi aynı boyutlara sahip olması gereken bir veya daha fazla Ayırma işlemi alır. Varsayılan olarak, çekirdek bu boyutlardaki her koordinat üzerinde yürütülür. Bu koordinatların bir alt kümesi üzerinde bir çekirdeği yürütmek içinforEach
veyareduce
yöntemine son bağımsız değişken olarak uygun birScript.LaunchOptions
iletin.Aynı
ScriptC_filename
sınıfında açıklananinvoke_functionName
yöntemlerini kullanarak çağrılabilir işlevleri başlatın. Bu lansmanlar eşzamansız yapılır. Allocation
nesne ve javaFutureType nesnelerinden veri alın. Java kodundan birAllocation
öğesindeki verilere erişmek içinAllocation
uygulamasındaki "kopya" yöntemlerinden birini kullanarak bu verileri Java'ya geri kopyalamanız gerekir. Çekirdek azaltma işleminin sonucunu elde etmek içinjavaFutureType.get()
yöntemini kullanmanız gerekir. "copy" (kopya) veget()
yöntemleri eşzamanlıdır.- RenderScript bağlamını ayırın. RenderScript bağlamını
destroy()
ile veya RenderScript bağlam nesnesinin çöp toplama yapmasına izin vererek kaldırabilirsiniz. Bu, söz konusu bağlama ait herhangi bir nesnenin ileride istisna bırakmak için kullanılmasına neden olur.
Eşzamansız yürütme modeli
Yansıtılan forEach
, invoke
, reduce
ve set
yöntemleri eşzamansızdır. Her biri, istenen işlemi tamamlamadan önce Java'ya dönebilir. Ancak, her bir işlem başlatılma sırasına göre serileştirilir.
Allocation
sınıfı, Ayırmalara ve Tahsislerden alınan verileri kopyalamak için "kopyalama" yöntemleri sunar. "Kopyalama" yöntemi eşzamanlıdır ve aynı Ayırma'ya dokunan yukarıdaki eşzamansız işlemlerin herhangi birine göre serileştirilir.
Yansıtılan javaFutureType sınıfları, bir azaltma sonucunu almak için bir get()
yöntemi sağlar. get()
eşzamanlıdır ve azaltmaya (eşzamansız olan) göre serileştirilir.
Tek Kaynaklı RenderScript
Android 7.0 (API düzeyi 24), Single-Source RenderScript adlı yeni bir programlama özelliğini kullanıma sunar. Bu özellikte çekirdekler, Java yerine tanımlandıkları komut dosyasından başlatılır. Bu yaklaşım, şu anda bu bölümde kısa ve öz olması için sadece "çekirdek" olarak adlandırılan çekirdeklerin eşlenmesiyle sınırlıdır. Bu yeni özellik, komut dosyasının içinden
rs_allocation
türünde ayırmalar oluşturmayı da destekler. Birden fazla çekirdek başlatması gerekse bile bir algoritmanın tamamını yalnızca bir komut dosyası içinde uygulamak artık mümkün.
İki yönlü avantajı vardır: Bir algoritmanın tek bir dilde uygulanmasını sağladığı için daha okunabilir kod; birden fazla çekirdek lansmanında Java ile RenderScript arasında daha az geçiş yapıldığından potansiyel olarak daha hızlı kod.
Tek Kaynak RenderScript'te çekirdekleri
RenderScript Çekirdeği Yazma konusunda açıklandığı gibi yazarsınız. Daha sonra, başlatmak için
rsForEach()
çağrısı yapan bir çağrılabilir işlev yazarsınız. Bu API, ilk parametre olarak bir çekirdek işlevini ve ardından giriş ve çıkış ayırmalarını alır. Benzer bir API
rsForEachWithOptions()
, çekirdek işlevinin işlemesi için giriş ve çıkış ayırmalarındaki öğelerin bir alt kümesini belirten
rs_script_call_t
türünde ek bir bağımsız değişken kullanır.
RenderScript hesaplamasını başlatmak için Java'dan çağrılabilir işlevi çağırırsınız.
Java Code'dan RenderScript'i kullanma bölümündeki adımları uygulayın.
Uygun çekirdekleri başlatma adımında, invoke_function_name()
kullanarak çağrılabilir işlevi çağırın. Bu işlem, çekirdeklerin başlatılması da dahil olmak üzere tüm hesaplamayı başlatır.
Ayırmalar genellikle bir çekirdek lansmanındaki ara sonuçları kaydedip diğerine aktarmak için gereklidir.
rsCreateAllocation() kullanarak bunları oluşturabilirsiniz. Bu API'nin kullanımı kolay bir biçimi
rsCreateAllocation_<T><W>(…)
'dir. Burada T bir öğenin veri türünü, W ise öğenin vektör genişliğini belirtir. API, X, Y ve Z boyutlarındaki boyutları bağımsız değişken olarak alır. 1D veya 2D ayırmalarda Y veya Z boyutu atlanabilir. Örneğin rsCreateAllocation_uchar4(16384)
, her biri uchar4
türünde olan 16384 öğelerin 1D tahsisini oluşturur.
Ayırmalar sistem tarafından otomatik olarak yönetilir. Bunları açıkça serbest bırakmanız veya serbest bırakmanız gerekmez. Ancak temel ayırma için herkese açık kullanıcı adına artık ihtiyacınız olmadığını belirtmek üzere
rsClearObject(rs_allocation* alloc)
yöntemini çağırabilirsiniz. Böylece sistem, kaynakları mümkün olan en kısa sürede serbest bırakabilir.alloc
RenderScript Çekirdeği Yazma bölümü, bir görüntüyü ters çeviren örnek bir çekirdek içerir. Aşağıdaki örnekte, Tek Kaynaklı RenderScript kullanılarak bir resme birden fazla efekt uygulanması için bu öğe genişletilmektedir. Bu çekirdek, renkli bir resmi siyah beyaza dönüştüren greyscale
adlı başka bir çekirdeği içerir. Çağırılabilir process()
işlevi, bu iki çekirdeği art arda bir giriş görüntüsüne uygulayarak bir çıkış görüntüsü üretir. Hem giriş hem de çıkış için ayırmalar,
rs_allocation
türünde bağımsız değişkenler olarak aktarılır.
// 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); }
process()
işlevini Java veya Kotlin'den aşağıdaki gibi çağırabilirsiniz:
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);
Bu örnekte, iki çekirdek lansmanı içeren bir algoritmanın, RenderScript dilinin kendisinde nasıl tamamen uygulanabileceği gösterilmektedir. Tek Kaynaklı RenderScript olmadan, her iki çekirdeği de Java kodundan başlatmanız ve çekirdek başlangıçlarını çekirdek tanımlarından ayırıp algoritmanın tamamını anlamayı zorlaştırmanız gerekir. Tek Kaynaklı RenderScript kodunun okunması daha kolay olmanın yanı sıra çekirdek lansmanlarında Java ile komut dosyası arasındaki geçişi de ortadan kaldırır. Bazı yinelemeli algoritmalar, çekirdekleri yüzlerce kez başlatabilir ve bu tür geçişin ek yükünü önemli ölçüde artırır.
Komut Dosyası Genelleri
Komut dosyası global, bir komut dosyası (.rs
) dosyasındaki static
olmayan normal bir genel değişkendir. filename.rs
dosyasında tanımlanmış var adlı bir global komut dosyası için ScriptC_filename
sınıfına yansıtılan bir get_var
yöntemi olacaktır. Genel boyut const
olmadığı sürece set_var
yöntemi de olacaktır.
Belirli bir global komut dosyasının iki ayrı değeri vardır: Java değeri ve script değeri. Bu değerler aşağıdaki gibi davranır:
- var komut dosyasında statik bir başlatıcı varsa bu işlev hem Java hem de komut dosyasında var başlangıç değerini belirtir. Aksi takdirde, bu başlangıç değeri sıfır olur.
- Komut dosyası içindeki var öğesine erişir ve komut dosyası değerini okur ve yazar.
get_var
yöntemi, Java değerini okur.set_var
yöntemi (varsa) Java değerini hemen yazar ve komut dosyası değerini eşzamansız olarak yazar.
NOT: Bu, komut dosyasındaki herhangi bir statik başlatıcı hariç olmak üzere, bir komut dosyasının içinden genel bir başlatıcıya yazılan değerlerin Java tarafından görülmeyeceği anlamına gelir.
Derinliği Azaltma Taneleri
Azaltma, bir veri koleksiyonunu tek bir değer altında birleştirme işlemidir. Bu, aşağıdaki gibi uygulamaların kullanıldığı paralel programlamada yararlı bir temel öğedir:
- tüm veriler üzerinden toplamı veya çarpımı hesaplayarak
- mantıksal işlemleri (
and
,or
,xor
) tüm veriler üzerinde - verilerdeki minimum veya maksimum değeri bulma
- belirli bir değeri arama veya verilerdeki belirli bir değerin koordinatı için arama yapma
RenderScript, Android 7.0 (API düzeyi 24) ve sonraki sürümlerde kullanıcı tarafından yazılan azaltma algoritmalarının verimli olmasını sağlamak için azaltma çekirdeklerini destekler. 1, 2 veya 3 boyutlu girişlerde çekirdek azaltma özelliğini başlatabilirsiniz.
Yukarıdaki örnekte basit bir addint azaltma çekirdeği gösterilmektedir.
1 boyutlu bir Allocation
'da minimum ve maksimum long
değerlerinin konumlarını bulan daha karmaşık bir findMinAndMax azaltma çekirdeğini burada görebilirsiniz:
#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; }
NOT: Kısaltma amaçlı daha fazla çekirdek örneği burada bulabilirsiniz.
RenderScript çalışma zamanı, bir azaltma çekirdeğini çalıştırmak için kısaltma işleminin durumunu korumak amacıyla toplayıcı veri öğeleri adı verilen bir veya daha fazla değişken oluşturur. RenderScript çalışma zamanı, performansı en üst düzeye çıkarmak için toplayıcı veri öğelerinin sayısını seçer. Toplayıcı veri öğelerinin türü (accumType), çekirdeğin toplayıcı işlevi tarafından belirlenir. Bu işlevin ilk bağımsız değişkeni, bir toplayıcı veri öğesinin işaretçisidir. Varsayılan olarak her toplayıcı veri öğesi sıfır olarak başlatılır (memset
tarafından olduğu gibi). Ancak farklı bir şey yapmak için bir başlatıcı işlevi yazabilirsiniz.
Örnek: addint çekirdeğinde, giriş değerlerini toplamak için toplayıcı veri öğeleri (int
türünde) kullanılır. Başlatıcı işlevi olmadığından her bir toplayıcı veri öğesi sıfır olarak başlatılır.
Örnek: findMinAndMax çekirdeğinde, toplayıcı veri öğeleri (MinAndMax
türünde) şimdiye kadar bulunan minimum ve maksimum değerleri izlemek için kullanılır. Bunları sırasıyla LONG_MAX
ve LONG_MIN
olarak ayarlamak ve bu değerlerin konumlarını -1 olarak ayarlamak için bir başlatıcı işlevi vardır. Böylece değerler, işlenen girişin (boş) bölümünde mevcut değildir.
RenderScript, girişlerdeki her koordinat için toplayıcı fonksiyonunuzu bir kez çağırır. Tipik olarak işleviniz, toplayıcı veri öğesini girişe göre bir şekilde güncellemelidir.
Örnek: addint çekirdeğinde toplayıcı işlevi, bir giriş Öğesinin değerini toplayıcı veri öğesine ekler.
Örnek: findMinAndMax çekirdeğinde toplayıcı işlevi, bir giriş Öğesinin değerinin, toplayıcı veri öğesinde kaydedilen minimum değere eşit veya bu değerden küçük olup olmadığını ve/veya toplayıcı veri öğesinde kaydedilen maksimum değere eşit ya da bu değerden büyük olup olmadığını kontrol eder ve toplayıcı veri öğesini uygun şekilde günceller.
Toplayıcı işlevi, girişlerdeki her koordinat için bir kez çağrıldıktan sonra, RenderScript toplayıcı veri öğelerini tek bir toplayıcı veri öğesi olarak birleştirmelidir. Bunun için bir birleştirici işlevi yazabilirsiniz. Toplayıcı işlevinde tek bir giriş varsa ve özel bağımsız değişkenler yoksa bir birleştirici işlevi yazmanız gerekmez. RenderScript, toplayıcı veri öğelerini birleştirmek için toplayıcı işlevini kullanır. (Bu varsayılan davranış istediğiniz gibi değilse yine de bir birleştirici işlevi yazabilirsiniz.)
Örnek: addint çekirdeğinde birleştirici işlevi bulunmadığından toplayıcı işlevi kullanılacaktır. Bu, doğru davranıştır, çünkü bir değer koleksiyonunu iki parçaya bölersek ve bu iki parçadaki değerleri ayrı ayrı toplarsak bu iki toplamı toplamak tüm koleksiyonun toplanmasıyla aynı olur.
Örnek: findMinAndMax çekirdeğinde birleştirici işlevi, *val
"kaynak" toplayıcı veri öğesinde kaydedilen minimum değerin, *accum
"hedef" toplayıcı veri öğesinde kaydedilen minimum değerden düşük olup olmadığını kontrol eder ve buna göre *accum
günceller. Maksimum değer için benzer işler yapar. Bu işlem, *accum
giriş değerlerinin bir kısmı *accum
ve bazıları *val
altında toplanmış olsaydı*accum
Toplayıcı veri öğelerinin tümü birleştirildikten sonra RenderScript, Java'ya dönmek için yapılan azaltma işleminin sonucunu belirler. Bunu yapmak için bir dış dönüştürücü işlevi yazabilirsiniz. Birleştirilmiş toplayıcı veri öğelerinin nihai değerinin indirgenmenin sonucu olmasını istiyorsanız bir dış dönüştürücü işlevi yazmanız gerekmez.
Örnek: addint çekirdeğinde dış dönüştürücü işlevi yoktur. Birleştirilmiş veri öğelerinin nihai değeri, girdideki tüm unsurların toplamıdır. Bu, döndürmek istediğimiz değerdir.
Örnek: findMinAndMax çekirdeğinde, dış dönüştürücü işlevi, tüm toplayıcı veri öğelerinin kombinasyonundan kaynaklanan minimum ve maksimum değerlerin konumlarını tutmak için bir int2
sonuç değeri başlatır.
Kısaltma çekirdeği yazma
#pragma rs reduce
, kısaltma çekirdeğinin adını ve çekirdeği oluşturan işlevlerin adlarını ve rollerini belirterek çekirdeğini tanımlar. Bu tür işlevlerin tümü static
olmalıdır. Azaltma çekirdeği her zaman bir accumulator
işlevi gerektirir. Çekirdeğin ne yapmasını istediğinize bağlı olarak diğer işlevlerin bazılarını veya tümünü atlayabilirsiniz.
#pragma rs reduce(kernelName) \ initializer(initializerName) \ accumulator(accumulatorName) \ combiner(combinerName) \ outconverter(outconverterName)
#pragma
’da yer alan öğelerin anlamları aşağıdaki gibidir:
reduce(kernelName)
(zorunlu): Bir kısaltma çekirdeğinin tanımlanmakta olduğunu belirtir. Yansıtılan bir Java yöntemi (reduce_kernelName
) çekirdeği başlatır.initializer(initializerName)
(isteğe bağlı): Bu azaltma çekirdeği için ilkleştirici işlevinin adını belirtir. Çekirdeği başlattığınızda, RenderScript her toplayıcı veri öğesi için bu işlevi bir kez çağırır. İşlev şu şekilde tanımlanmalıdır:static void initializerName(accumType *accum) { … }
accum
, bu işlevin başlatılması için bir toplayıcı veri öğesinin işaretçisidir.Bir başlatıcı işlevi sağlamazsanız RenderScript, aşağıdaki gibi görünen bir başlatıcı işlevi varmış gibi davranarak her bir toplayıcı veri öğesini sıfıra (
memset
ile olduğu gibi) başlatır:static void initializerName(accumType *accum) { memset(accum, 0, sizeof(*accum)); }
accumulator(accumulatorName)
(zorunlu): Bu azaltma çekirdeği için toplayıcı işlevinin adını belirtir. Çekirdeği başlattığınızda, RenderScript, bir toplayıcı veri öğesini girişlere göre bir şekilde güncellemek amacıyla girişlerdeki her koordinat için bu işlevi bir kez çağırır. Fonksiyon şu şekilde tanımlanmalıdır:static void accumulatorName(accumType *accum, in1Type in1, …, inNType inN [, specialArguments]) { … }
accum
, bu işlevin değiştirebileceği bir toplayıcı veri öğesinin işaretçisidir.in1
veinN
, çekirdek başlatmaya iletilen girişlere göre otomatik olarak doldurulan bir veya daha fazla bağımsız değişkendir (giriş başına bir bağımsız değişken). Toplayıcı işlevi, isteğe bağlı olarak özel bağımsız değişkenlerden herhangi birini alabilir.Çoklu girişli çekirdek örneği:
dotProduct
.combiner(combinerName)
(isteğe bağlı): Bu azaltma çekirdeği için birleştirici işlevinin adını belirtir. RenderScript, girdilerdeki her koordinat için toplayıcı işlevini bir kez çağırdıktan sonra, tüm toplayıcı veri öğelerini tek bir toplayıcı veri öğesinde birleştirmek için bu işlevi gerektiği kadar çağırır. Fonksiyon şu şekilde tanımlanmalıdır:
static void combinerName(accumType *accum, const accumType *other) { … }
accum
, bu işlevin değiştirebileceği "hedef" toplayıcı veri öğesinin işaretçisidir.other
, bu işlevin*accum
ile "birleştirmesi" için kullanılacak bir "kaynak" toplayıcı veri öğesine işaret eder.NOT:
*accum
,*other
veya her ikisi de başlatılmış ancak toplayıcı işlevine hiç geçirilmemiş olabilir. Diğer bir deyişle, bir veya her ikisi de herhangi bir giriş verisine göre güncellenmemiş olabilir. Örneğin, findMinAndMax çekirdeğindefMMCombiner
birleştirici işlevi,idx < 0
değerini açıkça kontrol eder. Çünkü bu, değeri INITVAL olan böyle bir toplayıcı veri öğesini belirtir.Birleştirici işlevi sağlamazsanız RenderScript bunun yerine aşağıdaki gibi bir birleştirici işlevi varmış gibi davranarak toplayıcı işlevini kullanır:
static void combinerName(accumType *accum, const accumType *other) { accumulatorName(accum, *other); }
Çekirdekte birden fazla giriş varsa, giriş verisi türü toplayıcı veri türüyle aynı değilse veya toplayıcı işlevi bir ya da daha fazla özel bağımsız değişken alırsa birleştirici işlevi zorunludur.
outconverter(outconverterName)
(isteğe bağlı): Bu azaltma çekirdeği için dış dönüştürücü işlevinin adını belirtir. RenderScript, toplayıcı veri öğelerinin tümünü birleştirdikten sonra, Java'ya dönmek için yapılan azaltma işleminin sonucunu belirlemek için bu işlevi çağırır. İşlev şu şekilde tanımlanmalıdır:static void outconverterName(resultType *result, const accumType *accum) { … }
result
, bu işlevin azaltma işleminin sonucuyla başlatılması için bir sonuç veri öğesinin (RenderScript çalışma zamanı tarafından tahsis edilen ancak başlatılmayan) bir işaretçidir. resultType, söz konusu veri öğesinin türüdür ve accumType ile aynı olması gerekmez.accum
, birleştirici işlevi tarafından hesaplanan son toplayıcı veri öğesine işaret eder.Bir dış dönüştürücü işlevi sağlamazsanız RenderScript, aşağıdaki gibi görünen bir dış dönüştürücü işlevi varmış gibi davranarak son toplayıcı veri öğesini sonuç veri öğesine kopyalar:
static void outconverterName(accumType *result, const accumType *accum) { *result = *accum; }
Toplayıcı veri türünden farklı bir sonuç türü istiyorsanız dış dönüştürücü işlevi zorunludur.
Çekirdeğin giriş türleri, toplayıcı veri öğe türü ve sonuç türü olduğunu ve hiçbirinin aynı olması gerekmediğini unutmayın. Örneğin, findMinAndMax çekirdeğinin giriş türü long
, toplayıcı veri öğesi türü MinAndMax
ve sonuç türü int2
birbirinden farklıdır.
Neleri varsayamazsınız?
Belirli bir çekirdek lansmanı için RenderScript tarafından oluşturulan toplayıcı verisi öğelerinin sayısına güvenmemelisiniz. Aynı girişle aynı çekirdeğin iki kez başlatılmasının aynı sayıda toplayıcı veri öğesi oluşturacağı garanti edilmez.
RenderScript'in başlatıcı, toplayıcı ve birleştirici işlevlerini çağırdığı sıraya güvenmemeniz gerekir, hatta bu işlevlerden bazılarını paralel olarak çağırabilir. Aynı çekirdeğin aynı girişe sahip iki lansmanının aynı sırayı takip edeceği garanti edilmez. Tek garanti, başlatılmamış toplayıcı veri öğesini yalnızca başlatıcı işlevinin görmesidir. Örneğin:
- Toplayıcı işlevi, çağrılmadan önce tüm toplayıcı verisi öğelerinin başlatılacağına dair bir garanti verilmez ancak yalnızca başlatılmış bir toplayıcı veri öğesinde çağrılır.
- Giriş öğelerinin toplayıcı işlevine aktarılma sırası konusunda herhangi bir garanti yoktur.
- Birleştirici işlevi çağrılmadan önce toplayıcı işlevinin tüm giriş öğeleri için çağrılacağına dair herhangi bir garanti verilmez.
Bunun bir sonucu, findMinAndMax çekirdeğinin belirleyici olmamasıdır: Giriş, aynı minimum veya maksimum değerin birden fazla tekrarını içeriyorsa çekirdeğin hangi tekrarı bulacağını bilmenin yolu yoktur.
Neyi garanti etmeniz gerekir?
RenderScript sistemi çekirdeği birçok farklı şekilde yürütmeyi seçebildiğinden, çekirdeğinizin istediğiniz şekilde davrandığından emin olmak için belirli kuralları uygulamanız gerekir. Bu kurallara uymazsanız yanlış sonuçlar, belirleyici olmayan davranış veya çalışma zamanı hataları alabilirsiniz.
Aşağıdaki kurallarda genellikle iki toplayıcı veri öğesinin "aynı değere" sahip olması gerektiği belirtilmektedir. Bu ne anlama geliyor? Bu, çekirdeğin ne yapmasını istediğinize bağlıdır. addint gibi matematiksel bir indirgeme söz konusu olduğunda, "aynı"nın matematiksel eşitliği ifade etmesi genellikle mantıklıdır. Aynı giriş değerlerinin birden fazla kez bulunabileceği findMinAndMax ("minimum ve maksimum giriş değerlerinin konumunu bul") gibi bir "herhangi birini seç" araması için belirli bir giriş değerinin tüm konumları "aynı" olarak kabul edilmelidir. "En soldaki minimum ve maksimum giriş değerlerinin konumunu bulmak" için benzer bir çekirdek yazabilirsiniz. Burada, örneğin) 200 konumunda özdeş bir minimum değer yerine 100 konumundaki minimum değer tercih edilir; bu çekirdek için "aynı" ifadesi, yalnızca özdeş değer değil, aynı konum anlamına gelir ve toplayıcı ve birleştirici işlevleri için MinAndve için gerekenden farklı olmalıdır.
Başlatıcı işlevi bir kimlik değeri oluşturmalıdır. YaniI
ve A
, toplayıcı işlevi tarafından başlatılan toplayıcı veri öğeleriyse ve I
toplayıcı işlevine hiç geçirilmediyse (ancak A
geçmiş olabilir) bu durumda combinerName(&A, &I)
,A
değerini aynı bırakmalıdırcombinerName(&I, &A)
,I
değeriniA
ile aynı bırakmalıdır
Örnek: addint çekirdeğinde bir toplayıcı veri öğesi sıfır olarak başlatılır. Bu çekirdeğin birleştirici işlevi toplama işlemi gerçekleştirir. Sıfır, toplama için kimlik değeridir.
Örnek: findMinAndMax çekirdeğinde bir toplayıcı veri öğesi INITVAL
olarak başlatılır.
I
INITVAL
olduğundanfMMCombiner(&A, &I)
,A
değerini aynı bırakır.I
,INITVAL
olduğundanfMMCombiner(&I, &A)
,I
değeriniA
olarak ayarlar.
Bu nedenle INITVAL
aslında bir kimlik değeridir.
Birleştirici işlevi değişimli olmalıdır. Yani A
ve B
, başlatıcı işlevi tarafından başlatılan toplayıcı veri öğeleriyse ve bu öğeler toplayıcı işlevine sıfır veya daha fazla kez geçirildiyse combinerName(&A, &B)
, A
öğesini combinerName(&B, &A)
ile B
ayarlayan aynı değere ayarlamalıdır.
Örnek: addint çekirdeğinde birleştirici işlevi, iki toplayıcı veri öğesi değerini ekler; toplama ise değişmelidir.
Örnek: findMinAndMax çekirdeğinde fMMCombiner(&A, &B)
, A = minmax(A, B)
ile aynıdır. minmax
değişimli olduğu için fMMCombiner
da aynıdır.
Birleştirici işlevi ilişkisel olmalıdır. Yani A
, B
ve C
, başlatıcı işlevi tarafından başlatılan toplayıcı verisi öğeleriyse ve bu öğeler toplayıcı işlevine sıfır veya daha fazla kez geçmişse aşağıdaki iki kod dizisi A
öğesini aynı değere ayarlamalıdır:
combinerName(&A, &B); combinerName(&A, &C);
combinerName(&B, &C); combinerName(&A, &B);
Örnek: addint çekirdeğinde, birleştirici işlevi iki toplayıcı veri öğesi değerini ekler:
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
Toplama ilişki olduğu gibi birleştirici işlevi de ilişkilendirilmiştir.
Örnek: findMinAndMax çekirdeğinde
fMMCombiner(&A, &B)değeri,
A = minmax(A, B)ile aynıdır. İki dizi,
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
ilişkilendirildiği için fMMCombiner
de ilişkilendirme yoluyla ilişkilendirme yapıyor.
Toplayıcı işlevi ve birleştirici işlevi birlikte temel katlama kuralına uymalıdır. Yani, A
ve B
toplayıcı veri öğeleriyse A
, başlatıcı işlevi tarafından başlatıldıysa ve toplayıcı işlevine sıfır veya daha fazla kez geçirildiyse, B
başlatılmamış ve bağımsız değişkenler toplayıcı işlevine yapılan belirli bir çağrıya ait giriş bağımsız değişkenleri ve özel bağımsız değişkenlerin listesidir. Bu durumda aşağıdaki iki kod dizisi A
olarak ayarlanmalıdır:
accumulatorName(&A, args); // statement 1
initializerName(&B); // statement 2 accumulatorName(&B, args); // statement 3 combinerName(&A, &B); // statement 4
Örnek: addint çekirdeğinde, V giriş değeri için:
- İfade 1,
A += V
ile aynı - İfade 2 ile
B = 0
aynı - İfade 3,
B += V
ile, yaniB = V
ile aynıdır - İfade 4,
A += B
ile, yaniA += V
ile aynıdır
İfade 1 ve 4, A
değerini aynı değere ayarlar ve böylece bu çekirdek, temel katlama kuralına uyar.
Örnek: findMinAndMax çekirdeğinde, X koordinatındaki V giriş değeri için:
- İfade 1,
A = minmax(A, IndexedVal(V, X))
ile aynı - İfade 2 ile
B = INITVAL
aynı - İfade 3,
B = minmax(B, IndexedVal(V, X))
ile aynıdır çünkü B başlangıç değeri olduğu içinB = IndexedVal(V, X)
ile aynıdır. - İfade 4,
A = minmax(A, IndexedVal(V, X))
ile aynı olanA = minmax(A, B)
ile aynıdır.
İfade 1 ve 4, A
değerini aynı değere ayarlar ve böylece bu çekirdek, temel katlama kuralına uyar.
Java kodundan azaltma çekirdeğini çağırma
filename.rs
dosyasında tanımlanan kernelName adlı bir azaltma çekirdeği için ScriptC_filename
sınıfında üç yöntem yansıtılır:
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);
Aşağıda, addint çekirdeğini çağırmaya ilişkin bazı örnekler verilmiştir:
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();
1. Yöntem, çekirdeğin toplayıcı işlevindeki her giriş bağımsız değişkeni için bir giriş Allocation
bağımsız değişkenine sahiptir. RenderScript çalışma zamanı, tüm giriş Allocations'larının aynı boyutlara sahip olup olmadığını ve giriş Ayırmalarının her birinin Element
türünün, toplayıcı işlevin prototipine ait ilgili giriş bağımsız değişkeniyle eşleşip eşleşmediğini kontrol eder. Bu kontrollerden herhangi biri başarısız olursa RenderScript bir istisna atar. Çekirdek, bu boyutlardaki her koordinat üzerinde yürütülür.
2. Yöntem, 1. Yöntem ile aynıdır. Tek fark, Yöntem 2'nin, çekirdek çalıştırmasını koordinatların bir alt kümesiyle sınırlamak için kullanılabilecek ek bir sc
bağımsız değişkenini almasıdır.
3. Yöntem, Ayırma girişleri yerine Java dizisi girişleri alması dışında 1. Yöntem ile aynıdır. Bu özellik, sizi açıkça bir Allocation oluşturmak ve bir Java dizisinden buna veri kopyalamak için kod yazma zahmetinden kurtarır. Ancak, Yöntem 1 yerine Yöntem 3'ün kullanılması kodun performansını
artırmaz. Yöntem 3, her bir giriş dizisi için uygun Element
türünü ve setAutoPadding(boolean)
etkin durumdayken geçici bir 1 boyutlu Ayırma oluşturur ve diziyi, Allocation
öğesinin uygun copyFrom()
yöntemini kullanarak Ayırma'ya kopyalar. Daha sonra bu geçici Ayırmaları ileterek Yöntem 1'i çağırır.
NOT: Uygulamanız, aynı dizi veya aynı boyutlar ve Öğe türünde farklı dizilerle birden fazla çekirdek çağrısı yapacaksa Ayırmaları 3. Yöntem kullanmak yerine açıkça oluşturarak, doldurarak ve yeniden kullanarak performansı artırabilirsiniz.
Yansıtılan azaltma yöntemlerinin dönüş türü olan javaFutureType, ScriptC_filename
sınıfı içinde yansıtılan statik bir iç içe sınıftır. Çekirdek çalıştırmasının gelecekteki sonucunu temsil eder. Çalıştırmanın gerçek sonucunu elde etmek için ilgili sınıfın get()
yöntemini çağırın. Bu yöntem, javaResultType türünde bir değer döndürür. get()
eşzamanlı.
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, outconverter işlevinin resultType'ından belirlenir. resultType, imzasız bir tür (skalar, vektör veya dizi) değilse javaResultType doğrudan karşılık gelen Java türüdür. resultType, imzasız bir türse ve daha büyük bir Java imzalı türü varsa javaResultType daha büyük Java imzalı türdür; aksi takdirde, doğrudan karşılık gelen Java türü olur. Örneğin:
- resultType,
int
,int2
veyaint[15]
ise javaResultTypeint
,Int2
veyaint[]
olur. Tüm resultType değerleri, javaResultType ile temsil edilebilir. - resultType,
uint
,uint2
veyauint[15]
ise javaResultTypelong
,Long2
veyalong[]
olur. Tüm resultType değerleri, javaResultType ile temsil edilebilir. - resultType,
ulong
,ulong2
veyaulong[15]
ise javaResultTypelong
,Long2
veyalong[]
olur. javaResultType ile temsil edilemeyen belirli resultType değerleri vardır.
javaFutureType, outconverter işlevinin resultType'ına karşılık gelen gelecekteki sonuç türüdür.
- resultType bir dizi türü değilse javaFutureType
result_resultType
olur. - resultType, memberType türündeki üyelerle birlikte Count uzunluğunda bir diziyse javaFutureType,
resultArrayCount_memberType
olur.
Örneğin:
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() { … } } }
javaResultType bir nesne türüyse (dizi türü dahil) aynı örnekteki her javaFutureType.get()
çağrısı aynı nesneyi döndürür.
javaResultType, resultType türündeki tüm değerleri temsil edemezse ve bir azaltma çekirdeği temsil edilemez bir değer oluşturursa javaFutureType.get()
bir istisna bildirir.
3. Yöntem ve devecSiInXType
devecSiInXType, toplayıcı işlevinin karşılık gelen bağımsız değişkeninin inXType'ına karşılık gelen Java türüdür. inXType imzasız veya vektör türü değilse devecSiInXType doğrudan karşılık gelen Java türüdür. inXType işaretsiz skaler bir türse devecSiInXType, aynı boyuttaki imzalı skaler türe doğrudan karşılık gelen Java türüdür. inXType işaretli bir vektör türüyse devecSiInXType, vektör bileşeni türüne doğrudan karşılık gelen Java türüdür. inXType imzasız bir vektör türüyse devecSiInXType, vektör bileşeni türüyle aynı boyuttaki imzalı skaler türe doğrudan karşılık gelen Java türüdür. Örneğin:
- inXType
int
ise devecSiInXTypeint
olur. - inXType
int2
ise devecSiInXTypeint
olur. Dizi, düzleştirilmiş bir gösterimdir: Ayırma'nın 2 bileşenli vektör Öğelerine kıyasla iki kat daha fazla skalar Öğe içerir. Bu,Allocation
kapsamındakicopyFrom()
yöntemlerinin çalışma şekliyle aynıdır. - inXType
uint
ise deviceSiInXTypeint
olur. Java dizisindeki imzalı değer, Ayırma'daki aynı bit kalıbının imzalanmamış değeri olarak yorumlanır. Bu,Allocation
kapsamındakicopyFrom()
yöntemlerinin çalışma şekliyle aynıdır. - inXType
uint2
ise deviceSiInXTypeint
olur. Bu,int2
veuint
işlenme yönteminin bir birleşimidir: Dizi, düzleştirilmiş bir gösterimdir ve Java dizisi imzalı değerleri, RenderScript'te imzalanmamış Öğe değerleri olarak yorumlanır.
3. Yöntem için giriş türlerinin, sonuç türlerinden farklı şekilde işlendiğini unutmayın:
- Bir komut dosyasının vektör girişi Java tarafında düz hale gelirken komut dosyasının vektör sonucu aynı değil.
- Bir komut dosyasının imzasız girişi, Java tarafında aynı boyutta bir imzalı giriş olarak gösterilirken, komut dosyasının imzasız sonucu Java tarafında genişletilmiş bir imzalı tür olarak temsil edilir (
ulong
durumu hariç).
Çekirdek azaltma örnekleri
#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]; }
Ek kod örnekleri
BasicRenderScript, RenderScriptIntrinsic ve Hello Compute örnekleri, bu sayfada ele alınan API'lerin kullanımını daha ayrıntılı bir şekilde gösterir.