RenderScript'e Genel Bakış

RenderScript, Android'de yoğun bilgi işlem gerektiren görevleri yüksek performansla çalıştırmak için kullanılan bir çerçevedir. RenderScript, öncelikli olarak veri paralel hesaplamayla kullanılmaya yöneliktir ancak sıralı iş yükleri de bundan yararlanabilir. RenderScript çalışma zamanı, bir cihazda bulunan işlemciler (ör. çok çekirdekli CPU'lar ve GPU'lar) arasında işi paralelleştirir. Bu sayede, iş planlamak yerine algoritmaları ifade etmeye odaklanabilirsiniz. RenderScript, özellikle görüntü işleme, hesaplamalı fotoğrafçılık veya bilgisayar görüşü gerçekleştiren uygulamalar için kullanışlıdır.

RenderScript'i kullanmaya başlamak için anlamanız gereken iki temel kavram vardır:

  • Dil, yüksek performanslı bilgi işlem kodu yazmak için C99'dan türetilmiş bir dildir. RenderScript çekirdeği yazma başlıklı makalede, işlem çekirdekleri yazmak için nasıl kullanılacağı açıklanmaktadır.
  • 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 (Java, Android NDK'da C++ ve C99'dan türetilmiş çekirdek dili) kullanılabilir. Using RenderScript from Java Code (Java kodundan RenderScript kullanma) ve Single-Source RenderScript (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 script adı verilir. Her komut dosyasında kendi çekirdekleri, işlevleri ve değişkenleri bulunur. 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 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, kitaplık projesinde değil, uygulama paketinizde yer alması 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. Bu tür işlemler genellikle ilk kurulum veya daha büyük bir işleme ardışık düzeni içindeki sıralı hesaplamalar için yararlıdır.
  • Sıfır veya daha fazla komut dosyası genel değişkeni. Bir komut dosyası genel değişkeni, C'deki genel değişkene benzer. Java kodundan komut dosyası genel değişkenlerine erişebilirsiniz. Bu değişkenler genellikle RenderScript çekirdeklerine parametre aktarmak için kullanılır. Komut dosyası genel değişkenleri burada daha ayrıntılı olarak açıklanmaktadır.

  • Sıfır veya daha fazla işlem çekirdeği. Hesaplama çekirdeği, RenderScript çalışma zamanını bir veri koleksiyonunda paralel olarak yürütmeye yönlendirebileceğiniz bir işlev veya işlevler koleksiyonudur. İki tür işlem çekirdeği vardır: Eşleme çekirdekleri (foreach çekirdekleri olarak da adlandırılır) ve azaltma ç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 yalnızca değil) bir giriş Allocations koleksiyonunu her seferinde bir Element olmak üzere çıkış Allocation koleksiyonuna dönüştürmek için kullanılır.

    • Basit bir eşleme çekirdeği örneğini aşağıda bulabilirsiniz:

      uchar4 RS_KERNEL invert(uchar4 in, uint32_t x, uint32_t y) {
        uchar4 out = in;
        out.r = 255 - in.r;
        out.g = 255 - in.g;
        out.b = 255 - in.b;
        return out;
      }

      Bu işlev, çoğu açıdan 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 açıklanmıştır. Çekirdekten döndürülen değer, çıktı Allocation içinde uygun konuma otomatik olarak yazılır. Varsayılan olarak bu çekirdek, girişinin tamamında Allocation çalıştırılır. Allocation içinde Element başına bir çekirdek işlevi yürütülür.

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

      NOT: Android 6.0 (API düzeyi 23) öncesinde, bir eşleme çekirdeğinde birden fazla giriş Allocation olmayabilir.

      Çekirdeğin sahip olduğundan daha fazla giriş veya çıkışa ihtiyacınız varsa bu nesneler Allocations betik genel değişkenlerine bağlanmalı ve rs_allocation veya rsSetElementAt_type() aracılığıyla bir çekirdekten ya da çağrılabilir işlevden erişilmelidir.rsGetElementAt_type()

      NOT: RS_KERNEL, RenderScript tarafından kolaylık sağlamak için otomatik olarak tanımlanan bir makrodur:

      #define RS_KERNEL __attribute__((kernel))

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

    • Girişinin Elements değerini toplayan basit bir azaltma çekirdeği örneğini aşağıda görebilirsiniz:

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

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

      Bir azaltma çekirdeği biriktirici işlevi void değerini döndürmeli ve en az iki bağımsız değişkene sahip olmalıdır. İlk bağımsız değişken (bu örnekte accum), bir toplayıcı veri öğesine yönelik bir işaretçidir. İkincisi (bu örnekte val) ise çekirdek başlatmaya iletilen Allocation girişine göre otomatik olarak doldurulur. Toplayıcı veri öğesi, RenderScript çalışma zamanı tarafından oluşturulur ve varsayılan olarak sıfır değerine başlatılır. Varsayılan olarak bu çekirdek, girişinin tamamında çalıştırılır Allocation. Allocation içinde Element başına bir kez toplayıcı işlevi yürütülür. Varsayılan olarak, toplayıcı veri öğesinin son 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 biriktirici işlevinin prototipiyle eşleştiğinden emin olmak için kontrol yapar. Eşleşmezse RenderScript bir istisna oluşturur.

      Bir azaltma çekirdeğinde bir veya daha fazla giriş Allocations vardır ancak çıkış Allocations yoktur.

      İndirgeme çekirdekleri daha ayrıntılı olarak burada açıklanmaktadı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 biriktirici işlevi, x, y ve z özel bağımsız değişkenlerini kullanarak mevcut yürütmenin koordinatlarına erişebilir. Bu bağımsız değişkenler int veya uint32_t türünde olmalıdır. Bu bağımsız değişkenler isteğe bağlıdır.

    Bir eşleme çekirdeği işlevi veya bir azaltma çekirdeği biriktirici işlevi, isteğe bağlı özel context bağımsız değişkenini de alabilir. Bu bağımsız değişkenin türü rs_kernel_context'tir. Mevcut 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ı init() işlevi. init() işlevi, RenderScript'in komut dosyası ilk kez oluşturulduğunda ç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 değişkeni ve işlevi. Statik komut dosyası genel değişkeni, Java kodundan erişilememesi dışında komut dosyası genel değişkenine eşdeğerdir. Statik işlev, komut dosyasındaki herhangi bir çekirdekten veya çağrılabilir işlevden çağrılabilen ancak Java API'ye sunulmayan standart bir C işlevidir. Bir komut dosyası genelinin veya işlevinin Java kodundan erişilmesi gerekmiyorsa static olarak bildirilmesi önemle tavsiye edilir.

