RenderScript'e Genel Bakış

RenderScript, Android'de yoğun işlem gücü gerektiren görevleri yüksek performansla çalıştırmak için kullanılan bir çerçevedir. RenderScript, öncelikli olarak veri paralel hesaplamasıyla kullanılmak üzere tasarlanmıştır ancak seri iş yükleri de bu özellikten yararlanabilir. RenderScript çalışma zamanı, bir cihazda bulunan işlemciler (ör. çok çekirdekli CPU'lar ve GPU'lar) arasında işleri paralel hale getirir. Bu sayede, iş planlaması yerine algoritmaları ifade etmeye odaklanabilirsiniz. RenderScript, özellikle görüntü işleme, dijital 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üretilmiş bir dildir. RenderScript çekirdeği yazma bölümünde, hesaplama çekirdekleri yazmak için RenderScript'in nasıl kullanılacağı açıklanmaktadır.
  • Denetim API'si, 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'dan türetilen çekirdek dili. Java Kodundan RenderScript Kullanma ve Tek Kaynaklı RenderScript, sırasıyla birinci 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ının kendi çekirdek, işlev ve değişken grubu vardır. Bir komut dosyası şunları içerebilir:

  • Bu komut dosyasında kullanılan RenderScript çekirdek dilinin sürümünü bildiren bir pragma bildirimi (#pragma version(1)). Şu anda geçerli olan tek değer 1'dir.
  • Bu komut dosyasından yansıtılan Java sınıflarının paket adını bildiren bir pragma bildirimi (#pragma rs java_package_name(com.example.app)). .rs dosyanızın bir kitaplık projesinde değil, uygulama paketinizde olması gerektiğini unutmayın.
  • Sıfır veya daha fazla çağrılabilir işlev. Çağrılabilir işlev, Java kodunuzdan rastgele bağımsız değişkenlerle çağırabileceğiniz tek iş parçacıklı bir RenderScript işlevidir. Bunlar genellikle daha büyük bir işleme ardışık düzenindeki ilk kurulum veya seri hesaplamalar için kullanışlıdır.
  • Sıfır veya daha fazla komut dosyası genel değişkeni. Komut dosyası genel değişkeni, C'deki genel değişkene benzer. Java kodundan komut dosyası genel değişkenlerine erişebilirsiniz. Bunlar genellikle RenderScript çekirdeklerine parametre aktarmak için kullanılır. Komut dosyası genel değişkenleri 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 koleksiyonu üzerinde paralel olarak yürütmeye yönlendirebileceğiniz bir işlev veya işlevler koleksiyonudur. İki tür hesaplama çekirdeği vardır: eşleme çekirdekleri (foreach çekirdekleri olarak da adlandırılır) ve indirgeme çekirdekleri.

    Eşleme çekirdeği, aynı boyutlardaki Allocations koleksiyonu üzerinde çalışan paralel bir işlevdir. Varsayılan olarak, bu boyutlardaki her koordinat için bir kez yürütülür. Genellikle (ancak her zaman değil) bir giriş Allocations koleksiyonunu her seferinde bir Element olacak şekilde çıkış Allocation koleksiyonuna dönüştürmek için kullanılır.

    • Basit bir eşleme çekirdeği örneği:

      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 RenderScript eşleme çekirdeği olduğunu belirtir. in bağımsız değişkeni, çekirdek başlatmaya iletilen Allocation girişine göre otomatik olarak doldurulur. x ve y bağımsız değişkenleri aşağıda ele alınmıştır. Çekirdekten döndürülen değer, çıkış Allocation içinde uygun konuma otomatik olarak yazılır. Varsayılan olarak bu çekirdek, tüm girişi Allocation boyunca çalıştırılır ve Allocation içindeki her Element için çekirdek işlevi bir kez yürütülür.

      Bir eşleme çekirdeğinde bir veya daha fazla giriş Allocations, tek bir çıkış Allocation ya da her ikisi birden bulunabilir. RenderScript çalışma zamanı, tüm giriş ve çıkış ayırmalarının aynı boyutlara sahip olduğundan ve giriş ile çıkış ayırmalarının Element türlerinin çekirdeğin prototipiyle eşleştiğinden emin olmak için kontrol eder. Bu kontrollerden biri başarısız olursa RenderScript bir istisna oluşturur.

      NOT: Android 6.0'dan (API düzeyi 23) önce, eşleme çekirdeğinin birden fazla girişi Allocation olmayabilir.

      Çekirdeğin sahip olduğundan daha fazla giriş veya çıkışa Allocations ihtiyacınız varsa bu nesneler rs_allocation komut dosyası genel değişkenlerine bağlanmalı ve rsGetElementAt_type() ya da rsSetElementAt_type() aracılığıyla bir çekirdekten veya ç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))

    İndirgeme çekirdeği, aynı boyutlara sahip bir giriş koleksiyonu Allocations üzerinde çalışan bir işlev ailesidir. Varsayılan olarak, toplayıcı işlevi bu boyutlardaki her koordinat için bir kez yürütülür. Genellikle (ancak her zaman değil) bir giriş Allocations koleksiyonunu tek bir değere "indirgemek" için kullanılır.

    • Girişinin Elements değerini toplayan basit bir indirgeme çekirdeği örneği aşağıda verilmiştir:

      #pragma rs reduce(addint) accumulator(addintAccum)
      
      static void addintAccum(int *accum, int val) {
        *accum += val;
      }

      Bir azaltma çekirdeği, kullanıcı tarafından yazılmış bir veya daha fazla işlevden oluşur. #pragma rs reduce, adını (bu örnekte addint) ve çekirdeği oluşturan işlevlerin adlarını ve rollerini (bu örnekte bir accumulator işlevi addintAccum) belirterek çekirdeği tanımlamak için kullanılır. Bu tür tüm işlevler static olmalıdır. Küçültme çekirdeği her zaman bir accumulator işlevi gerektirir. Çekirdeğin ne yapmasını istediğinize bağlı olarak başka işlevleri de olabilir.

      Bir indirgeme ç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 örnekte accum), bir toplayıcı veri öğesine işaret eder ve ikincisi (bu örnekte val), çekirdek başlatmaya iletilen Allocation 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, tüm girişi boyunca çalıştırılır Allocation ve Allocation içindeki her Element 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 kabul edilir ve Java'ya döndürülür. RenderScript çalışma zamanı, giriş Allocation'ın Element türünün toplayıcı işlevinin prototipiyle eşleştiğinden emin olmak için kontrol yapar. Eşleşme yoksa RenderScript bir istisna oluşturur.

      Bir indirgeme çekirdeğinin bir veya daha fazla giriş Allocations değeri vardır ancak çıkış Allocations değeri yoktur.

      Küçültme çekirdekleri burada daha ayrıntılı olarak açıklanmıştır.

      Küçültme çekirdekleri, Android 7.0 (API düzeyi 24) ve sonraki sürümlerde desteklenir.

    Bir eşleme çekirdeği işlevi veya bir azaltma çekirdeği toplayıcı işlevi, int veya uint32_t türünde olması gereken özel bağımsız değişkenleri x, y ve z kullanarak mevcut yürütmenin koordinatlarına erişebilir. Bu bağımsız değişkenler isteğe bağlıdır.

    Eşleme çekirdeği işlevi veya azaltma çekirdeği toplayıcı işlevi, rs_kernel_context türünde isteğe bağlı özel bağımsız değişken context de alabilir. Bu izin, geçerli yürütmenin belirli özelliklerini (ör. rsGetDimX) sorgulamak için kullanılan bir çalışma zamanı API'leri ailesi tarafından 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 örneklendirildiğ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 gerçekleşir.
  • Sıfır veya daha fazla statik komut dosyası global değişkeni ve işlevi. Statik komut dosyası globali, Java kodundan erişilememesi dışında komut dosyası globali 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 gösterilmeyen standart bir C işlevidir. Bir komut dosyası global değişkenine 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 olmadığında kullanışlıdır. Aşağıdaki pragmalar farklı bir kayan nokta kesinliği düzeyi ayarlayabilir:

  • #pragma rs_fp_full (hiçbir şey belirtilmezse varsayılan): IEEE 754-2008 standardında belirtilen kayan nokta hassasiyeti gerektiren uygulamalar için.
  • #pragma rs_fp_relaxed: Katı IEEE 754-2008 uyumluluğu gerektirmeyen ve daha az hassasiyeti tolere edebilen uygulamalar için. Bu mod, denormal sayılar için sıfıra yuvarlama ve sıfıra doğru yuvarlama özelliklerini etkinleştirir.
  • #pragma rs_fp_imprecise: Katı hassasiyet şartları olmayan uygulamalar için. Bu mod, rs_fp_relaxed modundaki her şeyin yanı sıra aşağıdakileri de etkinleştirir:
    • -0.0 ile sonuçlanan işlemler bunun yerine +0.0 döndürebilir.
    • INF ve NAN ile ilgili işlemler tanımlanmamıştır.

