RenderScript'e Genel Bakış

RenderScript, yoğun bilgi işlem gerektiren görevleri yüksek performansla çalıştırmak için kullanılan Android RenderScript, seri işleme olsa da öncelikli olarak veriye paralel hesaplama için tasarlanmıştır. fayda sağlayabilir. RenderScript çalışma zamanı, cihazda bulunan çok çekirdekli CPU'lar ve GPU'lar gibi işlemciler arasında paralel olarak çalışır. 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şlarken 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. Bir RenderScript Kernel'in yazılması, işlem çekirdekleri yazmak için nasıl kullanılacağını öğreneceğiz.
  • control API, RenderScript kaynaklarının kullanım ömrünü yönetmek için kullanılır ve nasıl yapıldığını göstereceğim. Üç farklı dilde kullanılabilir: Java, Android'de C++ NDK ve C99 türetilmiş çekirdek dilinin kendisidir. Java Code'dan RenderScript'i kullanma ve Tek Kaynaklı RenderScript, birinci ve üçüncü kaynak seçenekleri vardır.

RenderScript çekirdeği yazma

RenderScript çekirdeği genellikle <project_root>/src/rs dizinindeki bir .rs dosyasında bulunur. Her .rs dosyasına komut dosyası denir. Her komut dosyası kendi çekirdek, işlev ve değişken kümesini içerir. Komut dosyaları şunları içerebilir:

  • #pragma version(1) Bu komut dosyasında kullanılan RenderScript çekirdek dili. Şu anda tek geçerli değer 1'dir.
  • Bu komut dosyasından yansıtılan Java sınıflarının paket adını belirten bir pragma beyanı (#pragma rs java_package_name(com.example.app)). .rs dosyanızın, bir kitaplık projesinde değil, uygulama paketinizin parçası olması gerektiğini unutmayın.
  • Sıfır veya daha fazla çağrılanabilir 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 çoğu zaman ilk kurulum veya seri hesaplamalar için kullanılabilecektir.
  • Sıfır veya daha fazla komut dosyası global. Genel komut dosyası, C'deki genel değişkene benzer. Java kodundan komut dosyası genel değişkenlerine erişebilirsiniz. Bu değişkenler genellikle RenderScript çekirdeklerine parametre aktarmak için kullanılır. Komut dosyası global işlevleri burada daha ayrıntılı olarak açıklanmıştır.

  • Sıfır veya daha fazla işlem çekirdek. İşlem çekirdeği, bir işlevdir RenderScript çalışma zamanını paralel olarak yürütülmesi için yönlendirebileceğiniz fonksiyonlar veya koleksiyon veri kümesi genelinde kullanılır. İki tür bilgi işlem vardır çekirdekler: eşleme çekirdekleri (foreach çekirdekleri olarak da adlandırılır) ve azaltma çekirdekleridir.

    Eşleme çekirdeği, aynı boyutlardaki Allocations koleksiyonunda çalışan paralel bir işlevdir. Varsayılan olarak bu işlev, bu boyutlardaki her koordinat için bir kez yürütülür. Normalde (ancak özel olarak değil) Allocations girişi koleksiyonunu Allocation için bir Element çıkışı, gerekir.

    • Aşağıda basit bir eşleme çekirdeği örneği verilmiştir:

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

      Bu, çoğu bakımdan standart C ile aynıdır. işlevini kullanın. İşlev prototipine uygulanan RS_KERNEL özelliği, işlevin çağrılabilir bir işlev yerine bir RenderScript eşleme çekirdeği olduğunu belirtir. in bağımsız değişkeni, çekirdek başlatmaya iletilen Allocation girişine göre otomatik olarak doldurulur. İlgili içeriği oluşturmak için kullanılan x ve y bağımsız değişkenleri aşağıda ele alınmıştır. Çekirdekten döndürülen değer, çıktıdaki uygun konuma Allocation otomatik olarak yazılır. Varsayılan olarak bu çekirdek, girişin tamamında çalıştırılır Allocation: Allocation içinde her Element için çekirdek işlevi bir kez yürütülür.

      Eşleme çekirdeğinde bir veya daha fazla giriş (Allocations), tek bir çıkış (Allocation) veya her ikisi birden bulunabilir. İlgili içeriği oluşturmak için kullanılan Tüm giriş ve çıkış ayırmalarının aynı olduğundan emin olmak için RenderScript çalışma zamanı kontrolleri ve Element giriş ve çıkış türlerinin Ayırmalar, çekirdeğin prototipiyle eşleşir; bunlardan biri başarısız olursa RenderScript bir istisna oluşturur.

      NOT: Android 6.0'dan (API düzeyi 23) önce bir eşleme çekirdeği birden fazla Allocation girişi olmamalıdır.

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

      NOT: RS_KERNEL, bir makrodur RenderScript tarafından otomatik olarak tanımlanır:

      #define RS_KERNEL __attribute__((kernel))

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

    • Burada basit bir azaltmaörnek görebilirsiniz çekirdekElements giriş:

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

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

      Azaltma çekirdeği toplayıcı işlevi void döndürmeli ve en az iki bağımsız değişkene sahip olmalıdır. İlk bağımsız değişken (bu örnekte accum), bir toplayıcı veri öğesine işaretçidir ve ikinci bağımsız değişken (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 ve varsayılan olarak sıfır olarak başlatılır. Varsayılan olarak bu çekirdek, girişinin tamamı Allocation üzerinde çalıştırılır. Bu sırada, Allocation içindeki her Element için bir toplayıcı işlevi yürütülür. Toplayıcı veri öğesinin nihai değeri varsayılan olarak azaltmanın sonucu olarak değerlendirilir ve Java'ya döndürülür. RenderScript çalışma zamanı, giriş ayırma işlevinin Element türünün toplayıcı işlevinin prototip oluşturabilirsiniz. eşleşmiyorsa RenderScript bir istisna uygular.

      Azaltma çekirdeğinin bir veya daha fazla girişi Allocations vardır ancak çıkışı Allocations yoktur.

      Azaltma çekirdekleri hakkında daha fazla bilgiyi burada bulabilirsiniz.

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

    Bir eşleme çekirdek işlevi veya azaltma çekirdek toplayıcı işlevi, int veya uint32_t türündeki x, y ve z özel bağımsız değişkenlerini kullanarak geçerli yürütmenin koordinatlarına erişebilir. Bu bağımsız değişkenler isteğe bağlıdır.

    Eşleme çekirdeği işlevi veya indirgeme çekirdeği toplayıcısı işlevi, isteğe bağlı özel bağımsız değişkeni de alabilir rs_kernel_context türünde context. Geçerli yürütmenin belirli özelliklerini sorgulamak için kullanılan bir çalışma zamanı API'si ailesi (ör. rsGetDimX) 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ığı çalıştırılabilir bir işlevdir. Bu sayede, komut dosyası oluşturulurken bazı hesaplamalar otomatik olarak yapılabilir.
  • Sıfır veya daha fazla statik komut dosyası genel değişkeni ve işlevi. Statik komut dosyası global, Java kodundan erişilememesi dışında komut dosyası global ile aynıdır. Statik işlev, komut dosyasındaki herhangi bir çekirdekten veya çağrılabilir işlevden çağrılabilen ancak Java API'sine gösterilmeyen standart bir C işlevidir. Bir komut dosyası genel değerine veya işlevine Java kodundan erişilmesi gerekmiyorsa static olarak tanımlanması önemle tavsiye edilir.

Kayan nokta hassasiyetini ayarlama

Bir komut dosyasında gereken kayan nokta hassasiyet düzeyini kontrol edebilirsiniz. Bu, tam IEEE 754-2008 standardı (varsayılan olarak kullanılır) gerekli değilse yararlıdır. Aşağıdaki pragmalar farklı bir kayan nokta hassasiyeti düzeyi ayarlayabilir:

  • #pragma rs_fp_full (hiçbir şey belirtilmezse varsayılan): IEEE 754-2008 standardında belirtildiği şekilde kayan nokta hassasiyeti gerektiren uygulamalar için.
  • #pragma rs_fp_relaxed: Katı IEEE 754-2008 gerektirmeyen uygulamalar için daha az kesinliğe tolerans gösterebilir. Bu mod, değerler için sıfıra boşaltmayı etkinleştirir. sıfıra doğru yuvarlayın.
  • #pragma rs_fp_imprecise: En yüksek hassasiyete sahip olmayan uygulamalar için gereksinimlerini karşılayın. Bu mod, rs_fp_relaxed'teki her şeyi ve aşağıdakileri etkinleştirir:
    • -0,0 ile sonuçlanan işlemler, bunun yerine +0.0 sonucunu döndürebilir.
    • INF ve NAN üzerindeki işlemler tanımlanmamıştır.

Çoğu uygulamada rs_fp_relaxed yan etkisi olmadan kullanılabilir. Yalnızca gevşek hassasiyetle kullanılabilen ek optimizasyonlar (ör. SIMD CPU talimatları) nedeniyle bu, bazı mimarilerde çok yararlı olabilir.

Java'dan RenderScript API'lerine erişme

RenderScript kullanan bir Android uygulaması geliştirirken, API'ye Java'dan erişebilirsiniz. şu iki yöntemden birini kullanabilirsiniz:

  • android.renderscript - Bu sınıf paketindeki API'ler: Android 3.0 (API düzeyi 11) ve sonraki sürümleri çalıştıran cihazlarda kullanılabilir.
  • android.support.v8.renderscript: Bu paketteki API'ler, Android 2.3 (API seviyesi 9) ve sonraki sürümleri çalıştıran cihazlarda kullanabilmenizi sağlayan bir Destek Kitaplığı üzerinden kullanılabilir.

Bunun karşılığında yapılabilecekler şunlardır:

  • Destek Kitaplığı API'lerini kullanıyorsanız uygulamanızın RenderScript kısmı, hangi RenderScript özelliklerini kullandığınızdan bağımsız olarak Android 2.3 (API düzeyi 9) ve sonraki sürümleri çalıştıran cihazlarla uyumlu olur. Bu, uygulamanızın yerel (android.renderscript) API'ler.
  • Belirli RenderScript özellikleri, Destek Kitaplığı API'leri üzerinden kullanılamaz.
  • Destek Kitaplığı API'lerini kullanırsanız yerel (android.renderscript) API'leri kullanıyorsanız.

RenderScript Destek Kitaplığı API'lerini kullanma

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

  • Android SDK Araçları düzeltme 22.2 veya üstü
  • Android SDK Derleme Araçları 18.1.0 veya sonraki bir düzeltme sürümü

Android SDK Derleme Araçları 24.0.0, Android 2.2'den başlayarak (API düzeyi 8) artık desteklenmiyor.

Bu araçların yüklü sürümlerini şurada kontrol edebilir ve güncelleyebilirsiniz: Android SDK Yöneticisi.

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

  1. Gerekli Android SDK sürümünü yüklediğinizden 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.
    • Aşağıdaki RenderScript ayarlarını dosyaya ekleyin:
      EskiKotlin
              android {
                  compileSdkVersion 33
      
                  defaultConfig {
                      minSdkVersion 9
                      targetSdkVersion 19
      
                      renderscriptTargetApi 18
                      renderscriptSupportModeEnabled true
                  }
              }
              
              android {
                  compileSdkVersion(33)
      
                  defaultConfig {
                      minSdkVersion(9)
                      targetSdkVersion(19)
      
                      renderscriptTargetApi = 18
                      renderscriptSupportModeEnabled = true
                  }
              }
              

      Yukarıda listelenen ayarlar, Android derleme işlemindeki belirli davranışı kontrol eder:

      • renderscriptTargetApi: Oluşturulacak bayt kodu sürümünü belirtir. Bu değeri, sunabileceğiniz en düşük API seviyesine ayarlamanızı öneririz. ve renderscriptSupportModeEnabled olarak ayarlayın. Hedef: true. Bu ayar için geçerli değerler tam sayı değerleridir 11'den en son yayınlanan API düzeyine yükseldi. Minimum SDK sürümünüz belirtilenden farklı bir değere ayarlanmışsa, bu değer yok sayılır ve derleme dosyasındaki hedef değer, SDK sürümü.
      • renderscriptSupportModeEnabled - Oluşturulan cihazın çalıştığı cihazda bayt kodunun uyumlu bir sürüme geri dönmesi gerekir , hedef sürümü desteklemiyor.
  3. RenderScript kullanan uygulama sınıflarınızda Destek Kitaplığı için bir içe aktarma ekleyin sınıflar:
    KotlinJava
    import android.support.v8.renderscript.*
    import android.support.v8.renderscript.*;

Java veya Kotlin Kodundan RenderScript'i kullanma

Java veya Kotlin kodundan RenderScript'i kullanmak için android.renderscript ya da android.support.v8.renderscript paketinde bulunan API sınıflarını kullanmanız gerekir. Çoğu uygulama aynı temel kullanım kalıbını izler:

  1. RenderScript bağlamı başlatın. create(Context) ile oluşturulan RenderScript bağlamı, RenderScript'in kullanılabilmesini sağlar ve nesnesini ifade eder. Bağlamı göz önünde bulundurmalısınız. farklı platformlarda kaynak oluşturabileceği için, sürecin donanım parçaları; uygulamanın kritik yolunda hiç yer almamalıdır. yapmasını sağlar. Genellikle, bir uygulamada aynı anda yalnızca tek bir RenderScript bağlamı bulunur.
  2. BirAllocation komut dosyası. Allocation, aşağıdakileri sağlayan bir RenderScript nesnesidir: depolama alanı sunar. Komut dosyalarındaki çekirdekler Allocation alır nesnelerini giriş ve çıkışı olarak kullanır. Allocation nesne ise rsGetElementAt_type() ve kullanılan çekirdeklerde erişildi Komut dosyası genel değerleri olarak bağlandığında rsSetElementAt_type(). Allocation nesne, dizilerin Java kodundan RenderScript'e aktarılmasına izin verir ve tam tersi de geçerlidir. Allocation nesneleri genellikle createTyped() veya createFromBitmap() kullanılarak oluşturulur.
  3. Gerekli tüm komut dosyalarını oluşturun. RenderScript'i kullanırken iki tür komut dosyası kullanabilirsiniz:
    • ScriptC: Bunlar, yukarıdaki RenderScript Kernel'i Yazma bölümünde açıklanan kullanıcı tanımlı komut dosyalarıdır. Her komut dosyasının, Java kodundan komut dosyasına erişmeyi kolaylaştırmak için RenderScript derleyicisi tarafından yansıtılan bir Java sınıfı vardır. Bu sınıfın adı ScriptC_filename'tür. Örneğin, yukarıdaki eşleme çekirdeği invert.rs içindeyse ve mRenderScript içinde zaten bir RenderScript bağlamı varsa komut dosyasını örneklendirecek Java veya Kotlin kodu şu şekilde olur:
      KotlinJava
      val invert = ScriptC_invert(renderScript)
      ScriptC_invert invert = new ScriptC_invert(renderScript);
    • ScriptIntrinsic: Bunlar, Gauss bulanıklaştırma, topolojik dönüşüm ve resim birleştirme gibi yaygın işlemler için yerleşik RenderScript çekirdekleridir. Daha fazla bilgi için ScriptIntrinsic
  4. Ayrıntıları verilerle doldurun. createFromBitmap() ile oluşturulan Ayırmalar hariç olmak üzere, bir ayırma olduğunda boş verilerle doldurulur oluşturulmalıdır. Ayırmayı doldurmak için, "kopyalama" öğelerinden birini kullanın Allocation içinde farklı yöntemler kullanır. "Kopya" eş zamanlı olduğundan emin olun.
  5. Gerekli komut dosyası global değerlerini ayarlayın. set_globalname adlı aynı ScriptC_filename sınıfındaki yöntemleri kullanarak genel değişkenleri ayarlayabilirsiniz. Örneğin, Örneğin, threshold adlı bir int değişkeni ayarlamak için Java yöntemi set_threshold(int); Okuyucu Gelirleri Yöneticisi'ni lookup adlı bir rs_allocation değişkeni kullanıyorsanız Java yöntem set_lookup(Allocation). set yöntemleri eşzamansızlardı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. Çekirdekteki bağımsız değişkenlere bağlı olarak yöntemi bir veya daha fazla Ayırma alır ve bunların tümü aynı boyutlara sahip olmalıdır. Varsayılan olarak bir çekirdek, bu boyutlardaki her koordinat üzerinde yürütülür. Bir çekirdeği bu koordinatların bir alt kümesi üzerinde yürütmek için forEach veya reduce yöntemine son bağımsız değişken olarak uygun bir Script.LaunchOptions gönderin.

    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 nesneden veri alma ve javaFutureType nesneleri. Java kodundan bir Allocation'teki verilere erişmek için Allocation'teki "kopyala" yöntemlerinden birini kullanarak bu verileri Java'ya geri kopyalamanız gerekir. Azaltma çekirdeği sonucunu elde etmek için javaFutureType.get() yöntemini kullanmanız gerekir. "Kopya" ve get() yöntemleri eşzamanlı.
  8. RenderScript bağlamını kaldırın. RenderScript bağlamını destroy() ile veya RenderScript bağlam nesnesine çöp toplama işleminin uygulanmasına izin vererek yok edebilirsiniz. Bu durum, söz konusu bağlama ait herhangi bir nesnenin daha fazla kullanılması durumunda bir istisna atılmasına neden olur.

Eşzamansız yürütme modeli

Yansıtılan forEach, invoke, reduce, ve set yöntemleri eşzamansızdır. Her biri, istenen işlem. Ancak her bir işlem, kullanıma sunuldukları sırayla serileştirilir.

Allocation sınıfı "kopya" özelliğini sağlar veri kopyalama yöntemleri arasında yer alır. "Kopyala" yöntemi senkronizedir ve yukarıdaki aynı tahsisine dokunan eşzamansız işlemlerin herhangi birine göre serileştirilir.

Yansıtılan javaFutureType sınıfları, bir get() yöntemidir. get() eşzamanlı olup azaltmaya (eşzamansız) göre serileştirilir.

Tek Kaynaklı RenderScript

Android 7.0 (API düzeyi 24), Tek Kaynak adlı yeni bir programlama özelliğini kullanıma sunuyor. RenderScript: Çekirdeklerin başka bir şey var. Şu anda bu yaklaşım, yalnızca "çekirdek" olarak adlandırılan çekirdeklerin eşleştirilmesiyle sınırlıdır. kısa ve öz yazımlara dikkat edin. Bu yeni özellik, komut dosyasının içinden rs_allocation türündeki tahsislerin oluşturulmasını da destekler. Artık proje yönetimiyle ilgili birden fazla çekirdek başlatması gerekse bile algoritmanın tamamını yalnızca bir komut dosyası içinde uygulayabilir. Bunun iki avantajı vardır: Algoritmanın tek bir dilde uygulanmasını sağladığı için daha okunaklı kod ve birden fazla çekirdek başlatma işleminde Java ile RenderScript arasında daha az geçiş yapıldığı için potansiyel olarak daha hızlı kod.

Tek Kaynaklı RenderScript'te, çekirdekleri konusunda açıklandığı gibi yazarsınız RenderScript Kernel yazma. Ardından, Bunları başlatmak için rsForEach(). Bu API, ilk olarak bir çekirdek işlevini alır ve parametresi ve ardından giriş ve çıkış ayırmaları gelir. Benzer bir API rsForEachWithOptions(), çekirdek işlevinin işleyeceği giriş ve çıkış tahsislerinden öğelerin bir alt kümesini belirten rs_script_call_t türündeki ek bir bağımsız değişken alır.

RenderScript hesaplamasını başlatmak için Java'dan çağrılabilir işlevi çağırırsınız. Java Code'dan RenderScript'i kullanma bölümündeki adımları uygulayın. Uygun çekirdekleri başlatma adımında, şunu çağırın: başlatılacak olan invoke_function_name() kullanarak tüm hesaplamayı ele alacağız.

Paydaşlar genellikle değişiklikleri kaydedip iletmek için ara sonuçlar elde edilir. Bunları şununla oluşturabilirsiniz: rsCreateAllocation() gibidir. Bu API'nin kullanımı kolay bir biçimi rsCreateAllocation_<T><W>(…)'dir. Burada T, öğenin veri türüdür. öğesi ve W, öğenin vektör genişliğidir. API, X, Y ve Z boyutlarındaki boyutları bağımsız değişken olarak alır. 1D veya 2D ayırmalarda Y veya Z boyutunun boyutu atlanır. Örneğin, rsCreateAllocation_uchar4(16384), şunun 1D tahsisini oluşturur: Her biri uchar4 türünde 16384 öğe.

Ayırma işlemleri sistem tarafından otomatik olarak yönetilir. Bunları açıkça serbest bırakmanız veya serbest bırakmanız gerekmez. Ancak, Herkese açık kullanıcı adına artık ihtiyacınız olmadığını belirtmek için rsClearObject(rs_allocation* alloc) Temel tahsise alloc, Böylece sistem, kaynakları olabildiğince erken serbest bırakabilir.

RenderScript Kernel Yazma bölümü, bir çekirdeğin oluşturulması gerekir. Aşağıdaki örnekte, tek kaynaklı RenderScript kullanılarak bir resme birden fazla efekt uygulama işlemi genişletilmiştir. Renkli bir resmi siyah beyaza dönüştüren başka bir çekirdek (greyscale) içerir. Ardından, çağrılabilir bir işlev process() bu iki çekirdeği art arda bir giriş resmine uygular ve bir çıkış resmi oluşturur. Hem giriş hem de çıkış için ayrılan kaynaklar, rs_allocation türündeki bağımsız değişkenler olarak iletilir.

// File: singlesource.rs

#pragma version(1)
#pragma rs java_package_name(com.android.rssample)

static const float4 weight = {0.299f, 0.587f, 0.114f, 0.0f};

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

uchar4 RS_KERNEL greyscale(uchar4 in) {
  const float4 inF = rsUnpackColor8888(in);
  const float4 outF = (float4){ dot(inF, weight) };
  return rsPackColorTo8888(outF);
}

void process(rs_allocation inputImage, rs_allocation outputImage) {
  const uint32_t imageWidth = rsAllocationGetDimX(inputImage);
  const uint32_t imageHeight = rsAllocationGetDimY(inputImage);
  rs_allocation tmp = rsCreateAllocation_uchar4(imageWidth, imageHeight);
  rsForEach(invert, inputImage, tmp);
  rsForEach(greyscale, tmp, outputImage);
}

process() işlevini Java veya Kotlin'den şu şekilde çağırabilirsiniz:

KotlinJava
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)
// File SingleSource.java

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

Bu örnek, iki çekirdek lansmanı içeren bir algoritmanın tam olarak nasıl uygulanabileceğini gösterir pek çok dili vardır. Tek Kaynaklı RenderScript olmadan her iki çekirdeği de Java kodundan başlatmanız gerekir. Bu durumda çekirdek başlatma işlemleri çekirdek tanımlarından ayrılır ve algoritmanın tamamını anlamanız zorlaşır. Tek Kaynaklı RenderScript kodunun okunması daha kolaydır ve çekirdek başlatmalarında Java ile komut dosyası arasında geçiş yapılmasını ortadan kaldırır. Bazı iteratif algoritmalar çekirdekleri yüzlerce kez başlatabilir. Bu da bu tür geçişlerin ek maliyetini önemli ölçüde artırır.

Komut Dosyası Genelleri

Global komut dosyası, static dışındaki normal bir komut dosyasıdır komut dosyası (.rs) dosyasındaki genel değişken. filename.rs dosyasında tanımlanan var adlı bir komut dosyası global değişkeni için ScriptC_filename sınıfına yansıtılan bir get_var yöntemi bulunur. Global const değilse set_var yöntemi de vardır.

Belirli bir komut dosyasının iki ayrı değeri vardır: Java değer ve script değeri girin. Bu değerler şu şekilde çalışır:

  • var, komut dosyasında statik bir başlatıcıya sahipse hem Java'da hem de komut dosyasında var değerinin ilk değerini belirtir. Aksi takdirde bu ilk değer sıfır olur.
  • Komut dosyası içindeki var öğesine erişir ve komut dosyası değerini ekleyin.
  • get_var yöntemi, Java değer.
  • set_var yöntemi (varsa) Java değerini hemen, komut dosyası değerini ise eşzamansız olarak yazar.

NOT: Bu, harici kaynaklardaki tüm komut dosyasında statik başlatıcıyı kullanarak, JavaScript tarafından görülemez.

Çekirdekleri Derinlemesine Azaltma

Azaltma, bir veri kümesini tek bir değerde birleştirme işlemidir. Bu, paralel programlamada takip etmek için:

  • tüm veriler üzerinden toplam veya çarpım
  • Tüm veriler üzerinde mantıksal işlemleri (and, or, xor) hesaplama
  • verilerdeki minimum veya maksimum değeri bulmak
  • belirli bir değeri veya verilerdeki belirli bir değerin koordinatını aramak

RenderScript, Android 7.0 (API düzeyi 24) ve sonraki sürümlerde daha verimli şekilde kullanabilirsiniz. 1, 2 veya 3 boyut içeren girişlerde azaltma çekirdekleri başlatabilirsiniz.

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

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

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

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

typedef struct {
  IndexedVal min, max;
} MinAndMax;

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

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

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

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

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

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

NOT: Daha fazla örnek azaltma çekirdeği burada verilmiştir.

Bir azaltma çekirdeği çalıştırmak için RenderScript çalışma zamanı bir veya daha fazla toplayıcı verisi adı verilen değişkenler öğeler indirimin durumunu korur. RenderScript çalışma zamanı Toplayıcı veri öğelerinin sayısını, performansı en üst düzeye çıkaracak şekilde seçer. Toplayıcı veri öğelerinin türü (toplType), çekirdeğin toplayıcı işlevi tarafından belirlenir. Bu işlevin ilk bağımsız değişkeni, bir toplayıcı veri öğesinin işaretçisidir. Varsayılan olarak her bir toplayıcı veri öğesi sıfır olarak başlatılır (memset tarafından başlatılmış gibi); ancak farklı bir işlem yapmak için bir başlatıcı işlevi yazabilirsiniz.

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

Örnek: findMinAndMax çekirdeği, toplayıcı veri öğeleri (MinAndMax türü) minimum ve maksimum değerleri izlemek için kullanılır bulundu. Bunları sırasıyla LONG_MAX ve LONG_MIN olarak ayarlayan ve bu değerlerin konumlarını -1 olarak ayarlayan bir başlatıcı işlevi vardır. Bu işlev, değerlerin işlenen girişin (boş) kısmında bulunmadığını gösterir.

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

Örnek: addint toplayıcı fonksiyonu, bir giriş Öğesinin değerini toplayıcıya ekler. veri öğesine dokunun.

Örnek: findMinAndMax çekirdeği, toplayıcı işlevi bir giriş Öğesinin değerinin minimum değerden küçük veya bu değere eşit olup olmadığını kontrol eder toplayıcı veri öğesinde kaydedilen değer ve/veya maksimum değerden büyük ya da bu değere eşit değer, toplayıcı veri öğesine kaydedilir ve toplayıcı veri öğesini günceller buna göre hazırlar.

Toplayıcı işlevi, girişlerdeki her koordinat için bir kez çağrıldıktan sonra, RenderScript, toplayıcı veri öğelerini tek bir toplayıcı veri öğesinde toplar. Bir reklam birleştirici oluşturabilirsiniz. işlevini kullanın. Toplayıcı işlevinde tek bir giriş varsa ve özel bağımsız değişkenler yoksa birleştirici yazmanız gerekmez fonksiyon; RenderScript, toplayıcı verilerini bir araya getirmek için toplayıcı işlevini kullanır. öğeler. (Varsayılan davranış sizin için aynı değilse birleştirici işlevi de gerekir.)

Örnek: addint yoksa birleştirici işlevi yoktur, bu nedenle toplayıcı işlevi kullanılır. Bu doğru bir davranıştır. Çünkü bir değer koleksiyonunu iki parçaya böler ve bu iki parçadaki değerleri ayrı ayrı toplarsak bu iki toplamı toplamak, koleksiyonun tamamını toplamakla aynıdır.

Örnek: findMinAndMax çekirdeğinde, birleştirici işlevi, "kaynak" toplayıcı veri öğesi *val'de kaydedilen minimum değerin "hedef" toplayıcı veri öğesi *accum'de kaydedilen minimum değerden düşük olup olmadığını kontrol eder ve *accum'i buna göre günceller. Maksimum değer için de benzer bir işlem yapar. Bu güncelleme *accum bu durumda, tüm giriş değerlerinin tamamı toplanmış olsaydı *accum ve bazılarının *accum içine girmek yerine *val.

Tüm toplayıcı veri öğeleri birleştirildikten sonra RenderScript, Java'ya döndürülecek azaltma sonucunu belirler. Bunu yapmak için bir outconverter işlevi yazabilirsiniz. İsterseniz dış dönüştürücü işlevi yazmanıza gerek azaltmanın sonucu olacak şekilde birleştirilmiş veri öğelerinin son değeri.

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

Örnek: findMinAndMax çekirdeği, dış dönüştürücü işlevi en düşük ve en düşük konumlarını tutmak için bir int2 sonuç değerini başlatır tüm toplayıcı veri öğelerinin kombinasyonundan elde edilen maksimum değerlere

Azaltma çekirdeği yazma

#pragma rs reduce, bir azaltma çekirdeğini şu şekilde tanımlar: işlevlerinin adlarını ve rollerini belirterek yükseltmeye çalışıyor. Bu tür tüm işlevler static olmalıdır. İndirme çekirdeği için her zaman accumulator gerekir fonksiyon; istediğinize bağlı olarak, diğer işlevlerin bazılarını veya tümünü çıkarabilirsiniz çekirdeğine sahip olduğunu varsayalım.

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

#pragma içindeki öğelerin anlamı aşağıdaki gibidir:

  • reduce(kernelName) (zorunlu): Kısaltma çekirdeğinin tanımlanmaktadır. Yansıtılan bir Java yöntemi reduce_kernelName çekirdeği başlatır.
  • initializer(initializerName) (isteğe bağlı): ilkleştirici işlevi hakkında daha fazla bilgi edinin. Ç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şlev için toplayıcı veri öğesine işaret eden bir işarettir. ilk kullanıma hazırla.

    Başlatıcı işlevi sağlamazsanız RenderScript her toplayıcıyı başlatır veri öğesini sıfıra ayarlayarak (memset gibi) ve bir başlatıcı varmış gibi davranarak işlevi şu şekilde görünür:

    static void initializerName(accumType *accum) {
      memset(accum, 0, sizeof(*accum));
    }
  • accumulator(accumulatorName). (zorunlu): Bu öğe için toplayıcı işlevinin adını belirtir indirme çekirdeğidir. Çekirdeği başlattığınızda RenderScript, Bu işlev, girişlerdeki her koordinat için bir kez olmak üzere, bir öğeyi bir şekilde toplayıcı veri öğesine bakar. İşlev şu şekilde tanımlanmalıdır:

    static void accumulatorName(accumType *accum,
                                in1Type in1, , inNType inN
                                [, specialArguments]) {}

    accum, bu işlev için toplayıcı veri öğesine işaret eden bir işarettir. değiştirebilirsiniz. in1 ile inN, çekirdek başlatmaya iletilen girişlere göre otomatik olarak doldurulan bir veya daha fazla bağımsız değişkendir (her giriş için bir bağımsız değişken). Toplayıcı işlevi, isteğe bağlı olarak özel bağımsız değişkenlerin herhangi birini alabilir.

    Birden çok girişe sahip bir çekirdek: dotProduct.

  • combiner(combinerName)

    (isteğe bağlı): Bu azaltma çekirdeği için birleştirici işlevin adını belirtir. RenderScript, toplayıcı işlevini çağırdıktan sonra her koordinat için bir kez çalıştırıldığında, bu fonksiyonu tüm toplayıcı veri öğelerini tek bir veri altında birleştirmek için biriken veri öğesidir. İşlev aşağıdaki gibi tanımlanmalıdır:

    static void combinerName(accumType *accum, const accumType *other) {  }

    accum, bu işlevin değiştirmesi gereken bir "hedef" toplayıcı veri öğesine işaretçidir. other, bu işlevin *accum ile "birleştirilmesi" için "kaynak" bir toplayıcı veri öğesinin işaretçisidir.

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

    Birleştirici işlevi sağlamazsanız RenderScript, yerde, aşağıdaki gibi bir birleştirici işlevi varmış gibi davranır:

    static void combinerName(accumType *accum, const accumType *other) {
      accumulatorName(accum, *other);
    }

    Giriş verilerinde birden fazla giriş varsa birleştirici işlevi zorunludur. type, toplayıcı veri türüyle aynı değilse veya toplayıcı fonksiyonu veya daha fazla özel bağımsız değişken kullanabilirsiniz.

  • outconverter(outconverterName) (isteğe bağlı): Bu azaltma çekirdeği için çıkış dönüştürücü işlevinin adını belirtir. RenderScript, toplam girdi parçasının iki veri öğesi kullanıyorsanız, ilgili değişkenin sonucunu belirlemek için kısaltması için de bunu yapabilirsiniz. İşlev aşağıdaki gibi tanımlanmalıdır: bu:

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

    result, bu işlevin azaltma sonucuyla başlatılması için bir sonuç veri öğesinin (RenderScript çalışma zamanında ayrılmış ancak başlatılmamış) işaretçisidir. resultType, bu veri öğesinin türüdür ve accumType ile aynı olması gerekmez. accum, birleştirici işlevi tarafından hesaplanan nihai toplayıcı veri öğesinin işaretçisidir.

    Bir dış dönüştürücü işlevi sağlamazsanız RenderScript nihai toplayıcıyı kopyalar veri öğesini sonuç veri öğesine bağlayacak şekilde, dönüşüm gerçekleştiren bir işlev varmış gibi davranarak şöyle görünür:

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

    Toplayıcı veri türünden farklı bir sonuç türü istiyorsanız dış dönüştürücü işlevi zorunludur.

Çekirdeğin giriş türleri, toplayıcı veri öğesi türü ve sonuç türü olduğunu unutmayın. Bunların hiçbirinin aynı olması gerekmez. Örneğin, findMinAndMax çekirdeğinin temel değerini long, toplayıcı veri öğesi türü MinAndMax ve sonuç int2 türlerinin hepsi farklı.

Neleri varsayamazsınız?

Belirli bir çekirdek başlatma işlemi için RenderScript tarafından oluşturulan toplayıcı veri öğelerinin sayısına güvenmemeniz gerekir. Aynı çekirdekteki iki lansmanın aynı girişler, aynı sayıda toplayıcı veri öğesi oluşturur.

RenderScript'in başlatıcı, toplayıcı ve birleştirici işlevlerini çağıracağı sıraya güvenmemelisiniz. Hatta bazılarını paralel olarak bile çağırabilir. Aynı girişe sahip aynı çekirdeğin iki kez başlatılmasının aynı sırayla gerçekleşeceği garanti edilmez. Tek yalnızca başlatıcı işlevinin başlatılmamış bir toplayıcıyı göreceğinin garantisi yoktur. veri öğesine dokunun. Örnek:

  • Toplayıcı veri öğelerinin toplayıcı işlevi çağrılır, ancak yalnızca başlatılmış bir toplayıcıda çağrılır veri öğesine dokunun.
  • Giriş öğelerinin toplayıcıya aktarılma sırasına dair herhangi bir garanti verilmez. işlevini kullanın.
  • Toplayıcı işlevinin, birleştirici işlevi çağrılmadan önce tüm giriş öğeleri için çağrıldığı garanti edilmez.

Bunun sonuçlarından biri, findMinAndMax çekirdek deterministik değil: Giriş, aynı çekirdeğin hangi gerçekleşmeye başladığını bulabilirsiniz.

Neyi garanti etmelisiniz?

Çünkü RenderScript sistemi bir çekirdeği pek çok çekirdeğinizin davrandığından emin olmak için belirli kurallara istediğiniz gibi değiştirebilirsiniz. Bu kurallara uymazsanız yanlış sonuçlar, belirlenemeyen davranışlar veya çalışma zamanında hatalar alabilirsiniz.

Aşağıdaki kurallar genellikle iki toplayıcı veri öğesinin " aynı değere sahip". Bu ne anlama geliyor? Bu, çekirdeğin ne yapmasını istediğinize bağlıdır. Örneğin, addint gibi matematiksel bir indirgeme; bu genellikle "aynı" için ilk adımıdır. findMinAndMax ("minimum ve maksimum giriş değerlerinin yerini bul") gibi "herhangi birini seç" aramalarında, aynı giriş değerlerinin birden fazla kez bulunabileceği durumlarda, belirli bir giriş değerinin tüm konumları "aynı" olarak kabul edilmelidir. Örneğin, "leftmost minimum ve maksimum giriş değerlerinin konumunu bul" komutuna benzer bir çekirdek Bu örnekte, 100 numaralı konumdaki minimum değer, ilgili konumda özdeş bir minimum değere tercih edilir. 200; bu çekirdek için "aynı" yalnızca özdeş konum anlamına gelir. aynı değer ve toplayıcı ile birleştirici fonksiyonlarının findMinAndMax için olanlardan farklıdır.

Başlatıcı işlevi bir kimlik değeri oluşturmalıdır. Yani, I ve A toplayıcı veri öğeleri başlatıldıysa ilkleştirici işlevi tarafından gerçekleştirilmiştir ve I, toplayıcı fonksiyonu (ancak A kullanılmış olabilir)
  • combinerName(&A, &I) A için aynı bırak
  • combinerName(&I, &A) I için A ile aynı şekilde bırak

Örnek: addint bir toplayıcı veri öğesi sıfıra başlatılır. Bu çekirdeğin birleştirici işlevi toplama işlemi gerçekleştirir; sıfır, toplama işleminin kimlik değeridir.

Örnek: findMinAndMax bir toplayıcı veri öğesi başlatıldı INITVAL konumuna kadar.

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

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

Birleştirici işlevi toplayıcı olmalıdır. Yani, A ve B toplayıcı veri öğeleri başlatıldıysa Başlatıcı işlevi tarafından oluşturulur ve bu, toplayıcı işlevi sıfıra iletilmiş olabilir veya daha çok kez teslim etmek için combinerName(&A, &B) A öğesini aynı değere ayarla bu combinerName(&B, &A) B öğesini ayarlar.

Ö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) ile aynı A = minmax(A, B) ve minmax değişkendir fMMCombiner da aynı.