Kayan nokta hassasiyetini ayarlama

Bir komut dosyasında gereken kayan nokta duyarlılığı düzeyini kontrol edebilirsiniz. Bu, tam IEEE 754-2008 standardı (varsayılan olarak kullanılır) gerekmiyorsa yararlı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 belirtildiği gibi kayan nokta hassasiyeti gerektiren uygulamalar için.
  • #pragma rs_fp_relaxed: Katı IEEE 754-2008 uygunluğu gerektirmeyen ve daha az hassasiyete tolerans gösterebilen uygulamalar için. Bu mod, denormlar için sıfıra temizleme ve sıfıra doğru yuvarlama işlemlerini etkinleştirir.
  • #pragma rs_fp_imprecise: Kesinlik şartları katı olmayan uygulamalar için. Bu mod, rs_fp_relaxed içindeki her şeyi ve aşağıdakileri etkinleştirir:
    • -0,0 ile sonuçlanan işlemler bunun yerine +0,0 döndürebilir.
    • INF ve NAN üzerindeki işlemler tanımsızdır.

Çoğu uygulama, rs_fp_relaxed'yı herhangi bir yan etki olmadan kullanabilir. Bu, yalnızca gevşek hassasiyetle kullanılabilen ek optimizasyonlar (ör. SIMD CPU talimatları) 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ümlerin yüklü olduğu cihazlarda kullanmanıza olanak tanıyan bir destek kitaplığı aracılığıyla kullanılabilir.

Karşılaştırmalar:

  • Destek kitaplığı API'lerini kullanıyorsanız uygulamanızın RenderScript bölümü, hangi RenderScript özelliklerini kullandığınıza bakılmaksızın Android 2.3 (API düzeyi 9) ve sonraki sürümlerin yüklü olduğu cihazlarla uyumlu olur. Bu sayede uygulamanız, yerel (android.renderscript) API'leri kullandığınızda çalışabileceğinden daha fazla cihazda çalışabilir.
  • Bazı RenderScript özellikleri, Destek Kitaplığı API'leri üzerinden kullanılamaz.
  • Destek kitaplığı API'lerini kullanırsanız yerel (android.renderscript) API'leri kullandığınızdan (muhtemelen önemli ölçüde) daha büyük APK'ler elde edersiniz.

RenderScript Destek Kitaplığı API'lerini kullanma

Support Library RenderScript API'lerini kullanmak için geliştirme ortamınızı bu API'lere 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 üstü
  • Android SDK Build-tools düzeltme sürümü 18.1.0 veya üstü

Android SDK Build-tools 24.0.0'dan itibaren Android 2.2 (API düzeyi 8) sürümünün artık desteklenmediğini unutmayın.

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