Çoğu uygulama, rs_fp_relaxed özelliğini herhangi bir yan etki olmadan kullanabilir. Bu, yalnızca gevşek hassasiyetle kullanılabilen ek optimizasyonlar (SIMD CPU talimatları gibi) nedeniyle bazı mimarilerde çok faydalı olabilir.

Java'dan RenderScript API'lerine erişme

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 kullanmanıza olanak tanıyan bir Destek Kitaplığı aracılığıyla kullanılabilir.

Bu yöntemlerin avantajları ve dezavantajları şunlardır:

  • Destek Kitaplığı API'lerini kullanırsanız uygulamanızın RenderScript bölümü, kullandığınız RenderScript özelliklerinden bağımsız olarak Android 2.3 (API düzeyi 9) ve sonraki sürümleri çalıştıran cihazlarla uyumlu olur. Bu sayede uygulamanız, yerel (android.renderscript) API'leri kullandığınız duruma kıyasla 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ız duruma kıyasla (büyük olasılıkla önemli ölçüde) daha büyük APK'lar elde edersiniz.

RenderScript Destek Kitaplığı API'lerini kullanma

Destek Kitaplığı RenderScript API'lerini kullanmak için geliştirme ortamınızı bunlara erişebilecek şekilde yapılandırmanız gerekir. Bu API'leri kullanmak için aşağıdaki Android SDK araçları gereklidir:

  • Android SDK Tools düzeltme sürümü 22.2 veya daha yeni bir sürüm
  • Android SDK Derleme Araçları düzeltme sürümü 18.1.0 veya daha yeni bir sürüm

Android SDK Derleme Araçları 24.0.0 sürümünden itibaren Android 2.2'nin (API düzeyi 8) artık desteklenmediğini unutmayın.

Bu araçların yüklü sürümünü Android SDK Manager'da kontrol edip güncelleyebilirsiniz.