Birleştirici işlev ilişkisel olmalıdır. Yani, A, B ve C ise başlatıcı işlevi tarafından başlatılan ve iletilmiş olabilecek toplayıcı veri öğeleri ekleme işlevine sıfır veya daha fazla kez eklemeniz gerekiyorsa aşağıdaki iki kod dizisi A öğesini aynı değere ayarlayın:

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

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

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

Toplama ilişkilendirmeyle ilişkilidir, dolayısıyla birleştirici işlevi de ilişkiseldir.

Örnek: findMinAndMax çekirdeğinde,

fMMCombiner(&A, &B)
şununla aynı:
A = minmax(A, B)
Bu iki dizi
  • A = minmax(A, B)
    A = minmax(A, C)
    // Same as
    //   A = minmax(minmax(A, B), C)
  • B = minmax(B, C)
    A = minmax(A, B)
    // Same as
    //   A = minmax(A, minmax(B, C))
    //   B = minmax(B, C)

minmax ilişkilidir ve bu nedenle fMMCombiner da ilişkilidir.

Toplayıcı işlevi ve birleştirici işlevi birlikte, temel katlama kuralına bakın. Yani A ve B toplayıcı veri öğeleriyse, A başlatıcı işlevi tarafından başlatıldıysa ve toplayıcı işlevine sıfır veya daha fazla kez iletilmişse, B başlatılmadıysa ve args, toplayıcı 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 Aaynı 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:

  • İfade 1, A += V ile aynı
  • 2. ifade, B = 0 ile aynı
  • İfade 3, B += V ile aynı olup bu ifade B = V ile aynıdır
  • İfade 4, A += B ile aynı olup bu ifade A += V ile aynıdır

