RenderScript, işlem açısından yoğun görevleri Android'de yüksek performansta çalıştırmaya yönelik bir çerçevedir. RenderScript, birincil olarak veriye paralel işlem için tasarlanmıştır ancak seri iş yükleri de bu avantajdan 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üşü uygulamaları için kullanışlıdır.
RenderScript'i kullanmaya başlarken anlamanız gereken iki temel kavram vardır:
- language, yüksek performanslı bilgi işlem kodu yazmak için C99 tabanlı bir dildir. RenderScript Kernel yazma, işlem çekirdeklerini yazmak için bunun nasıl kullanılacağını açıklar.
- control API, RenderScript kaynaklarının kullanım ö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 türetilmiş çekirdek dili. Java Code'dan RenderScript'i kullanma ve Tek Kaynaklı RenderScript'te sırasıyla birinci ve üçüncü seçenek açıklanmaktadır.
RenderScript Kernel Yazma
RenderScript çekirdeği genellikle <project_root>/src/rs
dizinindeki bir .rs
dosyasında bulunur. Her .rs
dosyası bir komut dosyası olarak adlandırılır. Her komut dosyası kendi çekirdek, işlev ve değişkenleri grubunu 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ından yansıtılan Java sınıflarının paket adını tanımlayan 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ılanabilir 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 kurulumda veya seri hesaplamalarda kullanışlıdır.
Sıfır veya daha fazla komut dosyası global. Genel komut dosyası, C'deki genel değişkene benzer. Java kodundan global komut dosyası dosyalarına erişebilirsiniz. Bunlar genellikle RenderScript çekirdeklerine parametre iletmede 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. İşlem çekirdeği, RenderScript çalışma zamanını bir veri koleksiyonunda paralel olarak yürütmeye yönlendirebileceğiniz bir işlev veya işlev koleksiyonudur. İki tür işlem çekirdeği vardır: eşleme çekirdekleri (foreach çekirdekleri olarak da adlandırılır) ve indirgenme çekirdekleri.
Eşleme çekirdeği, aynı boyutlardaki
Allocations
koleksiyonunda çalışan paralel bir işlevdir. Varsayılan olarak bu boyutlardaki her koordinat için bir kez yürütülür. GenellikleAllocations
girişi koleksiyonunu tek seferde birElement
Allocation
çıkışına dönüştürmek için kullanılır.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, otomatik olarakAllocation
çıkışındaki uygun konuma 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ı, tüm giriş ve çıkış ayırmalarının aynı boyutlara sahip olduğundan veElement
giriş ve çıkış ayırma türlerinin, çekirdeğin prototipiyle eşleştiğinden emin olmak için kontrol gerçekleştirir. Bu denetimlerin herhangi biri başarısız olursa RenderScript bir istisna uygular.NOT: Android 6.0'dan (API düzeyi 23) önceki bir eşleme çekirdeğinde birden fazla giriş bulunmayabilir
Allocation
.Allocations
çekirdeğinde bulunan giriş veya çıkıştan daha fazla giriş veya çıkışa ihtiyacınız varsa bu nesneler,rs_allocation
komut dosyası global değerlerine bağlı olmalı versGetElementAt_type()
ya darsSetElementAt_type()
aracılığıyla çekirdekten veya çağrılabilir bir 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. GenellikleAllocations
girdisi koleksiyonunu tek bir değere "azaltmak" için kullanılır. Ancak yalnızca özel olarak kullanılmaz.Burada, girişinin
Elements
değerini 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; }
İndirme çekirdeği, kullanıcı tarafından yazılan bir veya daha fazla işlevden oluşur.
#pragma rs reduce
, çekirdeği adını (bu örnekteaddint
) ve çekirdeği oluşturan işlevlerin adlarını ve rollerini (bu örnekteaccumulator
işleviaddintAccum
) belirterek tanımlamak için kullanılır. Bu tür işlevlerin tümüstatic
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.İndirgeme çekirdeği toplayıcı işlevi,
void
değerini döndürmeli ve en az iki bağımsız değişkene sahip olmalıdır. Birinci bağımsız değişken (bu örnekteaccum
), bir toplayıcı veri öğesine işaret eder ve ikinci bağımsız değişken (bu örnekteval
), çekirdek lansmanına 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ıştırılır veAllocation
içindeki herElement
için toplayıcı işlevi bir kez yürütülür. Varsayılan olarak, toplayıcı veri öğesinin nihai değeri, azaltma işleminin sonucu olarak değerlendirilir ve Java'ya döndürülür. RenderScript çalışma zamanı, giriş ayırma işlevininElement
türünün, toplayıcı işlevinin prototipiyle eşleştiğinden emin olmak için kontrol eder. Bu prototip eşleşmiyorsa RenderScript bir istisna uygular.Azaltma çekirdeğinde bir veya daha fazla giriş
Allocations
var ancakAllocations
çıkışı yok.Azaltma çekirdekleri burada daha ayrıntılı bir şekilde açıklanmaktadır.
Azaltma çekirdekleri, Android 7.0 (API düzeyi 24) ve sonraki sürümlerde desteklenir.
Eşleme çekirdek işlevi veya indirgeme çekirdeği toplayıcı işlevi,
int
veyauint32_t
türünde olması gerekenx
,y
vez
özel bağımsız değişkenlerini kullanarak mevcut 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 azaltma çekirdeği toplayıcı işlevi de rs_kernel_context türündeki isteğe bağlı
context
özel bağımsız değişkenini 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, komut dosyası oluşturulurken bazı hesaplamaların otomatik olarak yapılmasını sağlar. - Sıfır veya daha fazla statik komut dosyası genel öğesi ve işlevi. Statik bir 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 tabi olmayan standart bir C işlevidir. Bir komut dosyasına veya işlevine Java kodundan erişilmesi gerekmiyorsa bunun
static
olarak belirtilmesi önemle tavsiye edilir.
Kayan nokta hassasiyetini ayarlama
Bir komut dosyasında gerekli kayan nokta kesinliğini kontrol edebilirsiniz. Bu, IEEE 754-2008 standardının (varsayılan olarak kullanılır) tam olarak gerekli olmadığı durumlarda yararlıdır. Aşağıdaki pragmalar farklı bir kayma nokta hassasiyeti seviyesi 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 uyumluluğu gerektirmeyen ve daha düşük hassasiyetlere dayanabilen uygulamalar içindir. Bu mod, denorlar için sıfıra boşaltma ve sıfıra yuvarlama özelliğini etkinleştirir.#pragma rs_fp_imprecise
: En katı hassasiyet gereksinimlerine sahip olmayan uygulamalar içindir. 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 üzerindeki işlemler tanımlanmamıştır.
Çoğu uygulamada rs_fp_relaxed
yan etkisi olmadan kullanılabilir. Bu, yalnızca rahat bir hassasiyetle (SIMD CPU talimatları gibi) sunulan ek optimizasyonlar nedeniyle bazı mimariler için son derece faydalı olabilir.
Java'dan RenderScript API'lerine erişme
RenderScript kullanan bir Android uygulaması geliştirirken, API'ye Java'dan erişmek için iki yöntemden birini kullanabilirsiniz:
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 edinilebilir.
Bunun karşılığında yapılabilecekler şunlardır:
- Destek Kitaplığı API'lerini kullanırsanız uygulamanızın RenderScript bölümü, hangi RenderScript özelliklerini kullanıyor olursanız olun Android 2.3 (API düzeyi 9) ve sonraki sürümleri çalıştıran cihazlarla uyumlu olur. Böylece uygulamanız, yerel (
android.renderscript
) API'lerin kullanımına kıyasla daha fazla cihazda çalışabilir. - Belirli RenderScript özellikleri, Destek Kitaplığı API'leri üzerinden kullanılamaz.
- Destek Kitaplığı API'lerini kullanırsanız yerel (
android.renderscript
) API'leri kullanmaya kıyasla daha büyük APK'lar (muhtemelen önemli ölçüde) alırsınız.
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ı düzeltmesi 18.1.0 veya üstü
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ümlerini 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. - Aşağıdaki RenderScript ayarlarını dosyaya 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 işlemindeki belirli davranışı kontrol eder:
renderscriptTargetApi
- Oluşturulacak bayt kodu sürümünü belirtir. Bu değeri, kullandığınız tüm işlevleri sunabilecek en düşük API seviyesine 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 manifestinizde belirtilen minimum SDK sürümünüz farklı bir değere ayarlanırsa bu değer yok sayılır ve derleme dosyasındaki hedef değer, minimum SDK sürümünü belirlemek için kullanılır.renderscriptSupportModeEnabled
: Oluşturulan bayt kodunun, çalıştığı cihaz hedef sürümü desteklemiyorsa uyumlu bir sürüme geri dönmesi gerektiğ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'i kullanmak, android.renderscript
veya android.support.v8.renderscript
paketinde bulunan API sınıflarına bağlıdır. Çoğu uygulama aynı temel kullanım kalıbını kullanır:
- Bir 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ği için 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 uygulamanın aynı anda yalnızca tek bir RenderScript bağlamı olur. - Komut dosyasına aktarılacak 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. Komut dosyası genelleri olarak bağlandığında,rsGetElementAt_type()
versSetElementAt_type()
kullanılarak çekirdeklerdenAllocation
nesneye erişilebilir.Allocation
nesneleri, dizilerin Java kodundan RenderScript koduna (veya bunun tersi) aktarılmasına 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ı vardır:
- ScriptC: Bunlar, yukarıdaki RenderScript Kernel'i Yazma bölümünde açıklanan kullanıcı tanımlı komut dosyalarıdır. Her komut dosyasının, Java kodundan komut dosyasına erişimi kolaylaştırmak için RenderScript derleyici 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
konumunda bulunuyorsa ve bir RenderScript bağlamımRenderScript
konumundaysa komut dosyasını örneklendirmek için kullanılan Java veya Kotlin kodu şöyle olur:Kotlin
val invert = ScriptC_invert(renderScript)
Java
ScriptC_invert invert = new ScriptC_invert(renderScript);
- ScriptIntrinsic: Bunlar, Gauss bulanıklaştırma, evrişim ve görüntü karıştırma gibi genel işlemler için yerleşik RenderScript çekirdekleridir. Daha fazla bilgi için
ScriptIntrinsic
alt sınıflarına bakın.
- ScriptC: Bunlar, yukarıdaki RenderScript Kernel'i Yazma bölümünde açıklanan kullanıcı tanımlı komut dosyalarıdır. Her komut dosyasının, Java kodundan komut dosyasına erişimi kolaylaştırmak için RenderScript derleyici tarafından yansıtılan bir Java sınıfı vardır. Bu sınıfın adı
- Tahsisleri veriyle doldurun.
createFromBitmap()
ile oluşturulan Ayırmalar hariç, bir ayırma ilk oluşturulduğunda boş verilerle doldurulur. Ayırma işlemini doldurmak içinAllocation
içindeki "kopyalama" yöntemlerinden birini kullanın. "Kopyalama" yöntemleri eşzamanlıdır. - Gerekli komut dosyası global değerlerini ayarlayın.
set_globalname
adlı aynıScriptC_filename
sınıfındaki yöntemleri kullanarak genel değerleri ayarlayabilirsiniz. Örneğin,threshold
adlı birint
değişkenini ayarlamak için Java yönteminiset_threshold(int)
,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ında yansıtılır. Bu lansmanlar eşzamansızdır. Çekirdekteki bağımsız değişkenlere bağlı olarak bu yöntem, her birinin aynı boyutlara sahip olması gereken bir veya daha fazla Ayırma alır. Varsayılan olarak çekirdek, bu boyutlardaki her koordinat üzerinde yürütülür. Bu koordinatların 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 yansıtılaninvoke_functionName
yöntemlerini kullanarak çağrılabilir işlevleri başlatın. Bu lansmanlar eşzamansızdır. Allocation
nesneleri ve javaFutureType nesnelerinden veri alın. BirAllocation
öğesindeki verilere Java kodundan erişmek için bu verileriAllocation
dilindeki "kopya" yöntemlerinden birini kullanarak Java'ya geri kopyalamanız gerekir. Azaltma çekirdeğinin sonucunu almak içinjavaFutureType.get()
yöntemini kullanmanız gerekir. "Kopyala" veget()
yöntemleri eşzamanlıdır.- RenderScript bağlamını ayrıntılarıyla inceleyin. RenderScript bağlamını
destroy()
ile kaldırabilir veya RenderScript bağlam nesnesinin atık toplanmasına izin verebilirsiniz. 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ız olup her biri, istenen işlem tamamlanmadan Java'ya dönebilir. Ancak her bir işlem, kullanıma sunuldukları sırayla serileştirilir.
Allocation
sınıfı, verileri Allocations'a ve Ayırmalar'dan kopyalamak için "kopyalama" yöntemleri sağlar. "Kopyalama" yöntemi eşzamanlıdır ve yukarıdaki aynı ayırmaya dokunan eşzamansız işlemlerin herhangi biriyle ilişkili olarak serileştirilir.
Yansıtılan javaFutureType sınıfları, bir azaltma sonucunu almak için get()
yöntemi sağlar. get()
eşzamanlıdır ve kısaltmaya (eşzamansız) göre serileştirilmiştir.
Tek Kaynaklı RenderScript
Android 7.0 (API düzeyi 24), Tek Kaynaklı RenderScript adlı yeni bir programlama özelliğini kullanıma sunuyor. Bu özellikte, çekirdekler Java'dan değil, tanımlandıkları komut dosyasından çalıştırılıyor. Şu anda bu yaklaşım, kısa ve öz olması için bu bölümde sadece "çekirdek" olarak bahsedilen çekirdeklerin eşleştirilmesiyle sınırlıdır. Bu yeni özellik, komut dosyasının içinden
rs_allocation
türü ayırmalar oluşturmayı 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: Algoritmanın bir dilde uygulanmasını sağladığı için daha okunabilir kod, birden fazla çekirdek lansmanında Java ile RenderScript arasındaki geçişlerin daha az olması nedeniyle potansiyel olarak daha hızlı kod.
Tek Kaynaklı RenderScript'te, çekirdekleri
RenderScript Kernel'i yazma bölümünde açıklandığı gibi yazarsınız. Ardından, bunları başlatmak için
rsForEach()
çağrısı yapan, çağrılabilir bir işlev yazarsınız. Bu API, ilk parametre olarak çekirdek işlevini, ardından giriş ve çıkış ayırmalarını alır. Benzer bir API
rsForEachWithOptions()
,
rs_script_call_t
türünde ekstra bir bağımsız değişken alır. Bu bağımsız değişken, çekirdek işlevinin işlemesi için giriş ve çıkış tahsislerindeki öğelerin bir alt kümesini belirtir.
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, çekirdekleri başlatma da dahil olmak üzere tüm hesaplamayı başlatacak olan invoke_function_name()
kullanarak çağrılabilir işlevi çağırın.
Ayırma işlemi genellikle, bir çekirdek lansmanından ara sonuçları kaydedip diğerine aktarmak için gereklidir. Bunları
rsCreateAllocation() kullanarak oluşturabilirsiniz. Bu API'nin kullanımı kolay bir biçimi
rsCreateAllocation_<T><W>(…)
şeklindedir. Burada T bir öğenin veri türüdü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 ayırmalarda Y veya Z boyutunun boyutu atlanabilir. Örneğin, rsCreateAllocation_uchar4(16384)
, her biri uchar4
türünde olan 16.384 öğeden oluşan 1D bir ayırma 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 Kernel Yazma bölümü, bir resmi ters çeviren örnek bir çekirdek içerir. Aşağıdaki örnek, Tek Kaynaklı RenderScript kullanarak bir resme birden fazla efekt uygulayacak şekilde genişletilir. Renkli 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 ayırmalar,
rs_allocation
türünün bağımsız değişkenleri 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 ş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 örnek, iki çekirdek lansmanı içeren bir algoritmanın RenderScript dilinde tam olarak nasıl uygulanabileceğini gösterir. Tek Kaynaklı RenderScript olmasaydı, her iki çekirdeği de Java kodundan başlatmanız gerekirdi. Böylece, çekirdek lansmanlarını çekirdek tanımlarından ayırıp tüm algoritmayı anlamayı zorlaştırırdınız. Tek Kaynaklı RenderScript kodunun okunmasını kolaylaştırmanın yanı sıra, ç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ı Genelleri
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 olacaktır.
Belirli bir komut dosyasının iki ayrı değeri vardır: Java değeri ve script değeri. Bu değerler şu şekilde çalışır:
- var komut dosyasında statik bir başlatıcıya sahipse hem Java'da hem de komut dosyasında var öğesinin başlangıç değerini belirtir. Aksi takdirde, bu başlangıç değeri 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 yazar ve komut dosyası değerini eşzamansız olarak yazar.
NOT: Bu, komut dosyasındaki herhangi bir statik başlatıcı hariç, bir komut dosyası içinden global şekilde yazılan değerlerin Java tarafından görülmediği anlamına gelir.
Çekirdekleri Derinlemesine Azaltma
Azaltma, veri koleksiyonunu tek bir değer halinde birleştirme işlemidir. Bu, paralel programlamada aşağıdaki gibi uygulamalara sahip yararlı bir temel öğedir:
- tüm veriler üzerinden toplam veya çarpım
- tüm veriler üzerinde mantıksal işlemleri (
and
,or
,xor
) - verilerdeki minimum veya maksimum değeri bulmak
- belirli bir değeri veya verilerdeki belirli bir değerin koordinatını aramak
RenderScript, Android 7.0 (API düzeyi 24) ve sonraki sürümlerde, kullanıcı tarafından yazılan verimli azaltma algoritmalarına olanak tanımak için azaltma çekirdeklerini destekler. 1, 2 veya 3 boyutlu girişlerde azaltma çekirdeklerini 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: Burada başka azaltma çekirdeklerine yer 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ü (accumType), çekirdeğin toplayıcı işlevi tarafından belirlenir. Bu işlevin ilk bağımsız değişkeni, toplayıcı veri öğesine yönlendiren bir işaretçidir. 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 başlatıcı işlevi yazabilirsiniz.
Örnek: addint çekirdeğinde, giriş değerlerini toplamak için toplayıcı veri öğeleri (int
türündeki) kullanılır. Başlatıcı işlevi olmadığından her toplayıcı veri öğesi sıfıra başlatılır.
Örnek: findMinAndMax çekirdeğinde, MinAndMax
türündeki toplayıcı veri öğeleri, şu ana 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 değerlerin işlenen giriş (boş) bölümünde gerçekten mevcut olmadığını belirten bir başlatıcı işlevi vardır.
RenderScript, girişlerdeki her koordinat için toplayıcı işlevinizi bir kez çağırır. Genellikle 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ğerden düşük veya bu değere eşit olup olmadığını ve/veya toplayıcı veri öğesinde kaydedilen maksimum değerden büyük ya da bu değere eşit olup olmadığını kontrol eder ve toplayıcı veri öğesini uygun şekilde günceller.
Girişlerdeki her koordinat için toplayıcı işlevi 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şlev yazabilirsiniz. Toplayıcı işlevinde tek bir giriş varsa ve özel bağımsız değişkenler yoksa birleştirici işlevi yazmanız gerekmez. RenderScript, toplayıcı veri öğelerini birleştirmek için toplayıcı işlevini kullanır. (Varsayılan davranış istediğiniz gibi değilse de birleştirici işlevi yazabilirsiniz.)
Örnek: addint çekirdeğinde birleştirici işlevi bulunmadığından toplayıcı işlevi kullanılacaktı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, *val
"source" 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 *accum
değerini buna göre günceller. Maksimum değer için de aynı işi 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önmek için azaltma işleminin sonucunu belirler. Bunu yapmak için bir outconverter işlevi yazabilirsiniz. Birleştirilmiş toplayıcı veri öğelerinin nihai değerinin azalmanın sonucu olmasını istiyorsanız 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 son değeri, girdideki tüm Öğelerin toplamıdır. Bu, döndürmek istediğimiz değerdir.
Örnek: findMinAndMax çekirdeğinde Outconverter 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.
İndirgeme çekirdeği yazma
#pragma rs reduce
, indirme çekirdeğini, adını ve çekirdeği oluşturan işlevlerin adlarını ve rollerini belirterek 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
içindeki öğelerin anlamı aşağıdaki gibidir:
reduce(kernelName)
(zorunlu): Bir azaltma çekirdeğinin tanımlanmakta olduğunu 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 bir 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.Başlatıcı işlevi sağlamazsanız RenderScript, her toplayıcı veri öğesini sıfıra sıfırlar (
memset
gibi) başlatır 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 toplayıcı veri öğesine işaret eden bir göstergedir.in1
ileinN
arası, çekirdek başlatmaya iletilen girişlere göre otomatik olarak doldurulan bir veya daha fazla bağımsız değişkendir. Bu bağımsız değişken giriş başına bir bağımsız değişkendir. Toplayıcı işlevi, isteğe bağlı olarak özel bağımsız değişkenlerden 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şlevinin 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 aşağıdaki gibi tanımlanmalıdır:
static void combinerName(accumType *accum, const accumType *other) { … }
accum
, bu işlevin değiştireceği "hedef" toplayıcı veri öğesine işaret eder.other
, bu işlevin*accum
ile "birleştirmesi" için kullanılan "source" toplayıcı veri öğesine işaret eder.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 herhangi 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.Birleştirici işlevi sağlamazsanız RenderScript, aşağıdaki gibi bir birleştirici işlevi varmış gibi davranarak onun yerine 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ıyorsa 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, tüm toplayıcı veri öğelerini birleştirdikten sonra, Java'ya dönen indirme 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 azaltmanın sonucuyla başlatılması için bir sonuç veri öğesine (RenderScript çalışma zamanı tarafından ayrılmış ancak başlatılmamış) işaret eden bir işarettir. resultType, bu veri öğesinin türüdür ve accumType ile aynı değildir.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, nihai toplayıcı veri öğesini sonuç veri öğesine kopyalar. Bu işlem için aşağıdaki gibi bir dış 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.
Çekirdeğin giriş türleri, 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.
Ne olduğunu varsayamazsınız?
Belirli bir çekirdek lansmanı için RenderScript tarafından oluşturulan toplayıcı veri öğelerinin sayısını temel almamalısınız. 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ğırdığı sıraya güvenmemeniz gerekir. Hatta bazılarını paralel olarak çağırabilir. Aynı çekirdekten aynı girişe sahip iki başlatmanın aynı sırayı izleyeceğinin garantisi yoktur. Tek garanti, başlatılmamış toplayıcı veri öğesini sadece başlatıcı işlevinin görmesidir. Ö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 hangi sırayla aktarılacağı konusunda herhangi bir garanti verilmez.
- 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.
Neyi garanti etmelisiniz?
RenderScript sistemi bir çekirdeği birçok farklı şekilde yürütmeyi seçebildiğinden çekirdeğinizin istediğiniz gibi çalışmasını sağlamak için belirli kurallara uymanız gerekir. Bu kurallara uymazsanız yanlış sonuçlar, belirleyici olmayan davranışlar veya çalışma zamanı 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. Aynı giriş değerlerinin birden fazla kez geçiyor olabileceği findMinAndMax gibi ("minimum ve maksimum giriş değerlerinin konumunu bul") "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 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
alanını aynı şekilde bırakmalıdırcombinerName(&I, &A)
,I
alanınıA
ile aynı şekilde bırakmalıdır
Örnek: addint çekirdeğinde bir toplayıcı veri öğesi sıfır olarak başlatıldı. Bu çekirdek için birleştirici işlevi toplama işlemi gerçekleştirir. Sıfır, toplama işleminin kimlik değeridir.
Örnek: findMinAndMax çekirdeğinde bir toplayıcı veri öğesi INITVAL
olarak başlatıldı.
I
değeriINITVAL
olduğundanfMMCombiner(&A, &I)
,A
değerini aynı bırakır.I
,INITVAL
değerine sahip olduğundanfMMCombiner(&I, &A)
,I
alanınıA
olarak ayarlar.
Dolayısıyla, INITVAL
gerçekten bir kimlik değeridir.
Birleştirici işlevi değişen olmalıdır. Yani A
ve B
, başlatıcı işlevi tarafından başlatılan toplayıcı veri öğeleriyse ve bu, toplayıcı işlevine sıfır veya daha fazla kez geçmişse combinerName(&A, &B)
, A
öğesini combinerName(&B, &A)
tarafından ayarlanan aynı değere ayarlamalıdır.B
Örnek: addint çekirdeğinde birleştirici işlevi, iki toplayıcı veri öğesi değerini ekler. Ekleme işlemi, değişmelitir.
Örnek: findMinAndMax çekirdeğinde fMMCombiner(&A, &B)
A = minmax(A, B)
ile aynıdır, minmax
ise değişmelidir. Dolayısıyla fMMCombiner
da değişkendir.
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ı veri öğeleriyse ve bunlar toplayıcı işlevine sıfır veya daha fazla kez geçmişse aşağıdaki iki kod dizisi, A
değerini 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şkilendirmeyle ilişkilidir, dolayısıyla birleştirici işlevi de ilişkiseldir.
Örnek: findMinAndMax çekirdeğinde
fMMCombiner(&A, &B)değeri
A = minmax(A, B)ile aynıdır. Dolayısıyla iki 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ş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ı - İfade 2,
B = 0
ile aynı - İfade 3,
B += V
ile aynı olup bu ifadeB = V
ile aynıdır - İfade 4,
A += B
ile aynı olup bu ifadeA += V
ile aynıdır
İfade 1 ve 4, A
öğesini aynı değere ayarladığından 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,
B = INITVAL
ile aynı - İfade 3,
B = minmax(B, IndexedVal(V, X))
ile aynıdır. Çünkü B başlangıç değeri olduğundanB = IndexedVal(V, X)
ile aynıdır - İfade 4,
A = minmax(A, B)
ile aynı olanA = minmax(A, IndexedVal(V, X))
ifadesiyle aynıdır
İfade 1 ve 4, A
öğesini aynı değere ayarladığından bu çekirdek, temel katlama kuralına uyar.
Java kodundan bir azaltma çekirdeği çağırma
filename.rs
dosyasında tanımlanan kernelName adlı bir azaltma çekirdeği için ScriptC_filename
sınıfında yansıtılan üç yöntem vardı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ö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ş ayırmalarının aynı boyutlara sahip olduğundan ve giriş ayırmalarının her birinin Element
türünün, toplayıcı işlevinin prototipine karşılık gelen giriş bağımsız değişkeniyle eşleştiğinden emin olur. Bu kontrollerden herhangi biri başarısız olursa RenderScript bir istisna uygular. Çekirdek, bu boyutlardaki her koordinat üzerinde yürütülür.
2. Yöntem, 2. Yöntem'in ç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 Yöntem 1 ile aynı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 sayede, açıkça Ayırma oluşturmak ve bir Java dizisinden buna veri kopyalamak için kod yazma zahmetinden kurtulabilirsiniz. Ancak Yöntem 1 yerine 3. Yöntem, kodun performansını artırmaz. Her bir giriş dizisi için 3. Yöntem, uygun Element
türü ve setAutoPadding(boolean)
etkinleştirilerek geçici bir 1 boyutlu ayırma oluşturur ve diziyi uygun copyFrom()
yöntemi (Allocation
) aracılığıyla sanki "ayırma"ya kopyalar. Daha sonra, bu geçici ayırmaları 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ürülen türü olan javaFutureType, ScriptC_filename
sınıfı içindeki iç içe yerleştirilmiş statik bir 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öz konusu 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 olan 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 javaResultType,int
,Int2
veyaint[]
olur. Tüm resultType değerleri, javaResultType ile temsil edilebilir. - resultType,
uint
,uint2
veyauint[15]
ise javaResultType,long
,Long2
veyalong[]
olur. Tüm resultType 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'ı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 üyelere sahip bir uzunluk Count dizisiyse 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 karşılık gelen bağımsız değişkeninin inXType öğesine karşılık gelen Java türüdür. inXType imzasız veya bir vektör türü olmadığı sürece, 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 imzasız bir vektör türüyse devecSiInXType, vektör bileşeniyle aynı boyuttaki imzalı skaler türüne doğrudan karşılık gelen Java türüdür. Örnek:
- inXType değeri
int
ise devecSiInXType değeri,int
olur. - inXType,
int2
ise devecSiInXType değeri,int
olur. Dizi, düzleştirilmiş bir temsildir: Ayırmada 2 bileşenli vektör öğeleri olduğundan iki kat daha fazla skaler Öğe vardır. Bu,Allocation
içincopyFrom()
yöntemlerinin çalışma biçimiyle aynıdır. - inXType
uint
ise deviceSiInXType değeriint
olur. Java dizisindeki imzalı bir değer, Ayırma'da aynı bit kalıbının imzasız bir değeri olarak yorumlanır. Bu,Allocation
için kullanılancopyFrom()
yöntemlerinin çalışma biçimiyle 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ı bir giriş olarak temsil edilirken, komut dosyasının imzalanmamış sonucu Java tarafında genişletilmiş imzalı bir tür olarak temsil edilir (
ulong
durumu hariç).
Daha fazla 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]; }
Ek kod örnekleri
BasicRenderScript, RenderScriptIntrinsic ve Hello Compute örnekleri bu sayfada ele alınan API'lerin kullanımını daha ayrıntılı şekilde gösterir.