Destek Kitaplığı RenderScript API'lerini kullanmak için:

  1. Gerekli Android SDK sürümünün yüklü olduğundan emin olun.
  2. Android derleme işlemi 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:

      Modern

              android {
                  compileSdkVersion 36
      
                  defaultConfig {
                      minSdkVersion 9
                      targetSdkVersion 19
      
                      renderscriptTargetApi 18
                      renderscriptSupportModeEnabled true
                  }
              }
              

      Kotlin

              android {
                  compileSdkVersion(36)
      
                  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 düzeyine ayarlamanızı ve renderscriptSupportModeEnabled değerini true olarak belirlemenizi öneririz. Bu ayar için geçerli değerler, 11'den en son yayınlanan API düzeyine kadar olan tüm tam sayı değerleridir. Uygulama manifestinizde 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ındaki hedef değer 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.
  3. 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 kullanmak, android.renderscript ya da android.support.v8.renderscript paketinde bulunan API sınıflarını kullanır. Çoğu uygulama aynı temel kullanım kalıbını izler:

  1. RenderScript bağlamını başlatma create(Context) ile oluşturulan RenderScript bağlamı, RenderScript'in kullanılabilmesini sağlar ve sonraki tüm RenderScript nesnelerinin kullanım ömrünü kontrol eden bir nesne sağlar. Bağlam oluşturma, farklı donanım parçalarında kaynak oluşturabileceğinden uzun sürebilecek bir işlem olarak değerlendirilmelidir. Mümkünse uygulamanın kritik yolunda olmamalıdır. Normalde bir uygulamanın aynı anda yalnızca tek bir RenderScript bağlamı olur.
  2. 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ış olarak Allocation nesnelerini alır. Allocation nesnelerine, komut dosyası genel değişkenleri olarak bağlandıklarında rsGetElementAt_type() ve rsSetElementAt_type() kullanılarak çekirdeklerden erişilebilir. Allocation nesneleri, dizilerin Java kodundan RenderScript koduna ve tam tersi şekilde aktarılmasına olanak tanır. Allocation nesneleri genellikle createTyped() veya createFromBitmap() kullanılarak oluşturulur.
  3. Gerekli komut dosyalarını oluşturun. RenderScript'i kullanırken yararlanabileceğiniz iki tür komut dosyası vardır:
    • ScriptC: Bunlar, yukarıdaki RenderScript Çekirdeği Yazma bölümünde açıklandığı gibi kullanıcı tarafından tanımlanan komut dosyalarıdır. Her komut dosyasının, komut dosyasına Java kodundan erişimi kolaylaştırmak için RenderScript derleyicisi tarafından yansıtılan bir Java sınıfı vardır. Bu sınıfın adı ScriptC_filename şeklindedir. Örneğin, yukarıdaki eşleme çekirdeği invert.rs içinde ve bir RenderScript bağlamı mRenderScript içinde bulunuyorsa komut dosyasını örneklendirmek için 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ıklığı, evrişim ve görüntü karıştırma gibi yaygın işlemler için yerleşik RenderScript çekirdekleridir. Daha fazla bilgi için ScriptIntrinsic alt sınıflarına bakın.
  4. Tahsisler sayfasını verilerle doldurun. createFromBitmap() ile oluşturulan tahsisler hariç olmak üzere, bir tahsis ilk oluşturulduğunda boş verilerle doldurulur. Bir tahsisi doldurmak için Allocation içindeki "kopyalama" yöntemlerinden birini kullanın. "Kopyalama" yöntemleri eşzamanlıdır.
  5. Gerekli genel komut dosyası değişkenlerini ayarlayın. Genel değişkenleri, set_globalname adlı aynı ScriptC_filename sınıfındaki yöntemleri kullanarak ayarlayabilirsiniz. Örneğin, threshold adlı bir int değişkeni ayarlamak için set_threshold(int) Java yöntemini, lookup adlı bir rs_allocation değişkeni ayarlamak için ise set_lookup(Allocation) Java yöntemini kullanın. set yöntemleri eşzamansızdır.
  6. Uygun çekirdekleri ve çağrılabilir işlevleri başlatın.

    Belirli bir çekirdeği başlatma yöntemleri, forEach_mappingKernelName() veya reduce_reductionKernelName() adlı yöntemlerle aynı ScriptC_filename sınıfına yansıtılır. Bu lansmanlar eşzamansızdır. Çekirdeğe yönelik bağımsız değişkenlere bağlı olarak yöntem, tümü aynı boyutlara sahip olması gereken bir veya daha fazla ayırma işlemi alı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çin forEach veya reduce yöntemine son bağımsız değişken olarak uygun bir Script.LaunchOptions iletin.

    Aynı ScriptC_filename sınıfına yansıtılan invoke_functionName yöntemlerini kullanarak çağrılabilir işlevleri başlatın. Bu lansmanlar eşzamansızdır.

  7. Allocation nesnelerinden ve javaFutureType nesnelerinden veri alma. Java kodundan bir Allocation içindeki verilere erişmek için Allocation içindeki "copy" yöntemlerinden birini kullanarak bu verileri Java'ya geri kopyalamanız gerekir. Bir indirgeme çekirdeğinin sonucunu elde etmek için javaFutureType.get() yöntemini kullanmanız gerekir. "copy" ve get() yöntemleri eşzamanlıdır.
  8. RenderScript bağlamını kaldırın. RenderScript bağlamını destroy() ile veya RenderScript bağlam nesnesinin çöp toplayıcı tarafından toplanmasına izin vererek yok edebilirsiniz. Bu durum, ilgili bağlama ait herhangi bir nesnenin daha sonra kullanılmasının istisna oluşturmasına neden olur.

Eşzamansız yürütme modeli

Yansıtılan forEach, invoke, reduce ve set yöntemleri eşzamansızdır. Her biri, istenen işlemi tamamlamadan önce Java'ya dönebilir. Ancak her bir işlem, başlatıldığı sırayla yürütülür.

Allocation sınıfı, verileri Allocations'a ve Allocations'tan kopyalamak için "copy" yöntemleri sağlar. "Kopyalama" yöntemi eşzamanlıdır ve aynı ayırmaya dokunan yukarıdaki eşzamansız işlemlerden herhangi birine göre serileştirilir.

Yansıtılan javaFutureType sınıfları, bir indirgemenin sonucunu elde etmek için get() yöntemini sağlar. get() eşzamanlıdır ve azaltma (eşzamansız) ile ilgili olarak serileştirilir.

Tek Kaynaklı RenderScript

Android 7.0 (API düzeyi 24), çekirdeklerin Java yerine tanımlandıkları komut dosyasından başlatıldığı Tek Kaynaklı RenderScript adlı yeni bir programlama özelliği sunar. Bu yaklaşım şu anda eşleme çekirdekleriyle sınırlıdır. Bu bölümde, kısalık açısından bunlara yalnızca "çekirdekler" denir. Bu yeni özellik, komut dosyasının içinden rs_allocation türünde tahsisler oluşturmayı da destekler. Artık birden fazla çekirdek başlatma işlemi gerekse bile tüm algoritmayı yalnızca bir komut dosyasında uygulayabilirsiniz. Bu sayede iki avantaj elde edilir: Bir algoritmanın tek bir dilde uygulanması sayesinde daha okunabilir kod ve birden fazla çekirdek başlatma işleminde Java ile RenderScript arasında daha az geçiş yapılması sayesinde daha hızlı kod.

Tek kaynaklı RenderScript'te, çekirdekleri RenderScript Çekirdeği Yazma bölümünde açıklandığı şekilde yazarsınız. Ardından, bunları başlatmak için rsForEach() yöntemini ç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ış ayırmalarını alır. Benzer bir API olan rsForEachWithOptions(), çekirdek işlevinin işleyeceği giriş ve çıkış ayırmalarındaki öğelerin bir alt kümesini belirten ek bir rs_script_call_t türü 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ş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.

Tahsisler genellikle ara sonuçları kaydedip bir çekirdek başlatmadan 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ü, W ise öğenin vektör genişliğidir. API, X, Y ve Z boyutlarındaki ölçüleri bağımsız değişken olarak alır. 1D veya 2D ayırmalar için Y ya da Z boyutunun boyutu atlanabilir. Örneğin, rsCreateAllocation_uchar4(16384), her biri uchar4 türünde olan 16.384 öğeden oluşan 1 boyutlu bir ayırma oluşturur.

Tahsisler sistem tarafından otomatik olarak yönetilir. Bunları açıkça serbest bırakmanız veya boşaltmanız gerekmez. Ancak sistemin kaynakları mümkün olan en kısa sürede serbest bırakabilmesi için temel ayırmaya yönelik alloc tutamağına artık ihtiyacınız olmadığını belirtmek üzere rsClearObject(rs_allocation* alloc) yöntemini çağırabilirsiniz.

RenderScript Çekirdeği Yazma bölümünde, bir resmi ters çeviren örnek bir çekirdek yer alır. Aşağıdaki örnek, tek kaynaklı RenderScript kullanarak bir görüntüye birden fazla efekt uygulamak için bu örneği genişletir. Bu örnekte, renkli bir resmi siyah beyaz resme dönüştüren başka bir çekirdek (greyscale) de yer alıyor. Çağrılabilir bir işlev process(), bu iki çekirdeği bir giriş görüntüsüne sırayla uygular ve bir çıkış görüntüsü oluşturur. Hem giriş hem de çıkış için ayırmalar, rs_allocation türünde 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 aşağıdaki gibi çağırabilirsiniz:

Kotlin

val RS: RenderScript = RenderScript.create(context)
val script = ScriptC_singlesource(RS)
val inputAllocation: Allocation = Allocation.createFromBitmapResource(
        RS,
        resources,
        R.drawable.image
)
val outputAllocation: Allocation = Allocation.createTyped(
        RS,
        inputAllocation.type,
        Allocation.USAGE_SCRIPT or Allocation.USAGE_IO_OUTPUT
)
script.invoke_process(inputAllocation, outputAllocation)

Java

// File SingleSource.java

RenderScript RS = RenderScript.create(context);
ScriptC_singlesource script = new ScriptC_singlesource(RS);
Allocation inputAllocation = Allocation.createFromBitmapResource(
    RS, getResources(), R.drawable.image);
Allocation outputAllocation = Allocation.createTyped(
    RS, inputAllocation.getType(),
    Allocation.USAGE_SCRIPT | Allocation.USAGE_IO_OUTPUT);
script.invoke_process(inputAllocation, outputAllocation);

Bu örnekte, iki çekirdek başlatma içeren bir algoritmanın tamamen RenderScript dilinde nasıl uygulanabileceği gösterilmektedir. Tek kaynaklı RenderScript olmasaydı her iki çekirdeği de Java kodundan başlatmanız, çekirdek başlatma işlemlerini çekirdek tanımlarından ayırmanız ve tüm algoritmayı anlamayı zorlaştırmanız gerekirdi. Tek kaynaklı RenderScript kodu yalnızca daha kolay okunmakla kalmaz, aynı zamanda çekirdek başlatmaları sırasında Java ile komut dosyası arasındaki geçişi de ortadan kaldırır. Bazı yinelemeli algoritmalar, çekirdekleri yüzlerce kez başlatabilir ve bu geçişin ek yükünü önemli hale getirebilir.

Script Globals

Komut dosyası genel değişkeni, bir komut dosyası (.rs) dosyasındaki sıradan, static olmayan genel değişkendir. filename.rs dosyasında tanımlanan var adlı bir komut dosyası globali için ScriptC_filename sınıfına yansıtılan bir get_var yöntemi olur. Genel const olmadığı sürece bir set_var yöntemi de olacaktır.

Belirli bir komut dosyası globalinin iki ayrı değeri vardır: Java değeri ve komut dosyası değeri. Bu değerler aşağıdaki gibi davranır:

  • var, komut dosyasında statik bir başlatıcıya sahipse hem Java'da hem de komut dosyasında var'ın başlangıç değerini belirtir. Aksi takdirde, bu ilk değer sıfırdır.
  • Komut dosyasındaki var erişimleri, 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 statik başlatıcılar hariç olmak üzere, komut dosyasından bir globale yazılan değerlerin Java tarafından görülemeyeceği anlamına gelir.

İndirgeme Çekirdekleri Hakkında Ayrıntılı Bilgi

İndirgeme, bir veri koleksiyonunu tek bir değerde birleştirme işlemidir. Bu, paralel programlamada kullanışlı bir temel öğedir ve aşağıdaki gibi uygulamaları vardır:

  • tüm veriler üzerinden toplamı veya çarpımı hesaplama
  • tüm veriler üzerinde mantıksal işlemler (and, or, xor) hesaplama
  • Verilerdeki minimum veya maksimum değeri bulma
  • Verilerde 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 olanak tanımak 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çindeki minimum ve maksimum long değerlerinin konumlarını bulan daha karmaşık bir findMinAndMax indirgeme ç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 daha fazla indirgeme çekirdeği örneği bulabilirsiniz.

RenderScript çalışma zamanı, bir indirgeme çekirdeği çalıştırmak için indirgeme işleminin durumunu tutacak toplayıcı veri öğeleri adlı bir veya daha fazla değişken oluşturur. RenderScript çalışma zamanı, performansı en üst düzeye çıkarmak için toplayıcı veri öğelerinin sayısını seçer. Toplayıcı veri öğelerinin türü (accumType), çekirdeğin toplayıcı işlevi tarafından belirlenir. Bu işlevin ilk bağımsız değişkeni, toplayıcı veri öğesine işaret eder. Varsayılan olarak her toplayıcı veri öğesi sıfıra başlatılır (memset ile yapılmış gibi). Ancak farklı bir işlem 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ünde) kullanılır. Başlatıcı işlevi olmadığından her toplayıcı veri öğesi sıfır olarak başlatılır.