İfade 1 ve 4, A öğesini aynı değere ayarladığından bu çekirdek temel katlama kuralı.

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

  • 1. ifade, A = minmax(A, IndexedVal(V, X)) ile aynı
  • 2. ifade, B = INITVAL ile aynı
  • İfade 3, aynı
    B = minmax(B, IndexedVal(V, X))
    Bu, B ilk değer olduğu için
    B = IndexedVal(V, X)
  • 4. ifade,
    A = minmax(A, B)
    aşağıdakiyle aynıdır:
    A = minmax(A, IndexedVal(V, X))

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

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

filename.rs dosyasında tanımlanan kernelName adlı bir azaltma çekirdeği için ScriptC_filename sınıfına üç yöntem yansıtılır:

KotlinJava
// 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
// 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:

KotlinJava
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()
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 bir giriş Allocation bağımsız değişkeni vardır çekirdeğin toplayıcısındaki her giriş bağımsız değişkeni işlevi hakkında daha fazla bilgi edinin. RenderScript çalışma zamanı, tüm giriş tahsislerinin aynı boyutlara sahip olduğundan ve giriş tahsislerinin her birinin Element türünün, toplayıcı işlevinin prototipinin ilgili giriş bağımsız değişkeniyle eşleştiğinden emin olmak için kontrol eder. Bu denetimlerden herhangi biri başarısız olursa RenderScript bir istisna oluşturur. İlgili içeriği oluşturmak için kullanılan bu boyutlardaki her koordinat üzerinde yürütülür.