Support Library RenderScript API'lerini kullanmak için:

  1. Gerekli Android SDK sürümünün yüklü olduğundan emin olun.
  2. Android derleme sürecinin ayarlarını, RenderScript ayarlarını içerecek şekilde güncelleyin:
    • Uygulama modülünüzün uygulama klasöründe build.gradle dosyasını açın.
    • Dosyaya aşağıdaki RenderScript ayarlarını ekleyin:

      Groovy

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

      Kotlin

              android {
                  compileSdkVersion(33)
      
                  defaultConfig {
                      minSdkVersion(9)
                      targetSdkVersion(19)
      
                      renderscriptTargetApi = 18
                      renderscriptSupportModeEnabled = true
                  }
              }
              

      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ğlayabilen en düşük API seviyesine ayarlamanızı ve renderscriptSupportModeEnabled değerini true olarak belirlemenizi öneririz. Bu ayar için geçerli değerler, 11 ile en son yayınlanan API düzeyi arasındaki herhangi bir tam sayı değeridir. 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, üzerinde ç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 işlemi 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ına bağlıdır. Çoğu uygulama aynı temel kullanım kalıbını izler:

  1. RenderScript bağlamını başlatın. 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 etmek için bir nesne sağlar. Farklı donanım parçalarında kaynak oluşturabileceğinden bağlam oluşturma işleminin uzun sürebileceğini göz önünde bulundurmanız gerekir. Mümkünse bu işlem uygulamanın kritik yolunda olmamalıdır. Genellikle, bir uygulamada aynı anda yalnızca tek bir RenderScript bağlamı bulunur.
  2. Bir 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ış olarak Allocation nesnelerini alır. Allocation nesnelerine, komut dosyası genel değişkenleri olarak bağlandığında rsGetElementAt_type() ve rsSetElementAt_type() kullanılarak çekirdeklerde 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 tüm senaryoları oluşturun. RenderScript kullanırken iki tür komut dosyası kullanabilirsiniz:
    • ScriptC: Bunlar, yukarıdaki RenderScript çekirdeği yazma bölümünde açıklandığı gibi kullanıcı tanımlı komut dosyalarıdır. Her komut dosyasının, Java kodundan komut dosyasına eriş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'dır. Örneğin, yukarıdaki eşleme çekirdeği invert.rs konumunda bulunuyorsa ve bir RenderScript bağlamı zaten mRenderScript konumundaysa komut dosyasını başlatmak 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ığı, konvolüsyon 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'i 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 bölümündeki "kopyalama" yöntemlerinden birini kullanın. "Kopyalama" yöntemleri eşzamanlıdır.
  5. Gerekli komut dosyası genel değişkenlerini ayarlayın. Aynı ScriptC_filename sınıfındaki set_globalname adlı yöntemleri kullanarak genel değişkenler 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ında yansıtılır. Bu lansmanlar eşzamansızdır. Yöntem, çekirdeğe iletilen bağımsız değişkenlere bağlı olarak bir veya daha fazla tahsis alır. Bu tahsislerin tümü aynı boyutlara sahip olmalıdır. Varsayılan olarak, bir çekirdek bu boyutlardaki her koordinat üzerinde yürütülür. Bir çekirdeği bu koordinatların bir alt kümesi üzerinde yürütmek iç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ında 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 Allocation verilerine erişmek için Allocation içindeki "kopyalama" yöntemlerinden birini kullanarak bu verileri tekrar Java'ya kopyalamanız gerekir. İndirgeme çekirdeği 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ı yıkın. RenderScript bağlamını destroy() ile yok edebilir veya RenderScript bağlamı nesnesinin çöp toplama işlemine tabi tutulmasına izin verebilirsiniz. Bu durum, söz konusu bağlama ait herhangi bir nesnenin daha sonraki kullanımlarında istisna oluşmasına neden olur.

Eşzamansız yürütme modeli

Yansıtılan forEach, invoke, reduce ve set yöntemleri eşzamansızdır. Her biri, istenen işlemi tamamlamadan önce Java'ya dönebilir. Ancak bağımsız işlemler, başlatıldıkları sırayla serileştirilir.

Allocation sınıfı, verileri tahsisatlara ve tahsisatlardan kopyalamak için "kopyalama" yöntemleri sağlar. Bir "kopyalama" yöntemi eşzamanlıdır ve aynı tahsisata dokunan yukarıdaki eşzamansız işlemlerden herhangi biriyle ilgili olarak serileştirilir.

Yansıtılan javaFutureType sınıfları, azaltma işleminin sonucunu elde etmek için get() yöntemini sağlar. get(), eşzamanlıdır ve azaltma (eşzamansızdır) açısından serileştirilir.

Tek Kaynaklı RenderScript