Örnek: findMinAndMax çekirdeğinde, o ana kadar bulunan minimum ve maksimum değerleri takip etmek için toplayıcı veri öğeleri (MinAndMax türünde) kullanılır. Bunları sırasıyla LONG_MAX ve LONG_MIN olarak ayarlamak ve bu değerlerin konumlarını -1 olarak ayarlamak için bir başlatıcı işlevi vardır. Bu, değerlerin girişin işlenen (boş) kısmında aslında mevcut olmadığını gösterir.

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, 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 ya da 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'in toplayıcı veri öğelerini tek bir toplayıcı veri öğesinde birleştirmesi gerekir. Bunun 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. (Bu varsayılan davranış istediğiniz gibi değilse bir birleştirici işlevi yazabilirsiniz.)

Örnek: addint çekirdeğinde birleştirici işlevi yoktur. Bu nedenle toplayıcı işlevi kullanılır. Bu doğru bir davranıştır. Bir değerler koleksiyonunu iki parçaya ayırıp bu iki parçadaki değerleri ayrı ayrı toplarsak bu iki toplamın toplamı, tüm koleksiyonun toplamıyla aynı olur.

Örnek: findMinAndMax çekirdeğinde birleştirici işlevi, "kaynak" toplayıcı veri öğesinde *val kaydedilen minimum değerin "hedef" toplayıcı veri öğesinde *accum kaydedilen minimum değerden daha küçük olup olmadığını kontrol eder ve *accum öğesini buna göre günceller. Maksimum değer için de benzer bir işlem yapar. Bu işlem, *accum değerini, tüm giriş değerleri *accum ve *val yerine *accum içinde toplanmış gibi günceller.