2. Yöntem, 1. Yöntem ile aynıdır ancak 2. Yöntem sc bağımsız değişkeni, çekirdek yürütme işlemini koordinatlarıyla birlikte çalışır.

3. Yöntem, ayırma girdileri yerine Java dizisi girişleri alır. Bu, kullanıcılara sizi, açıkça bir Ayırma oluşturmak ve verileri buna kopyalamak için kod yazma zahmetinden kurtarır dizesinden oluşur. Ancak Yöntem 1 yerine 3. Yöntem kullanıldığında hakkında daha fazla bilgi edinin. 3. yöntem, her giriş dizisi için uygun Element türüne ve setAutoPadding(boolean) etkinleştirilmiş bir geçici 1 boyutlu Allocation oluşturur ve diziyi, Allocation'ın uygun copyFrom() yöntemiyle kopyalar gibi Allocation'a kopyalar. Daha sonra 1. Yöntemi çağırarak bu geçici Ayırmalar.

NOT: Uygulamanız veya aynı boyutlara ve Öğe türüne sahip farklı dizilerle gösterilebilir. oluşturmak yerine, ayırmaları kendiniz oluşturmanız, doldurmanız ve yeniden kullanmanız, 3. Yöntem'i kullanın.

Yansıtılan azaltma yöntemlerinin döndürdüğü tür olan javaFutureType, ScriptC_filename sınıfında yansıtılan statik bir iç içe yerleştirilmiş sınıftır. Bir azaltma çekirdeği çalıştırmasının gelecekteki sonucunu temsil eder. Çalıştırmanın asıl sonucunu almak için söz konusu sınıfın get() yöntemi; bu yöntem bir değer döndürür javaResultType türünde. get() eşzamanlı olmalıdır.