Android 7.0 (API seviyesi 24), Tek Kaynaklı RenderScript adlı yeni bir programlama özelliği sunar. Bu özellikte çekirdekler, tanımlandıkları komut dosyasından başlatılır (Java'dan değil). Bu yaklaşım şu anda yalnızca eşleme çekirdekleriyle sınırlıdır. Bu çekirdekler, bu bölümde kısa olması için yalnızca "çekirdekler" olarak adlandırılır. Bu yeni özellik, komut dosyası içinden rs_allocation türünde tahsisler oluşturmayı da destekler. Artık birden fazla çekirdek başlatma işlemi gerekse bile bir algoritmanın tamamını yalnızca bir komut dosyası içinde uygulamak mümkündür. Bu yaklaşımın iki avantajı vardır: Bir algoritmanın tek bir dilde uygulanmasını sağladığı için daha okunabilir kod ve birden fazla çekirdek başlatma işlemi arasında Java ile RenderScript arasında daha az geçiş yapıldığı için potansiyel olarak 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() işlevini çağıran, çağrılabilir bir işlev yazarsınız. Bu API, ilk parametre olarak bir çekirdek işlevi, ardından giriş ve çıkış ayırmaları alır. Benzer bir API rsForEachWithOptions(), çekirdek işlevinin işleyeceği giriş ve çıkış tahsislerindeki öğelerin bir alt kümesini belirten rs_script_call_t türünde ek bir bağımsız değişken alır.

RenderScript hesaplamasını başlatmak için Java'dan çağrılabilir işlevi çağırırsınız. Java kodundan RenderScript kullanma bölümündeki adımları uygulayın. Uygun çekirdekleri başlatma adımında, invoke_function_name() kullanarak çağrılabilir işlevi çağırın. Bu işlem, çekirdeklerin başlatılması da dahil olmak üzere tüm hesaplamayı başlatır.

Bir çekirdek başlatmadan diğerine ara sonuçları kaydetmek ve iletmek için genellikle ayırma işlemi gerekir. Bunları rsCreateAllocation() kullanarak oluşturabilirsiniz. Bu API'nin kullanımı kolay bir biçimi rsCreateAllocation_<T><W>(…)'dir. Burada T, bir öğenin veri türü, W ise öğenin vektör genişliğidir. API, boyutları X, Y ve Z boyutlarında bağımsız değişken olarak alır. 1 boyutlu veya 2 boyutlu tahsislerde, Y veya Z boyutu için boyut atlanabilir. Örneğin, rsCreateAllocation_uchar4(16384), her biri uchar4 türünde olan 16384 öğelik 1 boyutlu bir ayırma oluşturur.

Tahsisler sistem tarafından otomatik olarak yönetilir. Bunları açıkça yayınlamanız veya serbest bırakmanız gerekmez. Ancak, sistemin kaynakları mümkün olduğunca erken serbest bırakabilmesi için temel tahsis için artık rsClearObject(rs_allocation* alloc) çağrısını yaparak tutamağa ihtiyacınız olmadığını belirtebilirsiniz. alloc

RenderScript çekirdeği yazma bölümünde, bir görüntüyü ters çeviren örnek bir çekirdek yer alır. Aşağıdaki örnekte, Single-Source RenderScript kullanılarak bir resme birden fazla efekt uygulanması gösterilmektedir. Bu filtre, renkli bir resmi siyah-beyaza dönüştüren başka bir çekirdek olan greyscale'yı da içerir. Daha sonra çağrılabilir bir işlev process() bu iki çekirdeği giriş görüntüsüne sırayla uygular ve çı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 RenderScript dilinde nasıl tamamen uygulanabileceği gösterilmektedir. Single-Source RenderScript olmadan her iki çekirdeği de Java kodundan başlatmanız, çekirdek başlatmalarını çekirdek tanımlarından ayırmanız ve algoritmanın tamamını anlamanız zorlaşırdı. Tek kaynaklı RenderScript kodu yalnızca daha kolay okunmakla kalmaz, aynı zamanda çekirdek başlatmaları sırasında Java ile komut dosyası arasında geçişi de ortadan kaldırır. Bazı yinelemeli algoritmalar, çekirdekleri yüzlerce kez başlatabilir. Bu da geçişin ek yükünü önemli ölçüde artırır.

Komut Dosyası Genel Değişkenleri

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

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

  • Komut dosyasında var için statik başlatıcı varsa hem Java'da hem de komut dosyasında var'ın başlangıç değeri belirtilir. Aksi takdirde, bu ilk değer sıfırdır.
  • Komut dosyasındaki var'a yapılan erişimler, komut dosyasının 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 statik başlatıcılar hariç olmak üzere, bir komut dosyası içinden genel bir değişkene yazılan değerlerin Java tarafından görünmediği anlamına gelir.

Derinlemesine İndirgeme Çekirdekleri

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

  • tüm verilerin toplamını veya çarpımını hesaplama
  • Tüm veriler üzerinde mantıksal işlemler (and, or, xor) gerçekleştirme
  • verilerdeki minimum veya maksimum değeri bulma
  • belirli bir değeri veya verilerdeki 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 çekirdeklerini başlatabilirsiniz.

Yukarıdaki örnekte basit bir addint azaltma çekirdeği gösterilmektedir. Aşağıda, 1 boyutlu Allocation içinde minimum ve maksimum long değerlerinin konumlarını bulan daha karmaşık bir findMinAndMax azaltma çekirdeği verilmiştir:

#define LONG_MAX (long)((1UL << 63) - 1)
#define LONG_MIN (long)(1UL << 63)

#pragma rs reduce(findMinAndMax) \
  initializer(fMMInit) accumulator(fMMAccumulator) \
  combiner(fMMCombiner) outconverter(fMMOutConverter)

// Either a value and the location where it was found, or INITVAL.
typedef struct {
  long val;
  int idx;     // -1 indicates INITVAL
} IndexedVal;

typedef struct {
  IndexedVal min, max;
} MinAndMax;

// In discussion below, this initial value { { LONG_MAX, -1 }, { LONG_MIN, -1 } }
// is called INITVAL.
static void fMMInit(MinAndMax *accum) {
  accum->min.val = LONG_MAX;
  accum->min.idx = -1;
  accum->max.val = LONG_MIN;
  accum->max.idx = -1;
}

//----------------------------------------------------------------------
// In describing the behavior of the accumulator and combiner functions,
// it is helpful to describe hypothetical functions
//   IndexedVal min(IndexedVal a, IndexedVal b)
//   IndexedVal max(IndexedVal a, IndexedVal b)
//   MinAndMax  minmax(MinAndMax a, MinAndMax b)
//   MinAndMax  minmax(MinAndMax accum, IndexedVal val)
//
// The effect of
//   IndexedVal min(IndexedVal a, IndexedVal b)
// is to return the IndexedVal from among the two arguments
// whose val is lesser, except that when an IndexedVal
// has a negative index, that IndexedVal is never less than
// any other IndexedVal; therefore, if exactly one of the
// two arguments has a negative index, the min is the other
// argument. Like ordinary arithmetic min and max, this function
// is commutative and associative; that is,
//
//   min(A, B) == min(B, A)               // commutative
//   min(A, min(B, C)) == min((A, B), C)  // associative
//
// The effect of
//   IndexedVal max(IndexedVal a, IndexedVal b)
// is analogous (greater . . . never greater than).
//
// Then there is
//
//   MinAndMax minmax(MinAndMax a, MinAndMax b) {
//     return MinAndMax(min(a.min, b.min), max(a.max, b.max));
//   }
//
// Like ordinary arithmetic min and max, the above function
// is commutative and associative; that is:
//
//   minmax(A, B) == minmax(B, A)                  // commutative
//   minmax(A, minmax(B, C)) == minmax((A, B), C)  // associative
//
// Finally define
//
//   MinAndMax minmax(MinAndMax accum, IndexedVal val) {
//     return minmax(accum, MinAndMax(val, val));
//   }
//----------------------------------------------------------------------

// This function can be explained as doing:
//   *accum = minmax(*accum, IndexedVal(in, x))
//
// This function simply computes minimum and maximum values as if
// INITVAL.min were greater than any other minimum value and
// INITVAL.max were less than any other maximum value.  Note that if
// *accum is INITVAL, then this function sets
//   *accum = IndexedVal(in, x)
//
// After this function is called, both accum->min.idx and accum->max.idx
// will have nonnegative values:
// - x is always nonnegative, so if this function ever sets one of the
//   idx fields, it will set it to a nonnegative value
// - if one of the idx fields is negative, then the corresponding
//   val field must be LONG_MAX or LONG_MIN, so the function will always
//   set both the val and idx fields
static void fMMAccumulator(MinAndMax *accum, long in, int x) {
  IndexedVal me;
  me.val = in;
  me.idx = x;

  if (me.val <= accum->min.val)
    accum->min = me;
  if (me.val >= accum->max.val)
    accum->max = me;
}

// This function can be explained as doing:
//   *accum = minmax(*accum, *val)
//
// This function simply computes minimum and maximum values as if
// INITVAL.min were greater than any other minimum value and
// INITVAL.max were less than any other maximum value.  Note that if
// one of the two accumulator data items is INITVAL, then this
// function sets *accum to the other one.
static void fMMCombiner(MinAndMax *accum,
                        const MinAndMax *val) {
  if ((accum->min.idx < 0) || (val->min.val < accum->min.val))
    accum->min = val->min;
  if ((accum->max.idx < 0) || (val->max.val > accum->max.val))
    accum->max = val->max;
}

static void fMMOutConverter(int2 *result,
                            const MinAndMax *val) {
  result->x = val->min.idx;
  result->y = val->max.idx;
}

NOT: Daha fazla örnek azaltma çekirdeğini burada bulabilirsiniz.

RenderScript çalışma zamanı, azaltma çekirdeğini çalıştırmak için azaltma işleminin durumunu tutan bir veya daha fazla toplayıcı veri öğesi adlı değişken oluşturur. RenderScript çalışma zamanı, performansı en üst düzeye çıkaracak şekilde biriktirici 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 yönelik bir işaretçidir. Varsayılan olarak her bir biriktirici veri öğesi sıfır olarak başlatılır (memset ile olduğu gibi). Ancak farklı bir işlem yapmak için başlatıcı işlev yazabilirsiniz.

Örnek: addint çekirdeğinde, giriş değerlerini toplamak için biriktirici veri öğeleri (int türünde) kullanılır. Başlatıcı işlev olmadığından her bir biriktirici veri öğesi sıfır olarak başlatılır.

Örnek: findMinAndMax çekirdeğinde, şimdiye kadar bulunan minimum ve maksimum değerleri takip etmek için biriktirici veri öğeleri (MinAndMax türünde) kullanılır. Bunları sırasıyla LONG_MAX ve LONG_MIN olarak ayarlamak için bir başlatıcı işlev vardır. Ayrıca, bu değerlerin konumlarını -1 olarak ayarlayarak değerlerin, işlenen girişin (boş) kısmında aslında bulunmadığını belirtir.

RenderScript, girişlerdeki her koordinat için bir kez toplayıcı işlevinizi çağırır. Genellikle işleviniz, girişe göre bir şekilde biriktirici veri öğesini güncellemelidir.

Örnek: addint çekirdeğinde, bir giriş öğesinin değeri biriktirici veri öğesine eklenir.

Örnek: findMinAndMax çekirdeğinde, bir giriş öğesinin değerinin birikim verisi öğesinde kayıtlı minimum değerden küçük veya bu değere eşit ve/veya birikim verisi öğesinde kayıtlı maksimum değerden büyük veya bu değere eşit olup olmadığını kontrol eden birikim işlevi, birikim verisi öğesini buna göre günceller.

Girişlerdeki her koordinat için bir kez toplayıcı işlevi çağrıldıktan sonra RenderScript, toplayıcı veri öğelerini tek bir toplayıcı veri öğesinde birleştirmelidir. Bunu yapmak için bir birleştirme işlevi yazabilirsiniz. Toplayıcı işlevinin tek bir girişi ve özel bağımsız değişkeni yoksa 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ış sizin istediğiniz gibi değilse yine de bir birleştirme işlevi yazabilirsiniz.)