Tüm toplayıcı veri öğeleri birleştirildikten sonra RenderScript, Java'ya döndürülecek azaltma sonucunu belirler. Bunun için bir outconverter işlevi yazabilirsiniz. Birleştirilmiş toplayıcı veri öğelerinin nihai değerinin, indirgemenin sonucu olmasını istiyorsanız bir outconverter işlevi yazmanız gerekmez.

Örnek: addint çekirdeğinde outconverter işlevi yoktur. Birleştirilmiş veri öğelerinin son değeri, girişin tüm öğelerinin toplamıdır. Bu, döndürmek istediğimiz değerdir.

Örnek: findMinAndMax çekirdeğinde, outconverter 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.

İndirgeme ç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. Bir indirgeme çekirdeği her zaman bir accumulator işlevi gerektirir. Çekirdeğin ne yapmasını istediğinize bağlı olarak diğer işlevlerin bir kısmını veya tamamını atlayabilirsiniz.

#pragma rs reduce(kernelName) \
  initializer(initializerName) \
  accumulator(accumulatorName) \
  combiner(combinerName) \
  outconverter(outconverterName)

#pragma içindeki öğelerin anlamı şu şekildedir:

  • reduce(kernelName) (zorunlu): Bir indirgeme çekirdeğinin tanımlandığını belirtir. Yansıtılmış bir Java yöntemi reduce_kernelName, çekirdeği başlatır.
  • initializer(initializerName) (isteğe bağlı): Bu indirgeme ç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 öğesine işaret eder.

    Bir başlatıcı işlevi sağlamazsanız RenderScript, her toplayıcı veri öğesini sıfıra başlatır (memset ile olduğu 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 indirgeme çekirdeği için toplayıcı işlevinin adını belirtir. Çekirdeği başlattığınızda RenderScript, girişlerdeki her koordinat için bu işlevi bir kez çağırarak toplayıcı veri öğesini girişlere göre bir şekilde günceller. İş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 ile inN arasındaki değerler, ç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 kullanılır. Toplayıcı işlevi, isteğe bağlı olarak özel bağımsız değişkenlerden herhangi birini alabilir.

    Birden fazla girişi olan bir çekirdek örneği dotProduct'dir.

  • combiner(combinerName)

    (isteğe bağlı): Bu indirgeme çekirdeği için birleştirici işlevinin adını belirtir. RenderScript, toplayıcı işlevini girişlerdeki her koordinat için 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ştireceği bir "hedef" toplayıcı veri öğesinin işaretçisidir. other, bu işlevin *accum ile "birleştirmesi" için "kaynak" toplayıcı veri öğesine işaret eden bir işaretçidir.

    NOT: *accum, *other veya her ikisi de başlatılmış ancak toplayıcı işlevine hiç aktarılmamış olabilir. Yani biri veya her ikisi de herhangi bir giriş verisine göre hiç güncellenmemiştir. Örneğin, findMinAndMax çekirdeğinde birleştirici işlevi fMMCombiner, idx < 0 değerini açıkça kontrol eder. Çünkü bu, değeri INITVAL olan bir toplayıcı veri öğesini gösterir.

    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);
    }

    Çekirdeğin birden fazla girişi varsa, giriş veri 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 bir birleştirici işlevi zorunludur.

  • outconverter(outconverterName) (isteğe bağlı): Bu indirgeme ç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 indirgeme sonucuyla ilk kullanıma hazırlanması için bir sonuç veri öğesine (RenderScript çalışma zamanı tarafından ayrılmış ancak ilk kullanıma hazırlanmamış) işaretçidir. 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 öğesine yönelik bir işaretçidir.

    Bir outconverter işlevi sağlamazsanız RenderScript, nihai toplayıcı veri öğesini sonuç veri öğesine kopyalar ve aşağıdaki gibi bir outconverter 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 çıkış dönüştürücü işlevi zorunludur.