KotlinJava
class ScriptC_filename(rs: RenderScript) : ScriptC(…) {
    object javaFutureType {
        fun get(): javaResultType {}
    }
}
public class ScriptC_filename extends ScriptC {
  public static class javaFutureType {
    public javaResultType get() {}
  }
}

javaResultType, outconverter işlevinin resultType özelliğinden belirlenir. resultType, bir imzasız tür (skalar, vektör veya dizi), javaResultType doğrudan Java türü. resultType imzasız bir türse ve daha büyük bir Java imzalı tür varsa javaResultType bu daha büyük Java imzalı türdür. Aksi takdirde, doğrudan karşılık gelen Java türüdür. Örnek:

  • resultType, int, int2 veya int[15] ise javaResultType ise int, Int2 olur, veya int[]. Tüm resultType değerleri temsil edilebilir javaResultType tarafından oluşturulur.
  • resultType, uint, uint2 veya uint[15] ise bu durumda javaResultType değeri long, Long2, veya long[]. Tüm resultType değerleri temsil edilebilir javaResultType tarafından oluşturulur.
  • resultType ise ulong, ulong2 ise veya ulong[15], ardından javaResultType long, Long2 veya long[]. Bu özellikte belirli değerler javaResultType ile temsil edilemeyen resultType.

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

  • resultType bir dizi türü değilse javaFutureType result_resultType olur.
  • resultType, memberType türündeki üyelere sahip bir Count uzunluk dizisiyse: javaFutureType, resultArrayCount_memberType olur.