Örnek: addint çekirdeğinde birleştirici işlev olmadığından biriktirici işlev kullanılır. Bu doğru bir davranıştır. Çünkü bir değerler 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ı şeydir.

Örnek: findMinAndMax çekirdeğinde, birleştirici işlev, "kaynak" biriktirici veri öğesinde *val kaydedilen minimum değerin "hedef" biriktirici veri öğesinde *accum kaydedilen minimum değerden 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, giriş değerlerinin bir kısmı *accum, bir kısmı ise *val içine değil de tamamı *accum içine biriktirilmiş olsaydı sahip olacağı duruma günceller.

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

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

Örnek: findMinAndMax çekirdeğinde, outconverter işlevi, tüm biriktirici veri öğelerinin birleştirilmesiyle elde edilen minimum ve maksimum değerlerin konumlarını tutmak için bir int2 sonuç değerini 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 işlevlerin tümü static olmalıdır. İndirgeme çekirdeği her zaman bir accumulator işlevi gerektirir. Çekirdeğin ne yapmasını istediğinize bağlı olarak diğer işlevlerin bazılarını veya tümünü atlayabilirsiniz.

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

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

  • reduce(kernelName) (zorunlu): Bir azaltma ç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 azaltma çekirdeği için başlatıcı işlevinin adını belirtir. Çekirdeği başlattığınızda RenderScript, her toplayıcı veri öğesi için bu işlevi bir kez çağırır. İşlev şu şekilde tanımlanmalıdır:

    static void initializerName(accumType *accum) {  }

    accum, bu işlevin başlatılacağı bir biriktirici veri öğesinin işaretçisidir.

    Başlatıcı işlevi sağlamazsanız RenderScript, her bir biriktirici veri öğesini sıfır olarak başlatır (memset ile yapılmış gibi). Bu durumda, başlatıcı işlevi şu şekilde görünür:

    static void initializerName(accumType *accum) {
      memset(accum, 0, sizeof(*accum));
    }
  • accumulator(accumulatorName) (zorunlu): Bu azaltma çekirdeği için biriktirici 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 girişlere göre bir şekilde birikim verisi öğesini 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 biriktirici veri öğesine yönelik bir işaretçidir. 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. Giriş başına 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 azaltma çekirdeği için birleştirici işlevin adını belirtir. RenderScript, girişlerdeki her koordinat için bir kez biriktirici işlevini çağırdıktan sonra, tüm biriktirici veri öğelerini tek bir biriktirici veri öğesinde birleştirmek için bu işlevi gerektiği kadar çok kez ç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" biriktirici veri öğesine yönelik bir işaretçidir. other, bu işlevin *accum içinde "birleştireceği" bir "kaynak" biriktirici veri öğesine yönelik bir işaretçidir.

    NOT: *accum, *other veya her ikisinin de başlatılmış ancak hiçbir zaman biriktirici işlevine aktarılmamış olması mümkündür. Yani, bir veya her ikisi de herhangi bir giriş verisine göre hiçbir zaman 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 biriktirici veri öğesini gösterir.

    Birleştirme işlevi sağlamazsanız RenderScript bunun yerine biriktirici işlevini kullanır ve şu şekilde bir birleştirme 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ş verisi türü biriktirici veri türüyle aynı değilse veya biriktirici 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önüştürücü işlevinin adını belirtir. RenderScript, tüm biriktirici veri öğelerini birleştirdikten sonra, Java'ya döndürülecek azaltma sonucunu belirlemek için bu işlevi çağırır. İşlev şu şekilde tanımlanmalıdır:

    static void outconverterName(resultType *result, const accumType *accum) {  }

    result, bu işlevin azaltma sonucuyla başlatılması için bir sonuç veri öğesine (RenderScript çalışma zamanı tarafından ayrılmış ancak başlatılmamış) yönelik bir 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 son biriktirici veri öğesine yönelik bir işaretçidir.

    Bir outconverter işlevi sağlamazsanız RenderScript, son biriktirici veri öğesini sonuç veri öğesine kopyalar ve şu şekilde bir outconverter işlevi varmış gibi davranır:

    static void outconverterName(accumType *result, const accumType *accum) {
      *result = *accum;
    }

    Biriktirici veri türünden farklı bir sonuç türü istiyorsanız outconverter işlevi zorunludur.