Bir çekirdeğin giriş türleri, toplayıcı veri öğesi türü ve sonuç türü olduğunu ve bunların hiçbirinin aynı olması gerekmediğini unutmayın. Örneğin, findMinAndMax çekirdeğinde giriş türü long, toplayıcı veri öğesi türü MinAndMax ve sonuç türü int2 birbirinden farklıdır.

Aşağıdakilerden hangisini varsayamazsınız?

Belirli bir çekirdek başlatma için RenderScript tarafından oluşturulan toplayıcı veri öğelerinin sayısına güvenmemelisiniz. Aynı girişlerle aynı çekirdeğin iki kez başlatılması, aynı sayıda toplayıcı veri öğesi oluşturmayabilir.

RenderScript'in başlatıcı, toplayıcı ve birleştirici işlevlerini çağırma sırasına güvenmemelisiniz. Hatta bunlardan bazılarını paralel olarak çağırabilir. Aynı girişe sahip aynı çekirdeğin iki kez başlatılması durumunda aynı sıranın izleneceğine dair bir garanti yoktur. Yalnızca başlatıcı işlevinin başlatılmamış bir toplayıcı veri öğesini göreceği garanti edilir. Örneğin:

  • Yalnızca başlatılmış bir toplayıcı veri öğesinde çağrılacak olsa da toplayıcı işlevi çağrılmadan önce tüm toplayıcı veri öğelerinin başlatılacağı garanti edilmez.
  • Giriş öğelerinin toplayıcı işlevine hangi sırayla iletileceği garanti edilmez.
  • Birleştirici işlevi çağrılmadan önce toplayıcı işlevinin tüm giriş öğeleri için çağrılacağı garanti edilmez.

Bunun bir sonucu olarak findMinAndMax çekirdeği deterministik değildir: Giriş, aynı minimum veya maksimum değerin birden fazla örneğini içeriyorsa çekirdeğin hangi örneği bulacağını bilemezsiniz.

Neyi garanti etmeniz gerekir?

RenderScript sistemi, çekirdeği birçok farklı şekilde yürütmeyi seçebileceğinden, çekirdeğinizin istediğiniz gibi davrandığından emin olmak için belirli kurallara uymanız gerekir. Bu kurallara uymazsanız yanlış sonuçlar, belirsiz davranışlar veya çalışma zamanı hataları alabilirsiniz.

Aşağıdaki kurallarda genellikle iki toplayıcı veri öğesinin "aynı değere" sahip olması gerektiği belirtilir. Bu ne anlama geliyor? Bu, çekirdeğin ne yapmasını istediğinize bağlıdır. addint gibi matematiksel bir indirgeme için "aynı" ifadesinin matematiksel eşitlik anlamına gelmesi genellikle mantıklıdır. Aynı giriş değerlerinin birden fazla kez bulunabileceği findMinAndMax ("minimum ve maksimum giriş değerlerinin konumunu bul") gibi "herhangi birini seç" aramalarında, belirli bir giriş değerinin tüm konumları "aynı" olarak kabul edilmelidir. "En soldaki minimum ve maksimum giriş değerlerinin konumunu bulmak" için benzer bir çekirdek yazabilirsiniz. Burada, örneğin 100 konumundaki bir minimum değer, 200 konumundaki aynı minimum değere tercih edilir. Bu çekirdek için "aynı", yalnızca aynı değer değil, aynı konum anlamına gelir ve toplayıcı ile birleştirici işlevlerinin findMinAndMax işlevlerinden farklı olması gerekir.

Başlatıcı işlevi, bir kimlik değeri oluşturmalıdır. Yani I 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 iletilmiş olabilir) şu geçerlidir:
  • combinerName(&A, &I), A aynı bırakmalıdır.
  • combinerName(&I, &A), I değerini A ile aynı bırakmalıdır.

Örnek: addint çekirdeğinde, bir toplayıcı veri öğesi sıfır olarak başlatılır. Bu çekirdeğin birleştirici işlevi toplama işlemi yapar. Sıfır, toplama işleminin birim değeridir.

Örnek: findMinAndMax çekirdeğinde, bir toplayıcı veri öğesi INITVAL olarak başlatılır.

  • fMMCombiner(&A, &I), A değerini aynı bırakır, çünkü I, INITVAL değerindedir.
  • fMMCombiner(&I, &A), I değerini A olarak ayarlar çünkü I, INITVAL değerine sahiptir.

Bu nedenle, INITVAL gerçekten bir birim değerdir.

Birleştirici işlevi değişmeli 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 öğesini combinerName(&B, &A) tarafından B öğesine ayarlanan 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şmeli olduğundan fMMCombiner de değişmelidir.

Birleştirici işlev birleşmeli 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 öğesini aynı değere ayarlamalıdır:

  • combinerName(&A, &B);
    combinerName(&A, &C);
  • combinerName(&B, &C);
    combinerName(&A, &B);

Örnek: addint çekirdeğinde, birleştirici işlevi iki toplayıcı veri öğesi değerini ekler:

  • A = A + B
    A = A + C
    // Same as
    //   A = (A + B) + C
  • B = B + C
    A = A + B
    // Same as
    //   A = A + (B + C)
    //   B = B + C

Toplama işlemi birleştirme özelliğine sahiptir ve bu nedenle birleştirici işlevi de bu özelliğe sahiptir.

Örnek: findMinAndMax çekirdeğinde,

