RenderScript, işlem açısından yoğun görevleri Android'de yüksek performansta çalıştırmaya yönelik bir çerçevedir. RenderScript, öncelikle veri paralel hesaplama ile kullanılmak üzere tasarlanmıştır ancak seri iş yüklerinden de yararlanabilir. RenderScript çalışma zamanı, bir cihazda bulunan çok çekirdekli CPU'lar ve GPU'lar gibi işlemciler genelinde çalışmayı paralel hale getirir. Bu sayede, işleri planlamak yerine algoritmaları ifade etmeye odaklanabilirsiniz. RenderScript özellikle görüntü 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ı bilgi işlem kodu yazmak için C99'dan türetilen bir dildir. RenderScript Çekirdeği Yazma başlıklı makalede, işlem çekirdeği yazmak için bu dilin nasıl kullanılacağı açıklanmaktadır.
- Kontrol API'si, RenderScript kaynaklarının kullanım süresini yönetmek ve çekirdek yürütmeyi kontrol etmek için kullanılır. Üç farklı dilde kullanılabilir: Java, Android NDK'de C++ ve C99'dan türetilen çekirdek dili. Java Kodundan RenderScript Kullanma ve Tek Kaynaklı RenderScript, sırasıyla ilk ve üçüncü seçenekleri açıklar.
RenderScript çekirdeği yazma
RenderScript çekirdeği genellikle <project_root>/src/rs
dizinindeki bir .rs
dosyasında bulunur. Her .rs
dosyasına komut dosyası denir. Her komut dosyası kendi çekirdek, işlev ve değişkenleri grubunu içerir. Komut dosyaları ş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ından yansıtılan Java sınıflarının paket adını belirten bir pragma beyanı (
#pragma rs java_package_name(com.example.app)
)..rs
dosyanızın, bir kitaplık projesinde değil, uygulama paketinizin 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 ilk kurulum veya daha büyük bir işleme ardışık düzeni içindeki seri hesaplamalar için yararlıdır.
Sıfır veya daha fazla komut dosyası genel öğesi. Genel komut dosyası, C'deki genel değişkene benzer. Java kodundan komut dosyası genel değişkenlerine erişebilirsiniz. Bu değişkenler genellikle RenderScript çekirdeklerine parametre aktarmak için kullanılır. Komut dosyası global işlevleri burada daha ayrıntılı olarak açıklanmıştır.
Sıfır veya daha fazla işlem çekirdeği. Hesaplama çekirdeği, RenderScript çalışma zamanını bir veri koleksiyonunda paralel olarak yürütmesi için yönlendirebileceğiniz bir işlev veya işlev koleksiyonudur. İki tür hesaplama çekirdeği vardır: eşleme çekirdekleri (foreach çekirdekleri olarak da bilinir) ve azaltma çekirdekleri.
Eşleme çekirdeği, aynı boyutlara sahip bir
Allocations
koleksiyonunda çalışan paralel bir işlevdir. Varsayılan olarak bu işlev, bu boyutlardaki her koordinat için bir kez yürütülür. Genellikle (ancak yalnızca değil) bir girişAllocations
koleksiyonunu bir çıkışAllocation
koleksiyonuna dönüştürmek için kullanılırElement
.Aşağıda basit bir eşleme çekirdeği örneği 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; }
Çoğu açıdan bu, standart bir C işleviyle aynıdır. İşlev prototipine uygulanan
RS_KERNEL
özelliği, işlevin çağrılabilir bir işlev yerine bir 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ınmaktadır. Çekirdekten döndürülen değer, çıktıdaki uygun konumaAllocation
otomatik olarak yazılır. Varsayılan olarak bu çekirdek,Allocation
girişinin tamamında çalıştırılır veAllocation
içindeki herElement
için çekirdek işlevi bir kez yürütülür.Eşleme çekirdeğinde bir veya daha fazla giriş (
Allocations
), tek bir çıkış (Allocation
) veya her ikisi birden bulunabilir. RenderScript çalışma zamanında, tüm giriş ve çıkış tahsislerinin aynı boyutlara sahip olduğundan ve giriş ile çıkış tahsislerininElement
türlerinin çekirdeğin prototipiyle eşleştiğinden emin olmak için kontroller yapılır. Bu kontrollerden herhangi biri başarısız olursa RenderScript bir istisna oluşturur.NOT: Android 6.0'dan (API düzeyi 23) önceki bir eşleme çekirdeğinde birden fazla giriş bulunmayabilir
Allocation
.Çekirdeğin sahip olduğundan daha fazla giriş veya çıkış
Allocations
öğesine ihtiyacınız varsa bu nesnelerrs_allocation
komut dosyası genel değişkenlerine bağlanmalı versGetElementAt_type()
veyarsSetElementAt_type()
aracılığıyla 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 giriş
Allocations
koleksiyonunda ç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 yalnızca değil) birAllocations
giriş koleksiyonunu tek bir değere "indirgemek" için kullanılır.Girişindeki
Elements
değerini toplayan basit bir azaltım çekirdeği örneği aşağıda verilmiştir:#pragma rs reduce(addint) accumulator(addintAccum) static void addintAccum(int *accum, int val) { *accum += val; }
Azaltma çekirdeği, kullanıcı tarafından yazılmış bir veya daha fazla işlevden oluşur.
#pragma rs reduce
, çekirdeğin 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 tüm işlevlerstatic
olmalıdır. İndirme çekirdeği, her zamanaccumulator
işlevi gerektirir. Çekirdeğin ne yapmasını istediğinize bağlı olarak başka işlevler de içerebilir.Azaltma çekirdeği toplayıcı işlevi
void
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çidir ve ikinci 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 ve varsayılan olarak sıfır olarak başlatılır. Varsayılan olarak bu çekirdek,Allocation
girişinin tamamında çalıştırılır veAllocation
içindeki herElement
için toplayıcı işlevi bir kez yürütülür. Toplayıcı veri öğesinin nihai değeri varsayılan olarak azaltmanın sonucu olarak değerlendirilir ve Java'ya döndürülür. RenderScript çalışma zamanı, giriş Allocation'ınınElement
türünün toplayıcı işlevinin prototipiyle eşleşip eşleşmediğini kontrol eder. Eşleşmezse RenderScript bir istisna atar.Azaltma çekirdeğinin bir veya daha fazla girişi
Allocations
vardır ancak çıkışıAllocations
yoktur.Azaltma çekirdekleri hakkında daha fazla bilgiyi burada bulabilirsiniz.
Azaltma çekirdekleri, Android 7.0 (API düzeyi 24) ve sonraki sürümlerde desteklenir.
Bir eşleme çekirdek işlevi veya azaltma çekirdek toplayıcı işlevi,
int
veyauint32_t
türündekix
,y
vez
özel bağımsız değişkenlerini kullanarak geçerli yürütmenin koordinatlarına erişebilir. Bu bağımsız değişkenler isteğe bağlıdır.Bir eşleme çekirdeği işlevi veya azaltma çekirdeği toplayıcı işlevi, rs_kernel_context türündeki isteğe bağlı özel bağımsız değişkeni
context
de alabilir. Mevcut yürütmenin belirli özelliklerini (ör. rsGetDimX) sorgulamak için kullanılan bir çalışma zamanı API'si ailesi 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'in çalıştırdığı özel bir çağrılabilir işlev türüdür. Bu sayede, komut dosyası oluşturulurken bazı hesaplamalar otomatik olarak yapılabilir. - Sıfır veya daha fazla statik komut dosyası genel değişkeni ve işlevi. Statik komut dosyası global, Java kodundan erişilememesi dışında komut dosyası global ile aynıdır. Statik işlev, komut dosyasındaki herhangi bir çekirdekten veya çağrılabilir işlevden çağrılabilen ancak Java API'ye tabi olmayan standart bir C işlevidir. Bir komut dosyası genel değerine veya işlevine Java kodundan erişilmesi gerekmiyorsa
static
olarak tanımlanması ö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 değilse yararlıdır. Aşağıdaki pragmalar farklı bir kayan nokta hassasiyeti düzeyi 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
: IEEE 754-2008'e sıkı uyumluluk gerektirmeyen ve daha az hassasiyete izin verebilecek uygulamalar için. Bu mod, denorlar için sıfıra boşaltma ve sıfıra yuvarlama özelliğini sağlar.#pragma rs_fp_imprecise
: Kesinlik açısından katı şartlara sahip olmayan uygulamalar için. Bu mod,rs_fp_relaxed
ile aşağıdakilerin yanı sıra her şeyi etkinleştirir:- -0,0 ile sonuçlanan işlemler, bunun yerine +0.0 sonucunu döndürebilir.
- INF ve NAN üzerinde yapılan işlemler tanımlanmamıştır.
Çoğu uygulama, rs_fp_relaxed
'ü herhangi bir yan etki olmadan kullanabilir. Yalnızca gevşek hassasiyetle kullanılabilen ek optimizasyonlar (ör. SIMD CPU talimatları) nedeniyle bu, bazı mimarilerde çok yararlı olabilir.
Java'dan RenderScript API'lerine erişim
RenderScript kullanan bir Android uygulaması geliştirirken API'sine Java'dan iki şekilde 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 bir Destek Kitaplığı üzerinden kullanılabilir.
Bu yöntemin avantajları ve dezavantajları aşağıda açıklanmıştır:
- Destek Kitaplığı API'lerini kullanıyorsanız uygulamanızın RenderScript kısmı, hangi RenderScript özelliklerini kullandığınızdan bağımsız olarak Android 2.3 (API düzeyi 9) ve sonraki sürümleri çalıştıran cihazlarla uyumlu olur. Bu sayede uygulamanız, yerel (
android.renderscript
) API'leri kullandığınızdan daha fazla cihazda çalışabilir. - Bazı RenderScript özellikleri, Destek Kitaplığı API'leri üzerinden kullanılamaz.
- Destek kitaplığı API'lerini kullanırsanız yerel (
android.renderscript
) API'leri kullandığınızdan daha büyük (muhtemelen önemli ölçüde) APK'ler elde edersiniz.
RenderScript Destek Kitaplığı API'lerini kullanma
Destek Kitaplığı RenderScript API'lerini kullanmak için geliştirme ortamınızı bunlara erişecek şekilde yapılandırmanız gerekir. Bu API'leri kullanmak için aşağıdaki Android SDK araçları gereklidir:
- Android SDK Araçları düzeltme 22.2 veya üstü
- Android SDK Derleme Araçları 18.1.0 veya sonraki bir düzeltme sürümü
Android SDK Derleme Araçları 24.0.0'dan itibaren Android 2.2 (API düzeyi 8) artık desteklenmediğini unutmayın.
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ündeki
build.gradle
dosyasını açın. - Dosyaya aşağıdaki RenderScript ayarlarını ekleyin:
Eski
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 sürecindeki 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 seviyesine ayarlamanız verenderscriptSupportModeEnabled
değerinitrue
olarak ayarlamanızı öneririz. Bu ayar için geçerli değerler, 11 ile en son yayınlanan API düzeyine kadar olan herhangi bir tam sayı değeridir. Uygulama manifest dosyanızda belirtilen minimum SDK sürümünüz farklı bir değere ayarlanmışsa bu değer yoksayılır ve minimum SDK sürümünü ayarlamak için derleme dosyasında belirtilen hedef değer kullanılır.renderscriptSupportModeEnabled
: Oluşturulan ikili kodun, çalıştırıldığı cihaz hedef sürümü desteklemiyorsa uyumlu bir sürüme geri döneceğini belirtir.
- Uygulama modülünüzün uygulama klasöründeki
- RenderScript kullanan uygulama sınıflarınıza 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'i kullanmak için android.renderscript
ya da android.support.v8.renderscript
paketinde bulunan API sınıflarını kullanmanız gerekir. Çoğu uygulama aynı temel kullanım kalıbını izler:
- RenderScript bağlamı 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şturma, farklı donanım parçaları üzerinde kaynak oluşturabileceğinden potansiyel olarak uzun süren bir işlem olarak kabul edilmelidir. Bu aşama, mümkünse bir uygulamanın kritik yolunda yer almamalıdır. Genellikle, bir uygulamada aynı anda yalnızca tek bir RenderScript bağlamı bulunur. - Bir 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
nesnelerini alır. Komut dosyası genel olarak bağlandığındaAllocation
nesnelerine çekirdeklerdersGetElementAt_type()
versSetElementAt_type()
kullanılarak erişilebilir.Allocation
nesneleri, dizilerin Java kodundan RenderScript koduna ve bunun tersi şekilde aktarılmasına olanak tanır.Allocation
nesneleri genelliklecreateTyped()
veyacreateFromBitmap()
kullanılarak oluşturulur. - Gerekli tüm komut dosyalarını oluşturun. RenderScript'i kullanırken iki tür komut dosyası kullanabilirsiniz:
- ScriptC: Bunlar, yukarıdaki RenderScript Çekirdeği Yazma bölümünde açıklandığı gibi kullanıcı tanımlı komut dosyalarıdır. Her komut dosyasının, Java kodundan komut dosyasına erişmeyi 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
'tür. Örneğin, yukarıdaki eşleme çekirdeğiinvert.rs
içindeyse vemRenderScript
içinde zaten bir RenderScript bağlamı varsa komut dosyasını örneklendirecek Java veya Kotlin kodu şu şekilde olur:Kotlin
val invert = ScriptC_invert(renderScript)
Java
ScriptC_invert invert = new ScriptC_invert(renderScript);
- ScriptIntrinsic: Bunlar, Gauss bulanıklaştırma, topolojik dönüşüm ve resim birleştirme gibi yaygın işlemler için yerleşik RenderScript çekirdekleridir. Daha fazla bilgi için
ScriptIntrinsic
sınıfının alt sınıflarına bakın.
- ScriptC: Bunlar, yukarıdaki RenderScript Çekirdeği Yazma bölümünde açıklandığı gibi kullanıcı tanımlı komut dosyalarıdır. Her komut dosyasının, Java kodundan komut dosyasına erişmeyi 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ı
- Ayrıntıları verilerle doldurun.
createFromBitmap()
ile oluşturulan tahsisler hariç olmak üzere, bir tahsis ilk oluşturulduğunda boş veri ile doldurulur. Bir tahsisat doldurmak içinAllocation
içindeki "kopyala" yöntemlerinden birini kullanın. "Kopyala" yöntemleri eşzamanlı. - Gerekli tüm komut dosyası genel değişkenlerini ayarlayın.
set_globalname
adlı aynıScriptC_filename
sınıfındaki yöntemleri kullanarak genel değişkenleri 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 iseset_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 yayınlar eşzamanlı değildir. Çekirdeğe verilen bağımsız değişkenlere bağlı olarak yöntem, bir veya daha fazla Allocation alır. Bu Allocation'ların tümü aynı boyutlara sahip olmalıdır. Varsayılan olarak bir çekirdek, bu boyutlardaki her koordinat üzerinde yürütülür. Bir çekirdeği bu koordinatların bir alt kümesi üzerinde yürütmek içinforEach
veyareduce
yöntemine son bağımsız değişken olarak uygun birScript.LaunchOptions
gönderin.Aynı
ScriptC_filename
sınıfında yansıtılaninvoke_functionName
yöntemlerini kullanarak çağrılabilir işlevleri başlatın. Bu lansmanlar eşzamansızdır. Allocation
nesnelerinden ve javaFutureType nesnelerinden veri alın. Java kodundan birAllocation
'teki verilere erişmek içinAllocation
'teki "kopyala" yöntemlerinden birini kullanarak bu verileri Java'ya geri kopyalamanız gerekir. Azaltma çekirdeği sonucunu elde etmek içinjavaFutureType.get()
yöntemini kullanmanız gerekir. "Kopyala" veget()
yöntemleri eşzamanlıdır.- RenderScript bağlamını kaldırın. RenderScript bağlamını
destroy()
ile veya RenderScript bağlam nesnesine çöp toplama işleminin uygulanmasına izin vererek yok edebilirsiniz. Bu durum, söz konusu bağlama ait herhangi bir nesnenin daha sonra istisna alması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 bağımsız işlemler, başlatıldıkları sırayla serileştirilir.
Allocation
sınıfı, verileri Allocations'a ve Allocations'tan kopyalamak için "copy" yöntemleri sağlar. "Kopyala" yöntemi senkronizedir ve yukarıdaki aynı tahsisine dokunan eşzamansız işlemlerin herhangi birine göre serileştirilir.
Yansıtılan javaFutureType sınıfları, bir azaltmanın sonucunu elde etmek için get()
yöntemi sağlar. get()
eşzamanlı olup azaltmaya (eşzamansız) göre serileştirilir.
Tek Kaynaklı RenderScript
Android 7.0 (API seviyesi 24), çekirdeklerin Java yerine tanımlandıkları komut dosyasından başlatıldığı Tek Kaynaklı RenderScript adlı yeni bir programlama özelliğini kullanıma sunar. Bu yaklaşım şu anda harita çekimi çekirdekleriyle sınırlıdır. Bu bölümde kısalık olması için çekirdekler "çekirdekler" olarak adlandırılmıştır. Bu yeni özellik, komut dosyasının içinden
rs_allocation
türündeki tahsislerin oluşturulmasını da destekler. Artık birden fazla çekirdek başlatması gerekse bile tüm algoritmayı yalnızca bir komut dosyası içinde uygulamak mümkün.
Bunun iki avantajı vardır: Bir algoritmanın tek bir dilde uygulanmasını sağladığı için daha okunaklı kod ve birden fazla çekirdek başlatma işleminde Java ile RenderScript arasında daha az geçiş yapıldığı için potansiyel olarak daha hızlı kod.
Tek kaynaklı RenderScript'te, çekirdekleri
Bir RenderScript Çekirdeği Yazma bölümünde açıklandığı şekilde yazarsınız. Ardından, bunları başlatmak için
rsForEach()
işlevini çağıran bir çağrılabilir işlev yazarsınız. Bu API, ilk parametre olarak bir çekirdek işlevi, ardından giriş ve çıkış tahsislerini alır. Benzer bir API
rsForEachWithOptions()
, çekirdek işlevinin işleyeceği giriş ve çıkış tahsislerinden öğelerin bir alt kümesini belirten
rs_script_call_t
türündeki ek bir bağımsız değişken alır.
RenderScript hesaplamasını başlatmak için Java'dan çağrılabilir işlevi çağırırsınız.
Java Kodundan RenderScript Kullanma başlıklı makaledeki adımları uygulayın.
Uygun çekirdekleri başlat adımında, çekirdekleri başlatma dahil olmak üzere tüm hesaplamayı başlatacak olan invoke_function_name()
kullanarak çağrılabilir işlevi çağırın.
Ara sonuçları kaydetmek ve bir çekirdek başlatmadan diğerine aktarmak için genellikle ayırma işlemleri gerekir. Bu öğeleri
rsCreateAllocation() kullanarak oluşturabilirsiniz. Bu API'nin kullanımı kolay bir biçimi
rsCreateAllocation_<T><W>(…)
'dir. Burada T, öğenin veri türü, W ise öğenin vektör genişliğidir. API, X, Y ve Z boyutlarındaki boyutları bağımsız değişken olarak alır. 1D veya 2D atamalarda Y veya Z boyutunun boyutu atlanabilir. Örneğin, rsCreateAllocation_uchar4(16384)
her biri uchar4
türündeki 16.384 öğeden oluşan 1 boyutlu bir tahsis oluşturur.
Ayırma işlemleri sistem tarafından otomatik olarak yönetilir. Bunları açıkça yayınlamanız veya serbest bırakmanız gerekmez. Ancak temel ayırma için artık alloc
herkese açık kullanıcı adına ihtiyacınız olmadığını belirtmek amacıyla
rsClearObject(rs_allocation* alloc)
çağrısı yapabilirsiniz. Bu sayede sistem, kaynakları mümkün olan en kısa sürede serbest bırakabilir.
RenderScript Çekirdeği Yazma bölümünde, bir resmi tersine çeviren örnek bir çekirdek bulunur. Aşağıdaki örnek, Tek Kaynaklı RenderScript kullanarak bir resme birden fazla efekt uygulayacak şekilde genişletilir. Renkli bir resmi siyah beyaza dönüştüren başka bir çekirdek (greyscale
) içerir. Ardından, çağrılabilir process()
işlevi, bu iki çekirdeği art arda bir giriş görüntüsüne uygular ve bir çıkış görüntüsü oluşturur. Hem giriş hem de çıkış için ayrılan kaynaklar,
rs_allocation
türündeki bağımsız değişkenler olarak iletilir.
// 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 şu şekilde ç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 başlatmayı içeren bir algoritmanın RenderScript dilinde nasıl tamamen uygulanabileceği gösterilmektedir. Tek Kaynaklı RenderScript olmadan her iki çekirdeği de Java kodundan başlatmanız gerekir. Bu durumda çekirdek başlatma işlemleri çekirdek tanımlarından ayrılır ve algoritmanın tamamını anlamanız zorlaşır. Tek Kaynaklı RenderScript kodunun okunmasını kolaylaştırmakla kalmaz, aynı zamanda çekirdek lansmanlarında Java ile komut dosyası arasındaki geçişi de ortadan kaldırır. Yinelemeli bazı algoritmalar, çekirdekleri yüzlerce kez kullanıma sunarak bu geçişin ek yükünü ciddi ölçüde artırabilir.
Komut dosyası genel değişkenleri
Genel komut dosyası, bir komut dosyası (.rs
) dosyasındaki static
dışında normal bir genel değişkendir. filename.rs
dosyasında tanımlanan var adlı genel bir komut dosyası için ScriptC_filename
sınıfına yansıtılan bir get_var
yöntemi olur. Global const
olmadığı sürece set_var
yöntemi de vardır.
Belirli bir genel komut dosyası, Java değeri ve komut dosyası değeri olmak üzere iki ayrı değere sahiptir. Bu değerler aşağıdaki şekilde çalışır:
- var, komut dosyasında statik bir başlatıcıya sahipse bu, var'ın hem Java'daki hem de komut dosyasındaki ilk değerini belirtir. Aksi takdirde bu ilk değer sıfır olur.
- Komut dosyası içindeki var öğesine erişir. Bu parametre, komut dosyası değerini okur ve yazar.
get_var
yöntemi Java değerini okur.set_var
yöntemi (varsa) Java değerini hemen, komut dosyası değerini ise eşzamansız olarak yazar.
NOT: Bu, komut dosyasındaki herhangi bir statik başlatıcı hariç, bir komut dosyası içinden global olarak yazılan değerlerin Java tarafından görülmediği anlamına gelir.
Azaltma Çekirdekleri Hakkında Ayrıntılı Bilgi
Azaltma, bir veri kümesini tek bir değerde birleştirme işlemidir. Bu, paralel programlamada aşağıdaki gibi uygulamalarla birlikte kullanışlı bir temel yapıdır:
- Tüm veriler için toplamı veya çarpımı hesaplama
- tüm veriler üzerinde mantıksal işlemleri (
and
,or
,xor
) - verilerdeki minimum veya maksimum değeri bulmak
- Veriler arasında belirli bir değeri veya belirli bir değerin koordinatını arama
Android 7.0 (API düzeyi 24) ve sonraki sürümlerde RenderScript, kullanıcı tarafından yazılan verimli azaltma algoritmalarına izin vermek için azaltma çekirdeklerini destekler. 1, 2 veya 3 boyutlu girişlerde azaltma çekirdekleri başlatabilirsiniz.
Yukarıdaki örnekte basit bir addint azaltma çekirdeği gösterilmektedir.
Aşağıda, 1 boyutlu bir Allocation
içinde minimum ve maksimum long
değerlerinin konumlarını bulan daha karmaşık bir findMinAndMax azaltma çekirdeği verilmiştir:
#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: Daha fazla örnek azaltma çekirdeği burada verilmiştir.
Bir indirme çekirdeği çalıştırmak için RenderScript çalışma zamanı, azaltma işleminin durumunu tutmak için toplayıcı veri öğeleri adı verilen bir veya daha fazla değişken oluşturur. RenderScript çalışma zamanı, toplayıcı veri öğelerinin sayısını performansı en üst düzeye çıkaracak şekilde seçer. Toplayıcı veri öğelerinin türü (toplType), ç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 bir toplayıcı veri öğesi sıfır olarak başlatılır (memset
tarafından başlatılmış gibi); ancak farklı bir işlem 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ü) 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ü) şimdiye kadar bulunan minimum ve maksimum değerleri takip etmek için kullanılır. Bunları sırasıyla LONG_MAX
ve LONG_MIN
olarak ayarlayan ve bu değerlerin konumlarını -1 olarak ayarlayan bir başlatıcı işlevi vardır. Bu işlev, değerlerin işlenen girişin (boş) kısmında bulunmadığını gösterir.
RenderScript, toplayıcı işlevinizi girişlerdeki her koordinat için bir kez çağırır. İşleviniz genellikle, girişe göre toplayıcı veri öğesini 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ğerden küçük veya bu değere eşit olup olmadığını ve/veya toplayıcı veri öğesinde kaydedilen maksimum değerden büyük veya bu değere eşit olup olmadığını kontrol eder ve toplayıcı veri öğesini buna göre 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 öğesinde birleştirmelidir. Bunu yapmak için bir birleştirici işlevi yazabilirsiniz. Toplayıcı işlevinin tek bir girişi varsa ve özel bağımsız değişkenleri yoksa bir birleştirici işlevi yazmanız gerekmez. RenderScript, toplayıcı veri öğelerini birleştirmek için toplayıcı işlevini kullanır. (İstediğiniz varsayılan davranış bu değilse yine de bir birleştirici işlevi yazabilirsiniz.)
Örnek: addint çekirdeğinde birleştirici işlevi olmadığından toplayıcı işlevi kullanılır. Doğru davranış budur çünkü bir değer koleksiyonunu iki parçaya bölersek ve bu iki parçadaki değerleri ayrı ayrı toplarsak bu iki toplamı toplamak, koleksiyonun tamamını toplamakla aynı şey olur.
Örnek: findMinAndMax çekirdeğinde, birleştirici işlevi, "kaynak" toplayıcı veri öğesi *val
'de kaydedilen minimum değerin "hedef" toplayıcı veri öğesi *accum
'de kaydedilen minimum değerden düşük olup olmadığını kontrol eder ve *accum
'i buna göre günceller. Maksimum değer için de benzer bir işlem yapar. Bu işlem *accum
, bazı giriş değerlerinin bazıları *accum
, bazıları da *val
içinde toplanmış olması yerine tüm giriş değerlerinin *accum
içinde toplanmış olması durumunda olacak şekilde güncellenir.
Tüm toplayıcı veri öğeleri birleştirildikten sonra RenderScript, Java'ya döndürülecek azaltma sonucunu belirler. Bunu yapmak için bir outconverter işlevi yazabilirsiniz. Birleştirilmiş toplayıcı veri öğelerinin nihai değerinin azaltmanın sonucu olmasını istiyorsanız çıkış dönüştürücü işlevi yazmanız gerekmez.
Örnek: addint çekirdeğinde dış dönüştürücü işlevi yoktur. Birleştirilen veri öğelerinin nihai değeri, girişin tüm öğelerinin toplamıdır ve döndürmek istediğimiz değerdir.
Örnek: findMinAndMax çekirdeğinde çıkış dönüştürücü işlevi, tüm toplayıcı veri öğelerinin birleşiminden elde edilen minimum ve maksimum değerlerin konumlarını tutmak için bir int2
sonuç değeri başlatır.
Azaltma çekirdeği yazma
#pragma rs reduce
, adını ve çekirdeği oluşturan işlevlerin adlarını ve rollerini belirterek bir azaltma çekirdeği tanımlar. Bu tür tüm işlevler static
olmalıdır. İndirme çekirdeği her zaman 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
öğelerinin anlamı şu şekildedir:
reduce(kernelName)
(zorunlu): Bir azaltma çekirdeğinin tanımlandığını belirtir. Yansıtılan bir Java yöntemireduce_kernelName
çekirdeği başlatır.initializer(initializerName)
(isteğe bağlı): Bu azaltma çekirdeği için başlatıcı 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, her bir toplayıcı veri öğesini sıfır olarak başlatır (
memset
tarafından başlatılmış gibi) ve aşağıdaki gibi bir başlatıcı işlevi varmış gibi davranı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 bu işlevi girişlerdeki her koordinat için bir kez çağırır. İşlev şu şekilde tanımlanmalıdır:static void accumulatorName(accumType *accum, in1Type in1, …, inNType inN [, specialArguments]) { … }
accum
, bu işlevin değiştireceği bir toplayıcı veri öğesinin işaretçisidir.in1
ileinN
, çekirdek başlatmaya iletilen girişlere göre otomatik olarak doldurulan bir veya daha fazla bağımsız değişkendir (her giriş için bir bağımsız değişken). Toplayıcı işlevi, isteğe bağlı olarak özel bağımsız değişkenlerin herhangi birini alabilir.Birden çok girişe sahip bir çekirdek:
dotProduct
.combiner(combinerName)
(isteğe bağlı): Bu azaltma çekirdeği için birleştirici işlevin adını belirtir. RenderScript, girişlerdeki 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. İşlev şu şekilde tanımlanmalıdır:
static void combinerName(accumType *accum, const accumType *other) { … }
accum
, bu işlevin değiştirmesi gereken bir "hedef" toplayıcı veri öğesine işaretçidir.other
, bu işlevin*accum
ile "birleştirilmesi" için "kaynak" bir toplayıcı veri öğesinin işaretçisidir.NOT:
*accum
,*other
veya her ikisi de başlatılmış ancak toplayıcı işlevine hiç aktarılmamış olabilir. Diğer bir deyişle, biri veya ikisi de hiçbir giriş verisine göre hiç güncellenmemiş olabilir. Örneğin, findMinAndMax çekirdeğinde birleştirici işlevifMMCombiner
, değeri INITVAL olan böyle bir toplayıcı veri öğesini belirttiği içinidx < 0
öğesini açıkça kontrol eder.Bir birleştirici işlevi sağlamazsanız RenderScript, bunun yerine toplayıcı işlevini kullanır ve aşağıdaki gibi bir birleştirici işlevi varmış gibi davranır:
static void combinerName(accumType *accum, const accumType *other) { accumulatorName(accum, *other); }
Çekirdekte birden fazla giriş varsa, giriş veri türü toplayıcı veri türüyle aynı değilse veya toplayıcı işlevi bir veya daha fazla özel bağımsız değişken alıyorsa birleştirici işlevi zorunludur.
outconverter(outconverterName)
(isteğe bağlı): Bu azaltma çekirdeği için çıkış dönüştürücü işlevinin adını belirtir. RenderScript, tüm toplayıcı veri öğelerini birleştirdikten sonra, Java'ya döndürülecek azaltma 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 sonucuyla başlatılması için bir sonuç veri öğesinin (RenderScript çalışma zamanında ayrılmış ancak başlatılmamış) işaretçisidir. resultType, bu veri öğesinin türüdür ve accumType ile aynı olması gerekmez.accum
, birleştirici işlevi tarafından hesaplanan nihai toplayıcı veri öğesinin işaretçisidir.Bir çıkış dönüştürücü işlevi sağlamazsanız RenderScript, nihai toplayıcı veri öğesini sonuç veri öğesine kopyalar ve aşağıdaki gibi bir çıkış dönüştürücü işlevi varmış gibi davranır:
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.
Bir çekirdeğin giriş türleri, bir toplayıcı veri öğesi türü ve bir sonuç türü olduğunu unutmayın. Bunların hiçbirinin aynı olması gerekmez. Örneğin, findMinAndMax çekirdeğinde long
giriş türü, MinAndMax
toplayıcı veri öğesi türü ve int2
sonuç türünün tümü farklıdır.
Neleri varsayamazsınız?
Belirli bir çekirdek başlatma işlemi için RenderScript tarafından oluşturulan toplayıcı veri öğelerinin sayısına güvenmemeniz gerekir. Aynı girişlerle aynı çekirdekte iki başlatmanın aynı sayıda toplayıcı veri öğesi oluşturacağının garantisi yoktur.
RenderScript'in başlatıcı, toplayıcı ve birleştirici işlevlerini çağıracağı sıraya güvenmemelisiniz. Hatta bazılarını paralel olarak bile çağırabilir. Aynı girişe sahip aynı çekirdeğin iki kez başlatılmasının aynı sırayla gerçekleşeceği garanti edilmez. Tek garanti, yalnızca başlatıcı işlevinin hiç bir zaman ilklenmemiş bir toplayıcı veri öğesi göreceğidir. Örnek:
- Toplayıcı işlevi çağrılmadan önce tüm toplayıcı veri öğelerinin başlatılacağının garantisi yoktur 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ı garanti edilmez.
- Birleştirici işlevi çağrılmadan önce toplayıcı işlevinin tüm giriş Öğeleri için çağrıldığının garantisi yoktur.
Bunun sonuçlarından biri, findMinAndMax çekirdeğinin belirleyici olmamasıdır: Giriş aynı minimum veya maksimum değerin birden fazla kez geçiyorsa çekirdeğin hangi yeri bulacağını bilemezsiniz.
Neleri garanti etmeniz gerekir?
RenderScript sistemi bir çekirdeği birçok farklı şekilde yürütmeyi seçebileceğinden, çekirdeğinizin istediğiniz şekilde davranmasını sağlamak için belirli kurallara uymanız gerekir. Bu kurallara uymazsanız yanlış sonuçlar, belirlenemeyen davranışlar veya çalışma zamanında hatalar alabilirsiniz.
Aşağıdaki kurallar genellikle iki toplayıcı veri öğesinin "aynı değere sahip olması gerektiğini belirtir. Bu ne anlama geliyor? Bu, çekirdeğin ne yapmasını istediğinize bağlıdır. addint gibi matematiksel bir indirgemede, "aynı" ifadesinin matematiksel eşitliği kastetmesi genellikle anlamlıdır. findMinAndMax ("minimum ve maksimum giriş değerlerinin yerini bul") gibi "herhangi birini seç" aramalarında, aynı giriş değerlerinin birden fazla kez bulunabileceği durumlarda, belirli bir giriş değerinin tüm konumları "aynı" olarak kabul edilmelidir. "En soldaki minimum ve maksimum giriş değerlerinin konumunu bul" işlevine benzer bir çekirdek yazabilirsiniz. Burada (örneğin, 100 konumundaki minimum değer, 200 konumundaki aynı minimum değere göre tercih edilir). Bu çekirdek için "aynı", yalnızca aynı değer'i değil, özdeş konum anlamına gelir. Ayrıca, bu çekirdek için "aynı" değer anlamına gelir. Ayrıca, bu çekirdek ve birleştirme işlevlerinin sahip olması gerekenlerden farklı olacaktır.
Başlatıcı işlevi bir kimlik değeri oluşturmalıdır. YaniI
ve A
, başlatıcı işlevi tarafından başlatılan toplayıcı veri öğeleriyse ve I
, toplayıcı işlevine hiç iletilmediyse (ancak A
gönderilmiş olabilir)
combinerName(&A, &I)
,A
'u aynı şekilde bırakmalıdır.combinerName(&I, &A)
,I
'uA
ile aynı şekilde bırakmalıdır.
Örnek: addint çekirdeğinde bir toplayıcı veri öğesi sıfır olarak başlatıldı. Bu çekirdeğin birleştirici işlevi toplama işlemi gerçekleştirir; sıfır, toplama işlemi için kimlik değeridir.
Örnek: findMinAndMax çekirdeğinde, bir toplayıcı veri öğesi INITVAL
olarak başlatılır.
fMMCombiner(&A, &I)
,I
INITVAL
olduğu içinA
değerini aynı bırakır.I
INITVAL
olduğundanfMMCombiner(&I, &A)
,I
değeriniA
olarak ayarlar.
Dolayısıyla, INITVAL
gerçekten bir kimlik değeridir.
Birleştirici işlevi toplayıcı olmalıdır. Yani, A
ve B
, başlatıcı işlevi tarafından başlatılan ve toplayıcı işlevine sıfır veya daha fazla kez iletilmiş olabilecek toplayıcı veri öğeleriyse combinerName(&A, &B)
, A
'ü combinerName(&B, &A)
'in B
'ü ayarladığı aynı değere ayarlamalıdır.
Örnek: addint çekirdeğinde, birleştirici işlevi iki toplayıcı veri öğesi değerini ekler; toplama işlemi değişmelidir.
Örnek: findMinAndMax çekirdeğinde, fMMCombiner(&A, &B)
, A = minmax(A, B)
ile aynıdır ve minmax
değişmelidir. Bu nedenle fMMCombiner
de değişmelidir.
Birleştirici işlev ilişkisel olmalıdır. Yani, A
, B
ve C
, başlatıcı işlevi tarafından başlatılan ve toplayıcı işlevine sıfır veya daha fazla kez iletilmiş olabilecek toplayıcı veri öğeleriyse aşağıdaki iki kod dizisi, A
'ü 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şkilidir ve bu nedenle birleştirici işlevi de ilişkilidir.
Örnek: findMinAndMax çekirdeğinde,
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
ilişkilendirici olduğundan fMMCombiner
de ilişkilidir.
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ılmıştır ve toplayıcı işlevine sıfır veya daha fazla kez aktarılmış olabilir, B
başlatılmamıştır 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şkenlerinin ve özel bağımsız değişkenlerin listesidir. Aşağıdaki iki kod dizisi, A
değerini aynı değere (A
) ayarlamalı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ı - 2. ifade,
B = 0
ile aynı - İfade 3,
B += V
ile aynı olup bu ifadeB = V
ile aynıdır - 4. ifade,
A += B
ile aynıdır veA += V
ile de aynıdır.
1. ve 4. ifadeler A
değerini aynı değere ayarlar. Bu nedenle bu çekirdek temel katlama kuralına uyar.
Örnek: findMinAndMax çekirdeğinde, X koordinatındaki V giriş değeri için:
- 1. ifade,
A = minmax(A, IndexedVal(V, X))
ile aynı - 2. ifade,
B = INITVAL
ile aynı - İfade 3, aynı
B başlangıç değeri olduğundan bu değer,B = minmax(B, IndexedVal(V, X))
B = IndexedVal(V, X)
- İfade 4,
aşağıdakiyle aynıdır:A = minmax(A, B)
A = minmax(A, IndexedVal(V, X))
1. ve 4. ifadeler A
değerini aynı değere ayarlar. Bu nedenle bu çekirdek temel katlama kuralına uyar.
Java kodundan azaltma çekirdeği çağırma
filename.rs
dosyasında tanımlanan kernelName adlı bir azaltma çekirdeği için ScriptC_filename
sınıfına üç 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);
addint çekirdeğini çağırmayla ilgili bazı örnekleri aşağıda bulabilirsiniz:
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öntemde, çekirdeğin toplayıcı işlevindeki her giriş bağımsız değişkeni için bir giriş Allocation
bağımsız değişkeni bulunur. RenderScript çalışma zamanı, tüm giriş tahsislerinin aynı boyutlara sahip olduğundan ve giriş tahsislerinin her birinin Element
türünün, toplayıcı işlevinin prototipinin ilgili giriş bağımsız değişkeniyle eşleştiğinden emin olmak için kontrol yapar. Bu denetimlerden herhangi biri başarısız olursa RenderScript bir istisna oluşturur. Çekirdek, bu boyutlardaki her koordinat üzerinde çalışır.
2. yöntem, çekirdek yürütme işlemini koordinatların bir alt kümesiyle sınırlamak için kullanılabilecek ek bir sc
bağımsız değişkeni alması dışında 1. yöntemle aynıdır.
3. yöntem, Ayırma girişleri yerine Java dizisi girişleri alması dışında 1. yöntemle aynıdır. Bu sayede, açıkça Ayırma oluşturmak ve bir Java dizisinden buna veri kopyalamak için kod yazma zahmetinden kurtulabilirsiniz. Ancak 1. yöntem yerine 3. yöntemi kullanmak kodun performansını artırmaz. 3. yöntem, her giriş dizisi için uygun Element
türüne ve setAutoPadding(boolean)
etkinleştirilmiş bir geçici 1 boyutlu Allocation oluşturur ve diziyi, Allocation
'ın uygun copyFrom()
yöntemiyle kopyalar gibi Allocation'a kopyalar. Ardından, bu geçici tahsisleri ileterek 1. yöntemi çağırır.
NOT: Uygulamanız, aynı diziyle veya aynı boyutlara ve Öğe türüne sahip farklı dizilerle birden çok çekirdek çağrısı yapacaksa 3. Yöntemi kullanmak yerine Tahsisleri açıkça oluşturarak, doldurarak ve yeniden kullanarak performansı artırabilirsiniz.
Yansıtılan azaltma yöntemlerinin döndürdüğü tür olan javaFutureType, ScriptC_filename
sınıfında yansıtılan statik bir iç içe yerleştirilmiş sınıftır. Bir azaltma çekirdeği çalıştırmasının gelecekteki sonucunu temsil eder. Çalıştırmanın gerçek sonucunu elde etmek için sınıfın get()
yöntemini çağırın. Bu yöntem, javaResultType türüne sahip bir değer döndürür. get()
eşzamanlı olmalıdır.
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 özelliğinden belirlenir. resultType imzasız bir tür (skalar, vektör veya dizi) olmadığı sürece, 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 bu daha büyük Java imzalı türdür. Aksi takdirde, doğrudan karşılık gelen Java türüdür. Örnek:
- resultType
int
,int2
veyaint[15]
ise javaResultTypeint
,Int2
veyaint[]
olur. resultType değerinin tüm değerleri javaResultType ile temsil edilebilir. - resultType
uint
,uint2
veyauint[15]
ise javaResultTypelong
,Long2
veyalong[]
olur. resultType değerinin tüm değerleri javaResultType ile temsil edilebilir. - resultType,
ulong
,ulong2
veyaulong[15]
ise javaResultType,long
,Long2
veyalong[]
olur. resultType için javaResultType ile temsil edilemeyen belirli değerler vardır.
javaFutureType, outconverter işlevinin resultType değerine 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üne sahip üyeler içeren Count uzunluğunda bir diziyse javaFutureType
resultArrayCount_memberType
olur.
Örnek:
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 edilemeyen bir değer üretirse javaFutureType.get()
bir istisna uygular.
3. yöntem ve devecSiInXType
devecSiInXType, toplayıcı işlevinin ilgili bağımsız değişkeninin inXType değerine karşılık gelen Java türüdür. inXType, işaretsiz bir tür veya vektör türü değilse devecSiInXType doğrudan karşılık gelen Java türüdür. inXType imzasız bir skaler türse devecSiInXType, aynı boyuttaki imzalı skaler türüne doğrudan karşılık gelen Java türüdür. inXType, imzalı bir vektör türüyse devecSiInXType, doğrudan vektör bileşeni türüne karşılık gelen Java türüdür. inXType işaretsiz bir vektör türüyse devecSiInXType, vektör bileşeni türüyle aynı boyutta işaretli skaler türe doğrudan karşılık gelen Java türüdür. Örnek:
- inXType
int
ise devecSiInXTypeint
olur. - inXType
int2
ise devecSiInXTypeint
olur. Dizi, düzleştirilmiş bir temsildir: Atamadaki 2 bileşenli vektör öğelerinin iki katı kadar skalar öğesi vardır. Bu,Allocation
sınıfınıncopyFrom()
yöntemlerinin işleyiş şekliyle aynıdır. - inXType
uint
ise deviceSiInXTypeint
olur. Java dizisindeki imzalı bir değer, tahsiste aynı bit desenine sahip imzasız bir değer olarak yorumlanır. Bu,Allocation
'uncopyFrom()
yöntemlerinin işleyiş şekliyle aynıdır. - inXType
uint2
ise deviceSiInXType değeriint
olur. Bu,int2
veuint
işlemlerinin işlenme şeklinin bir kombinasyonudur: Dizi düzleştirilmiş bir temsildir ve Java dizisi imzalı değerleri RenderScript 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üzleştirilirken, komut dosyasının vektör sonucu aynı değildir.
- Bir komut dosyasının imzasız girişi, Java tarafında aynı boyutta imzalı giriş olarak temsil edilir. Bir komut dosyasının imzasız sonucu ise Java tarafında genişletilmiş imzalı tür olarak temsil edilir (
ulong
hariç).
Diğer örnek azaltma çekirdekleri
#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]; }
Diğer kod örnekleri
BasicRenderScript, RenderScriptIntrinsic ve Merhaba Compute örneklerinde bu sayfada ele alınan API'lerin kullanımı daha ayrıntılı bir şekilde gösterilmektedir.