Bir çekirdeğin giriş türleri, bir biriktirici 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 giriş türü long, biriktirici veri öğesi türü MinAndMax ve sonuç türü int2 birbirinden farklıdır.

Neleri varsayamazsınız?

Belirli bir çekirdek başlatma işlemi için RenderScript tarafından oluşturulan biriktirici veri öğelerinin sayısına güvenmemelisiniz. Aynı girişlere sahip aynı çekirdeğin iki kez başlatılmasının aynı sayıda biriktirici veri öğesi oluşturacağı garanti edilmez.

RenderScript'in başlatıcı, toplayıcı ve birleştirici işlevlerini çağırma sırasına güvenmemelisiniz. Hatta bazılarını paralel olarak çağırabilir. Aynı girişle aynı çekirdeğin iki kez başlatılmasının aynı sırayı izleyeceği garanti edilmez. Tek garanti, başlatıcı işlevin hiçbir zaman başlatılmamış bir biriktirici veri öğesi görmeyeceğidir. Örneğin:

  • Biriktirici işlevi yalnızca başlatılmış bir biriktirici veri öğesinde çağrılacak olsa da, bu işlev çağrılmadan önce tüm biriktirici veri öğelerinin başlatılacağı garanti edilmez.
  • Giriş öğelerinin biriktirici işlevine aktarılma sırası garanti edilmez.
  • Birleştirici işlevi çağrılmadan önce biriktirici işlevinin tüm giriş öğeleri için çağrıldığı garanti edilmez.