fMMCombiner(&A, &B)
ile aynıdır.
A = minmax(A, B)
Bu durumda iki dizi şu şekildedir:
  • 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 birleşmelidir, dolayısıyla fMMCombiner de birleşmelidir.

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ışsa ve toplayıcı işlevine sıfır veya daha fazla kez iletilmişse, B başlatılmamışsa ve args, toplayıcı işlevine yapılan belirli bir çağrının giriş bağımsız değişkenlerinin ve özel bağımsız değişkenlerinin listesiyse aşağıdaki iki kod dizisi A değerini aynı değere 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:

  • 1. ifade, A += V ile aynı
  • 2. ifade B = 0 ile aynı
  • 3. ifade, B += V ile aynıdır. Bu da B = V ile aynıdır.
  • 4. ifade, A += B ile aynıdır. Bu da A += V ile aynıdır.

1 ve 4 numaralı 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ı
  • 3. ifade,
    B = minmax(B, IndexedVal(V, X))
    B başlangıç değeri olduğundan bu, şununla aynıdır:
    B = IndexedVal(V, X)
  • 4. ifade,
    A = minmax(A, B)
    Bu da şuna eşittir:
    A = minmax(A, IndexedVal(V, X))

1 ve 4 numaralı ifadeler A değerini aynı değere ayarlar. Bu nedenle bu çekirdek, temel katlama kuralına uyar.

Java kodundan indirgeme çekirdeği çağırma

filename.rs dosyasında tanımlanan kernelName adlı bir azaltma çekirdeği için ScriptC_filename sınıfına 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);

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öntem, çekirdeğin toplayıcı işlevindeki her giriş bağımsız değişkeni için bir giriş Allocation bağımsız değişkenine sahiptir. RenderScript çalışma zamanı, tüm giriş ayırmalarının aynı boyutlara sahip olduğundan ve her bir giriş ayırmasının Element türünün, toplayıcı işlevinin prototipinin karşılık gelen giriş bağımsız değişkeniyle eşleştiğinden emin olmak için kontrol eder. Bu kontrollerden herhangi biri başarısız olursa RenderScript bir istisna oluşturur. Çekirdek, bu boyutlardaki her koordinat üzerinde yürütülür.

Yöntem 2, Yöntem 1 ile aynıdır ancak Yöntem 2, çekirdek yürütmeyi koordinatların bir alt kümesiyle sınırlamak için kullanılabilecek ek bir sc bağımsız değişkeni alır.

Yöntem 3, Allocation girişleri yerine Java dizisi girişleri alması dışında Yöntem 1 ile aynıdır. Bu, açıkça bir Allocation oluşturmak ve bir Java dizisinden veri kopyalamak için kod yazma zahmetinden kurtaran bir kolaylıktır. Ancak Yöntem 1 yerine Yöntem 3'ü kullanmak kodun performansını artırmaz. 3. yöntem, her giriş dizisi için uygun Element türüne ve setAutoPadding(boolean) özelliğine sahip geçici bir tek boyutlu Allocation oluşturur ve diziyi, Allocation sınıfının uygun copyFrom() yöntemiyle kopyalanmış 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 Element türüne sahip farklı dizilerle birden fazla çekirdek çağrısı yapacaksa 3. yöntem yerine ayırmaları kendiniz açıkça oluşturup doldurarak ve yeniden kullanarak performansı artırabilirsiniz.

javaFutureType, yansıtılan azaltma yöntemlerinin dönüş türüdür ve ScriptC_filename sınıfı içinde yansıtılan statik iç içe yerleştirilmiş bir sınıftır. Bir indirgeme çekirdeği çalıştırmasının gelecekteki sonucunu temsil eder. Çalıştırmanın gerçek sonucunu elde etmek için ilgili sınıfın get() yöntemini çağırın. Bu yöntem, javaResultType türünde bir değer döndürür. get() eşzamanlı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 değerine göre belirlenir. resultType işaretsiz bir tür (skaler, vektör veya dizi) değilse javaResultType doğrudan karşılık gelen Java türüdür. resultType işaretsiz bir türse ve daha büyük bir Java işaretli tür varsa javaResultType bu daha büyük Java işaretli türdür. Aksi takdirde, doğrudan karşılık gelen Java türüdür. Örneğin:

  • resultType int, int2 veya int[15] ise javaResultType int, Int2 veya int[] olur. resultType'ın tüm değerleri javaResultType ile temsil edilebilir.
  • resultType uint, uint2 veya uint[15] ise javaResultType long, Long2 veya long[] olur. resultType'ın tüm değerleri javaResultType ile temsil edilebilir.
  • resultType ulong, ulong2 veya ulong[15] ise javaResultType long, Long2 veya long[] olur. resultType'ın, javaResultType ile temsil edilemeyen belirli değerleri vardır.

javaFutureType, outconverter işlevinin resultType'ına karşılık gelen gelecekteki sonuç türüdür.

  • resultType bir dizi türü değilse javaFutureType, result_resultType olur.
  • resultType, memberType türünde üyeleri olan Count uzunluğunda bir diziyse javaFutureType, resultArrayCount_memberType olur.

Örneğin:

Kotlin

class ScriptC_filename(rs: RenderScript) : ScriptC(…) {

    // for kernels with int result
    object result_int {
        fun get(): Int = …
    }

    // for kernels with int[10] result
    object resultArray10_int {
        fun get(): IntArray = …
    }

    // for kernels with int2 result
    //   note that the Kotlin type name "Int2" is not the same as the script type name "int2"
    object result_int2 {
        fun get(): Int2 = …
    }

    // for kernels with int2[10] result
    //   note that the Kotlin type name "Int2" is not the same as the script type name "int2"
    object resultArray10_int2 {
        fun get(): Array<Int2> = …
    }

    // for kernels with uint result
    //   note that the Kotlin type "long" is a wider signed type than the unsigned script type "uint"
    object result_uint {
        fun get(): Long = …
    }

    // for kernels with uint[10] result
    //   note that the Kotlin type "long" is a wider signed type than the unsigned script type "uint"
    object resultArray10_uint {
        fun get(): LongArray = …
    }

    // for kernels with uint2 result
    //   note that the Kotlin type "Long2" is a wider signed type than the unsigned script type "uint2"
    object result_uint2 {
        fun get(): Long2 = …
    }