Örnek:

KotlinJava
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> =     }
}
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) her çağrı aynı örnekte javaFutureType.get() değerine ayarlanırsa aynı nesnesini tanımlayın.

javaResultType, resultType türündeki tüm değerleri ve bir indirgeme çekirdeği temsil edilemeyen bir değer üretir, sonra javaFutureType.get() bir istisna yapar.

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 Doğrudan aynının işaretli skaler türüne karşılık gelen Java türü seçin. inXType işaretli bir vektör türüyse devecSiInXType, Java'dır. doğrudan vektör bileşeni türüne karşılık gelen türe karşılık gelir. inXType işaretsiz bir vektör türüyse devecSiInXType, vektör bileşeni türüyle aynı boyutta işaretli skaler türe doğrudan karşılık gelen Java türüdür. Örnek:

  • inXType int ise devecSiInXType int olur.
  • inXType değeri int2 ise devecSiInXType int. Dizi, düzleştirilmiş bir temsildir: Dizi, iki kat daha fazla Ayırma 2 bileşenli vektöre sahip olduğundan birçok skaler Öğe Öğeler. Bu, Allocation için copyFrom() yöntemlerinin çalışma biçimiyle aynıdır.
  • inXType değeri uint ise deviceSiInXType değeri int. Java dizisindeki imzalı bir değer, tahsisimde aynı bit örüntüsüne sahip imzasız bir değer olarak yorumlanır. Bu, copyFrom() öğesinin çalışma yöntemidir.Allocation
  • inXType değeri uint2 ise deviceSiInXType değeri int. Bu, int2 ve uint yöntemlerinin kombinasyonudur. işlenir: Dizi, düzleştirilmiş bir gösterimdir ve Java dizisi imzalı değerleri değeri olarak yorumlanır.

3. Yöntem için giriş türlerinin farklı şekilde işlendiğini unutmayın. şu sonuç türlerinden birini kullanıyor:

  • Komut dosyasının vektör girişi Java tarafında düzleştirilir, ancak vektör sonucu düzleştirilmez.
  • Bir komut dosyasının imzasız girişi, Java tarafında aynı boyutta imzalı giriş olarak temsil edilir. Bir komut dosyasının imzasız sonucu ise Java tarafında genişletilmiş imzalı tür olarak temsil edilir (ulong hariç).

Daha fazla azaltma çekirdekleri

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

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

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

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

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

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

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

#define BUCKETS 256
typedef uint32_t Histogram[BUCKETS];

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

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

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

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

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

Diğer kod örnekleri

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