Bunun bir sonucu olarak findMinAndMax çekirdeği deterministik değildir: Girişte aynı minimum veya maksimum değerden birden fazla varsa çekirdeğin hangi değeri bulacağını bilemezsiniz.

Neyi garanti etmeniz gerekir?

RenderScript sistemi bir çekirdeği birçok farklı şekilde yürütmeyi seçebileceğinden, çekirdeğinizin istediğiniz şekilde çalışmasını sağlamak için belirli kurallara uymanız gerekir. Bu kurallara uymazsanız yanlış sonuçlar, nondeterministik davranışlar veya çalışma zamanı hataları alabilirsiniz.

Aşağıdaki kurallarda genellikle iki biriktirici 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 azaltma için genellikle "aynı"nın matematiksel eşitlik anlamına gelmesi mantıklıdır. findMinAndMax ("minimum ve maksimum giriş değerlerinin konumunu bul") gibi "herhangi birini seç" aramalarında, aynı giriş değerlerinin birden fazla kez oluşabileceği durumlarda, belirli bir giriş değerinin tüm konumları "aynı" olarak kabul edilmelidir. "En soldaki minimum ve maksimum giriş değerlerinin konumunu bul"a benzer bir çekirdek yazabilirsiniz. Burada (örneğin) 100 konumundaki 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. Biriktirici ve birleştirici işlevler, findMinAndMax işlevlerindekinden farklı olmalıdır.

Başlatıcı işlev bir kimlik değeri oluşturmalıdır. Yani, I ve A başlatıcı işlevi tarafından başlatılan birikim verisi öğeleriyse ve I hiçbir zaman birikim işlevine iletilmediyse (ancak A iletilmiş olabilir)
  • combinerName(&A, &I), A aynı bırakmalıdır.
  • combinerName(&I, &A), I aynı bırakmalıdır.A

Örnek: addint çekirdeğinde bir biriktirici veri öğesi sıfır olarak başlatılır. Bu çekirdeğin birleştirme işlevi toplama işlemi gerçekleştirir. Sıfır, toplama işleminin kimlik değeridir.

Örnek: findMinAndMax çekirdeğinde, bir biriktirici 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 INITVAL olduğu için I değerini A olarak ayarlar.

Bu nedenle, INITVAL gerçekten bir kimlik değeridir.

Birleştirme işlevi değişme özelliği taşımalı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 aktarılmış olabilecek toplayıcı veri öğeleriyse combinerName(&A, &B), combinerName(&B, &A) öğesinin B için ayarladığı aynı değeri A için ayarlamalıdır.

Örnek: addint çekirdeğinde, birleştirici işlevi iki biriktirici veri öğesi değerini toplar; toplama işlemi değişme özelliğine sahiptir.

Örnek: findMinAndMax çekirdeğinde, fMMCombiner(&A, &B) ile A = minmax(A, B) aynıdır ve minmax değişme özelliğine sahiptir. Bu nedenle fMMCombiner de değişme özelliğine sahiptir.

Birleştirme işlevi birleştirilebilir 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 aktarılmış olabilecek toplayıcı veri öğeleri ise 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 biriktirici 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şme özelliğine sahiptir. Bu nedenle, birleştirme işlevi de birleşme özelliğine sahiptir.

Örnek: findMinAndMax çekirdeğinde,

fMMCombiner(&A, &B)
ile aynıdır.
A = minmax(A, B)
Bu nedenle iki dizi şöyledir:
  • 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şkilidir, dolayısıyla 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 biriktirici veri öğeleri ise, A başlatıcı işleviyle başlatılmışsa ve biriktirici işlevine sıfır veya daha fazla kez aktarılmışsa, B başlatılmamışsa ve args, biriktirici işlevine yapılan belirli bir çağrı için giriş bağımsız değişkenlerinin ve özel bağımsız değişkenlerin 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ıdır.
  • 2. ifade, B = 0 ile aynı
  • 3. ifade, B += V ile aynıdır ve B = V ile aynıdır.
  • 4. ifade, A += B ile aynıdır ve A += V ile aynıdır.

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

Örnek: findMinAndMax çekirdeğinde, X koordinatındaki V giriş değeri için:

  • 1. ifade, A = minmax(A, IndexedVal(V, X)) ile aynıdır.
  • 2. ifade, B = INITVAL ile aynı
  • 3. ifade, aşağıdakilerle aynıdır:
    B = minmax(B, IndexedVal(V, X))
    Bu değer, B başlangıç değeri olduğundan
    B = IndexedVal(V, X)
  • 4. ifade,
    A = minmax(A, B)
    aynı
    A = minmax(A, IndexedVal(V, X))

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