    // for kernels with uint2[10] result
    //   note that the Kotlin type "Long2" is a wider signed type than the unsigned script type "uint2"
    object resultArray10_uint2 {
        fun get(): Array<Long2> = …
    }
}

Java

public class ScriptC_filename extends ScriptC {
  // for kernels with int result
  public static class result_int {
    public int get() { … }
  }

  // for kernels with int[10] result
  public static class resultArray10_int {
    public int[] get() { … }
  }

  // for kernels with int2 result
  //   note that the Java type name "Int2" is not the same as the script type name "int2"
  public static class result_int2 {
    public Int2 get() { … }
  }

  // for kernels with int2[10] result
  //   note that the Java type name "Int2" is not the same as the script type name "int2"
  public static class resultArray10_int2 {
    public Int2[] get() { … }
  }

  // for kernels with uint result
  //   note that the Java type "long" is a wider signed type than the unsigned script type "uint"
  public static class result_uint {
    public long get() { … }
  }

  // for kernels with uint[10] result
  //   note that the Java type "long" is a wider signed type than the unsigned script type "uint"
  public static class resultArray10_uint {
    public long[] get() { … }
  }

  // for kernels with uint2 result
  //   note that the Java type "Long2" is a wider signed type than the unsigned script type "uint2"
  public static class result_uint2 {
    public Long2 get() { … }
  }

  // for kernels with uint2[10] result
  //   note that the Java type "Long2" is a wider signed type than the unsigned script type "uint2"
  public static class resultArray10_uint2 {
    public Long2[] get() { … }
  }
}

javaResultType bir nesne türüyse (dizi türü dahil) aynı örnekte javaFutureType.get() için yapılan her çağrı aynı nesneyi döndürür.

javaResultType, resultType türündeki tüm değerleri temsil edemiyorsa ve bir azaltma çekirdeği temsil edilemeyen bir değer üretiyorsa javaFutureType.get() bir istisna oluşturur.

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 işaretsiz bir skaler türse devecSiInXType, aynı boyuttaki işaretli skaler türe doğrudan karşılık gelen Java türüdür. inXType, işaretli 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ı boyuttaki işaretli skaler türüne doğrudan karşılık gelen Java türüdür. Örneğin:

  • inXType int ise devecSiInXType int olur.
  • inXType int2 ise devecSiInXType int olur. Dizi, düzleştirilmiş bir temsildir: Tahsisatın 2 bileşenli vektör öğeleri olduğu için iki kat daha fazla skaler öğeye sahiptir. Bu, Allocation yöntemlerinin copyFrom() ile aynı şekilde çalışır.
  • inXType uint ise deviceSiInXType int olur. Java dizisindeki işaretli bir değer, Allocation'da aynı bit desenine sahip işaretsiz bir değer olarak yorumlanır. Bu, Allocation yöntemlerinin copyFrom() ile aynı şekilde çalışır.
  • inXType uint2 ise deviceSiInXType int olur. Bu, int2 ve uint'in işlenme şeklinin bir kombinasyonudur: Dizi, düzleştirilmiş bir temsildir ve Java dizisi işaretli değerleri, RenderScript işaretsiz öğ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 düzleştirilmez.
  • Bir komut dosyasının imzasız girişi, Java tarafında aynı boyutta imzalı bir giriş olarak temsil edilirken, bir komut dosyasının imzasız sonucu, Java tarafında genişletilmiş imzalı bir tür olarak temsil edilir (ulong hariç).

Daha fazla örnek azaltma çekirdeği

#pragma rs reduce(dotProduct) \
  accumulator(dotProductAccum) combiner(dotProductSum)

// Note: No initializer function -- therefore,
// each accumulator data item is implicitly initialized to 0.0f.

static void dotProductAccum(float *accum, float in1, float in2) {
  *accum += in1*in2;
}

// combiner function
static void dotProductSum(float *accum, const float *val) {
  *accum += *val;
}
// Find a zero Element in a 2D allocation; return (-1, -1) if none
#pragma rs reduce(fz2) \
  initializer(fz2Init) \
  accumulator(fz2Accum) combiner(fz2Combine)

static void fz2Init(int2 *accum) { accum->x = accum->y = -1; }

static void fz2Accum(int2 *accum,
                     int inVal,
                     int x /* special arg */,
                     int y /* special arg */) {
  if (inVal==0) {
    accum->x = x;
    accum->y = y;
  }
}

static void fz2Combine(int2 *accum, const int2 *accum2) {
  if (accum2->x >= 0) *accum = *accum2;
}
// Note that this kernel returns an array to Java
#pragma rs reduce(histogram) \
  accumulator(hsgAccum) combiner(hsgCombine)

#define BUCKETS 256
typedef uint32_t Histogram[BUCKETS];

// Note: No initializer function --
// therefore, each bucket is implicitly initialized to 0.

static void hsgAccum(Histogram *h, uchar in) { ++(*h)[in]; }

static void hsgCombine(Histogram *accum,
                       const Histogram *addend) {
  for (int i = 0; i < BUCKETS; ++i)
    (*accum)[i] += (*addend)[i];
}

// Determines the mode (most frequently occurring value), and returns
// the value and the frequency.
//
// If multiple values have the same highest frequency, returns the lowest
// of those values.
//
// Shares functions with the histogram reduction kernel.
#pragma rs reduce(mode) \
  accumulator(hsgAccum) combiner(hsgCombine) \
  outconverter(modeOutConvert)

static void modeOutConvert(int2 *result, const Histogram *h) {
  uint32_t mode = 0;
  for (int i = 1; i < BUCKETS; ++i)
    if ((*h)[i] > (*h)[mode]) mode = i;
  result->x = mode;
  result->y = (*h)[mode];
}

Ek kod örnekleri

BasicRenderScript, RenderScriptIntrinsic ve Hello Compute örnekleri, bu sayfada ele alınan API'lerin kullanımını daha ayrıntılı bir şekilde gösterir.