Java kodundan bir azaltma çekirdeğini çağırma

filename.rs dosyasında tanımlanan kernelName adlı bir azaltma çekirdeği için ScriptC_filename sınıfında 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ö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ş Allocations'ın aynı boyutlara sahip olduğundan ve giriş Allocations'ın her birinin Element türünün, biriktirici işlevinin prototipinin karşılık gelen giriş bağımsız değişkeninin türüyle eşleştiğinden emin olmak için kontroller yapar. 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.

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

3. Yöntem, 1. Yöntem ile aynıdır. Tek farkı, tahsis girişleri yerine Java dizisi girişleri almasıdır. Bu özellik, bir tahsis oluşturmak ve verileri bir Java dizisinden kopyalamak için açıkça kod yazma zorunluluğunu ortadan kaldırarak size kolaylık sağlar. Ancak 1. Yöntem yerine 3. Yöntem'in kullanılması kodun performansını artırmaz. Yöntem 3, her giriş dizisi için uygun Element türüyle ve setAutoPadding(boolean) etkinleştirilmiş olarak geçici bir 1 boyutlu Allocation oluşturur ve diziyi, Allocation'ün uygun copyFrom() yöntemiyle Allocation'a kopyalar. Ardından, bu geçici tahsisleri ileterek 1. Yöntem'i çağırır.

NOT: Uygulamanız aynı diziyle veya aynı boyutlara ve öğe türüne sahip farklı dizilerle birden fazla çekirdek çağrısı yapacaksa 3. yöntemi kullanmak yerine tahsisleri kendiniz açıkça oluşturup doldurarak ve yeniden kullanarak performansı artırabilirsiniz.

Yansıtılan azaltma yöntemlerinin dönüş türü olan javaFutureType, ScriptC_filename sınıfı içinde yansıtılan statik bir iç içe sınıfıdır. Bu, bir azaltma çekirdeği çalıştırmasının gelecekteki sonucunu gösterir. Çalıştırmanın gerçek sonucunu almak için bu 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ğerinden belirlenir. resultType işaretsiz bir tür (skaler, vektör veya dizi) olmadığı sürece 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 imzalı tür varsa javaResultType bu daha büyük Java imzalı türdür. Aksi takdirde, doğrudan karşılık gelen Java türüdür. Örneğin:

  • resultType, int, int2 veya int[15] ise javaResultType, int, Int2 veya int[]'dir. resultType'ın tüm değerleri javaResultType ile gösterilebilir.
  • resultType, uint, uint2 veya uint[15] ise javaResultType, long, Long2 veya long[] olur. resultType'ın tüm değerleri javaResultType ile gösterilebilir.
  • resultType, ulong, ulong2 veya ulong[15] ise javaResultType, long, Long2 veya long[]'dür. resultType'ın javaResultType ile gösterilemeyen belirli değerleri vardır.

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

  • resultType bir dizi türü değilse javaFutureType, result_resultType olur.
  • resultType, Count uzunluğunda ve memberType türünde üyeler içeren bir diziyse javaFutureType, resultArrayCount_memberType olur.

Örneğin:

Kotlin

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

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

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

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

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

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

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

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

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

Java

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

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

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

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

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

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

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

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

javaResultType bir nesne türüyse (dizi türü dahil), aynı örnekteki her javaFutureType.get() çağrısı aynı nesneyi döndürür.

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

3. yöntem ve devecSiInXType

devecSiInXType, toplama işlevinin ilgili bağımsız değişkeninin inXType'ına karşılık gelen Java türüdür. inXType işaretsiz bir tür veya vektör türü olmadığı sürece 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ürle doğrudan eşleşen Java türüdür. inXType imzalı bir vektör türüyse devecSiInXType, vektör bileşen türüne doğrudan karşılık gelen Java türüdür. inXType işaretsiz bir vektör türüyse devecSiInXType, vektör bileşen türüyle aynı boyutta olan 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 gösterimdir: Tahsisin 2 bileşenli vektör öğeleri olduğu için iki kat daha fazla skaler öğe içerir. copyFrom() Allocation yöntemleri de aynı şekilde çalışır.
  • inXType uint ise deviceSiInXType int olur. Java dizisindeki imzalı bir değer, Allocation'da aynı bit deseniyle imzalanmamış bir değer olarak yorumlanır. Bu, copyFrom() yöntemlerinin Allocation çalışma şekliyle aynıdır.
  • inXType uint2 ise deviceSiInXType int olur. Bu, int2 ve uint değerlerinin işlenme şeklinin bir kombinasyonudur: Dizi, düzleştirilmiş bir gösterimdir ve Java dizisi imzalı değerleri, RenderScript imzalanmamış öğe değerleri olarak yorumlanır.

3. yöntemde 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 vektör sonucu düzleştirilmez.
  • Bir komut dosyasının işaretsiz girişi, Java tarafında aynı boyutta imzalı bir giriş olarak gösterilirken bir komut dosyasının işaretsiz sonucu, Java tarafında genişletilmiş imzalı bir tür olarak gösterilir (ulong hariç).

Diğer örnek indirgeme ç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ı olarak gösterir.