RenderScript'e Genel Bakış

RenderScript, işlem yükü açısından yoğun görevleri Android'de yüksek performansta çalıştırmak için geliştirilmiş bir çerçevedir. RenderScript esasen veri paralel işlemede kullanılmak üzere tasarlanmıştır, ancak seri iş yükleri de faydalı olabilir. RenderScript çalışma zamanı, bir cihazda kullanılabilen çok çekirdekli CPU'lar ve GPU'lar gibi işlemcilerde çalışmayı paralel hale getirir. Böylece işleri programlamak yerine algoritmaları ifade etmeye odaklanabilirsiniz. RenderScript özellikle görüntü işleme, hesaplamalı fotoğrafçılık veya bilgisayar görüşü uygulamaları için kullanışlıdır.

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

  • language, yüksek performanslı işlem kodu yazmak için C99'dan türetilmiş bir dildir. RenderScript Kernel'i yazma bölümünde, bu dizenin işlem çekirdeklerini yazmak için nasıl kullanılacağı açıklanmıştır.
  • control API, RenderScript kaynaklarının ömrünü yönetmek ve çekirdek yürütmeyi kontrol etmek için kullanılır. Üç farklı dilde kullanılabilir: Java, Android NDK'da C++ ve C99'dan türetilen çekirdek dilinin kendisi. Java Code'dan RenderScript'i kullanma ve Single-Source RenderScript'te sırasıyla birinci ve üçüncü seçenekler açıklanmıştı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ı adı verilir. Her komut dosyası kendi çekirdek, işlev ve değişken kümesini içerir. Bir komut dosyası şunları içerebilir:

  • Bu komut dosyasında kullanılan RenderScript çekirdek dilinin sürümünü açıklayan 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ı açıklayan bir pragma bildirimi (#pragma rs java_package_name(com.example.app)). .rs dosyanızın bir kitaplık projesinde değil, uygulama paketinizin bir parçası olması gerektiğini unutmayın.
  • Sıfır veya daha fazla çağrılabilir işlev. Çağırılabilir işlev, rastgele bağımsız değişkenlerle Java kodunuzdan çağırabileceğiniz tek iş parçacıklı bir RenderScript işlevidir. Bunlar genellikle ilk kurulum veya daha büyük bir işleme ardışık düzeni içindeki seri hesaplamalar için yararlıdır.
  • Sıfır veya daha fazla komut dosyası global. Genel komut dosyası, C'deki genel değişkene benzer. Komut dosyası genellerine Java kodundan erişebilirsiniz. Bunlar genellikle RenderScript çekirdeklerine parametre aktarımı için kullanılır. Komut dosyası genelleri, burada daha ayrıntılı olarak açıklanmıştır.

  • Sıfır veya daha fazla işlem çekirdeği. İşlem çekirdeği, RenderScript çalışma zamanının bir veri koleksiyonunda paralel olarak yürütülmesini yönlendirebileceğiniz bir işlev veya işlev koleksiyonudur. İki tür işlem çekirdeği vardır: çekirdekleri eşleme (forher çekirdekleri olarak da adlandırılır) ve azaltma.

    Eşleme çekirdeği, aynı boyutlara sahip 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. Normalde (ancak özel olarak değil) bir giriş koleksiyonunu her defasında bir Element olacak şekilde bir Allocations girişi Allocation çıkışına 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, çoğu açıdan standart C işleviyle aynıdır. İşlev prototipine uygulanan RS_KERNEL özelliği, işlevin çağrılabilir bir işlev yerine bir RenderScript eşleme çekirdeği olduğunu belirtir. in bağımsız değişkeni, çekirdek başlatma işlemine 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, Allocation çıkışındaki uygun konuma otomatik olarak yazılır. Varsayılan olarak bu çekirdek, Allocation içindeki Element başına çekirdek işlevinin bir kez yürütülmesiyle Allocation girişinin tamamında çalıştırılır.

      Bir eşleme çekirdeğinde bir veya daha fazla giriş (Allocations), tek bir çıkış (Allocation) veya her ikisi birden bulunabilir. RenderScript çalışma zamanı, tüm giriş ve çıkış Allocations'larının aynı boyutlara sahip olup olmadığını ve Element giriş ve çıkış türlerinin, çekirdeğin prototipi ile eşleşip eşleşmediğini kontrol eder. Bu denetimlerden biri başarısız olursa RenderScript bir istisna uygular.

      NOT: Android 6.0'dan (API düzeyi 23) önceki bir eşleme çekirdeğinde birden fazla giriş Allocation bulunamaz.

      Çekirdektekinden daha fazla Allocations girişine veya çıkışına ihtiyacınız varsa bu nesneler rs_allocation komut dosyası genellerine bağlı olmalı ve rsGetElementAt_type() ya da rsSetElementAt_type() aracılığıyla çekirdekten veya çağrılabilir bir işlevden erişilmelidir.

      NOT: RS_KERNEL, size kolaylık sağlamak amacıyla RenderScript tarafından otomatik olarak tanımlanan bir makrodur:

      #define RS_KERNEL __attribute__((kernel))
      

    Azaltma çekirdeği, aynı boyutlardaki Allocations girdi koleksiyonu üzerinde çalışan bir işlev ailesidir. Varsayılan olarak, toplayıcı işlevi bu boyutlardaki her koordinat için bir kez yürütülür. Genellikle (ancak özel olarak değil) bir Allocations girdi koleksiyonunu tek bir değere "azaltmak" için kullanılır.

    • Burada, girdisinin Elements değerini toplayan basit bir azaltma çekirdeği örneğini görebilirsiniz:

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

      Kısaltma çekirdeği, kullanıcı tarafından yazılan bir veya daha fazla işlevden oluşur. #pragma rs reduce, çekirdek adını (bu örnekte addint) ve çekirdeği oluşturan işlevlerin adlarını ve rollerini (bu örnekte accumulator işlevi addintAccum) belirterek tanımlamak için kullanılır. 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.

      Bir azaltma çekirdek toplayıcı işlevi void değerini döndürmeli ve en az iki bağımsız değişkene sahip olmalıdır. Birinci bağımsız değişken (bu örnekte accum) bir toplayıcı veri öğesine işaret eder. İkinci bağımsız değişken (bu örnekte val), çekirdek başlatma işlemine iletilen Allocation girdisine göre otomatik olarak doldurulur. Toplayıcı veri öğesi, RenderScript çalışma zamanı tarafından oluşturulur; varsayılan olarak sıfıra başlatılır. Varsayılan olarak bu çekirdek, Allocation içindeki Element başına toplayıcı işlevinin bir kez yürütülmesiyle Allocation girişinin tamamında çalıştırılır. Varsayılan olarak, toplayıcı veri öğesinin son değeri azaltma işleminin sonucu olarak değerlendirilir ve Java'ya döndürülür. RenderScript çalışma zamanı, giriş Ayırma'nın Element türünün, toplayıcı işlevinin prototipi ile eşleşip eşleşmediğini kontrol eder. Eşleşmezse RenderScript bir istisna uygular.

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

      Azaltma çekirdekleri, burada daha ayrıntılı olarak açıklanmıştır.

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

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

    Bir eşleme çekirdeği işlevi veya bir azaltma çekirdeği toplayıcı işlevi, rs_kernel_context türündeki isteğe bağlı özel context bağımsız değişkenini de alabilir. Bu, mevcut yürütmenin belirli özelliklerini sorgulamak için kullanılan bir çalışma zamanı API'leri ailesi tarafından gereklidir (örneğin, rsGetDimX). (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 örneklendiğinde RenderScript'in çalıştırdığı özel bir çağrılabilir işlev türüdür. Bu, komut dosyası oluşturulurken bazı hesaplamaların otomatik olarak yapılmasına olanak tanır.
  • Sıfır veya daha fazla statik komut dosyası genelleri ve işlevleri. Statik bir global komut dosyası, Java kodundan erişilememesi dışında global komut dosyasına eşdeğerdir. Statik işlev, komut dosyasındaki herhangi bir çekirdekten veya çağrılabilir işlevden çağrılabilen ancak Java API'sine sunulmayan standart bir C işlevidir. Global komut dosyasına veya işleve Java kodundan erişilmesi gerekmiyorsa static olarak belirtilmesi önemle tavsiye edilir.

Kayan nokta hassasiyetini ayarlama

Bir komut dosyasında gereken kayan nokta hassasiyeti 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çindir.
  • #pragma rs_fp_relaxed: Katı IEEE 754-2008 uyumluluğu gerektirmeyen ve daha düşük hassasiyete tolerans gösterebilen uygulamalar içindir. Bu mod, denormlar için sıfırlamayı sıfıra ve sıfıra doğru yuvarlamayı etkinleştirir.
  • #pragma rs_fp_imprecise: Katı hassasiyet gereksinimleri olmayan uygulamalar içindir. Bu mod, aşağıdakilerin yanı sıra rs_fp_relaxed içindeki her şeyi etkinleştirir:
    • -0,0 ile sonuçlanan işlemler +0,0 döndürebilir.
    • INF ve NAN işlemleri tanımlanmamış.

Çoğu uygulamada rs_fp_relaxed, herhangi bir yan etki olmadan kullanılabilir. Bu, yalnızca esnek hassasiyetle kullanılabilen ek optimizasyonlar (SIMD CPU talimatları gibi) nedeniyle bazı mimarilerde çok faydalı olabilir.

Java'dan RenderScript API'lerine erişme

RenderScript kullanan bir Android uygulaması geliştirirken uygulamanın API'sine şu iki yöntemden biriyle Java'dan erişebilirsiniz:

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

Bu işlemin artıları ve eksileri:

  • Support Library API'lerini kullanırsanız uygulamanızın RenderScript kısmı, kullandığınız RenderScript özelliklerinden bağımsız olarak Android 2.3 (API düzeyi 9) ve sonraki sürümleri çalıştıran cihazlarla uyumlu olur. Bu, uygulamanızın yerel (android.renderscript) API'lerine kıyasla daha fazla cihazda çalışmasına olanak tanır.
  • Belirli RenderScript özellikleri Destek Kitaplığı API'leri aracılığıyla kullanılamaz.
  • Destek Kitaplığı API'lerini kullanırsanız yerel (android.renderscript) API'lere kıyasla (muhtemelen önemli ölçüde) daha büyük APK'lar elde edersiniz.

RenderScript Destek Kitaplığı API'lerini kullanma

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

  • Android SDK Tools 22.2 veya sonraki sürümü
  • Android SDK Derleme Araçları düzeltmesi 18.1.0 veya üzeri

Android SDK Derleme Araçları 24.0.0 sürümünden itibaren Android 2.2 (API düzeyi 8) artık desteklenmemektedir.

Bu araçların yüklü sürümlerini Android SDK Yöneticisi'nden kontrol edip güncelleyebilirsiniz.

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

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

      Modern

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

      Kotlin

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

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

      • renderscriptTargetApi - Oluşturulacak bayt kodu sürümünü belirtir. Bu değeri, kullandığınız tüm işlevleri sağlayabilecek en düşük API seviyesine ve renderscriptSupportModeEnabled değerini true olarak ayarlamanızı öneririz. Bu ayar için geçerli değerler, 11 ile en son kullanıma sunulan API düzeyi arasındaki tüm tam sayı değerleridir. Uygulama manifestinizde belirtilen minimum SDK sürümünüz farklı bir değere ayarlanırsa bu değer yok sayılır ve derleme dosyasındaki hedef değer, minimum SDK sürümünü belirlemek için kullanılır.
      • renderscriptSupportModeEnabled - Çalıştığı cihaz hedef sürümü desteklemiyorsa, oluşturulan bayt kodunun uyumlu bir sürüme yedeklenmesi gerektiğini belirtir.
  3. RenderScript kullanan uygulama sınıflarınıza Destek Kitaplığı sınıfları için bir içe aktarma ekleyin:

    Kotlin

    import android.support.v8.renderscript.*
    

    Java

    import android.support.v8.renderscript.*;
    

Java veya Kotlin Kodu'ndan RenderScript'i kullanma

Java veya Kotlin kodundan RenderScript'in kullanılması, android.renderscript veya android.support.v8.renderscript paketinde bulunan API sınıflarına bağlıdır. Çoğu uygulama aynı temel kullanım kalıbını izler:

  1. Bir 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 ömrünü kontrol etmek için bir nesne sağlar. Bağlam oluşturmayı, farklı donanım parçalarında kaynak oluşturabileceğinden potansiyel olarak uzun süreli bir işlem olarak değerlendirmelisiniz. Mümkünse bir uygulamanın kritik yolunda yer almamalıdır. Genellikle bir uygulamada aynı anda yalnızca bir RenderScript bağlamı bulunur.
  2. Komut dosyasına geçirilecek 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 nesneleri alır. Komut dosyası genel olarak bağlandığında, rsGetElementAt_type() ve rsSetElementAt_type() kullanılarak çekirdeklerdeki Allocation nesnelerine erişilebilir. Allocation nesneleri, dizilerin Java kodundan RenderScript koduna ve RenderScript koduna aktarılmasına izin verir. Allocation nesneleri genellikle createTyped() veya createFromBitmap() kullanılarak oluşturulur.
  3. Gerekli komut dosyalarını oluşturun. RenderScript'i kullanırken iki tür komut dosyası kullanabilirsiniz:
    • ScriptC: Bunlar, yukarıdaki RenderScript Kernelleri Yazma bölümünde açıklanan kullanıcı tanımlı komut dosyalarıdır. Her komut dosyasının, Java kodundan komut dosyasına erişimi kolaylaştırmak için RenderScript derleyicisi tarafından yansıtılan bir Java sınıfı vardır. Bu sınıfın adı ScriptC_filename'dir. Örneğin, yukarıdaki eşleme çekirdeği invert.rs konumunda bulunuyorsa ve bir RenderScript bağlamı zaten mRenderScript konumunda bulunuyorsa komut dosyasını somutlaştırmak için Java veya Kotlin kodu şöyle olur:

      Kotlin

      val invert = ScriptC_invert(renderScript)
      

      Java

      ScriptC_invert invert = new ScriptC_invert(renderScript);
      
    • ScriptIntrinsic: Bunlar, Gauss bulanıklığı, evrişim ve görüntü harmanlama 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. Ayırmaları verilerle doldurun. createFromBitmap() ile oluşturulan Ayırmalar haricinde, Ayırma ilk oluşturulduğunda boş verilerle doldurulur. Bir Ayırma'yı doldurmak için Allocation sayfasındaki "kopyalama" yöntemlerinden birini kullanın. "Kopyalama" yöntemleri eşzamanlıdır.
  5. Gerekli tüm komut dosyası genel değerlerini belirleyin. Genel değerleri, set_globalname adlı aynı ScriptC_filename sınıfında yöntemler kullanarak ayarlayabilirsiniz. Örneğin, threshold adlı bir int değişkeni ayarlamak için set_threshold(int) Java yöntemini, lookup adlı bir rs_allocation değişkeni ayarlamak için de 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. Çekirdeğin bağımsız değişkenlerine bağlı olarak, yöntem her biri aynı boyutlara sahip olması gereken bir veya daha fazla Ayırma alır. Varsayılan olarak, çekirdek bu boyutlardaki her koordinat üzerinde yürütülür. Bu koordinatların bir alt kümesi üzerinde bir çekirdeği yürütmek için forEach veya reduce yönteminin son bağımsız değişkeni olarak uygun bir Script.LaunchOptions iletin.

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

  7. Allocation nesne ve javaFutureType nesnelerinden veri alın. Bir Allocation öğesindeki verilere Java kodundan erişmek için bu verileri Allocation içindeki "kopya" yöntemlerinden birini kullanarak Java'ya geri kopyalamanız gerekir. İndirme çekirdeğinin sonucunu elde etmek için javaFutureType.get() yöntemini kullanmanız gerekir. "Kopya" ve get() yöntemleri eşzamanlıdır.
  8. RenderScript bağlamını kaldırın. RenderScript bağlamını destroy() ile veya RenderScript içerik nesnesinin atık halde toplanmasına izin vererek kaldırabilirsiniz. Bu durum, söz konusu bağlama ait herhangi bir nesnenin ileride istisna uygulanması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 yöntem, istenen işlemi tamamlamadan önce Java'ya dönebilir. Ancak, her bir işlem başlatılma sırasına göre serileştirilir.

Allocation sınıfı, Ayırmalar'a ve Tahsislerden veri kopyalamak için "kopyalama" yöntemleri sunar. "Kopyalama" yöntemi eşzamanlıdır ve aynı Ayırma'ya dokunan yukarıdaki eşzamansız işlemlere göre serileştirilir.

Yansıtılan javaFutureType sınıfları, indirgeme sonucunu almak için bir get() yöntemi sağlar. get() eşzamanlıdır ve azaltmaya (eşzamansız olan) göre serileştirilir.

Tek Kaynaklı RenderScript

Android 7.0 (API düzeyi 24), Single-Source RenderScript adlı yeni bir programlama özelliğini kullanıma sunar. Bu programda çekirdekler, Java yerine tanımlandıkları komut dosyasından başlatılır. Bu yaklaşım şu anda, kısa ve öz olması için bu bölümde "çekirdekler" olarak adlandırılan çekirdekleri eşlemeyle sınırlıdır. Bu yeni özellik, komut dosyasının içinden rs_allocation türünde ayırmalar oluşturmayı da destekler. Birden fazla çekirdek başlatma işlemi gerekse bile, artık bir algoritmanın tamamı yalnızca bir komut dosyası içinde uygulamak mümkündür. Avantajı, bir algoritmanın tek bir dilde uygulanmasını sağladığı için daha okunabilir bir kod ve birden fazla çekirdek başlatmada Java ile RenderScript arasında daha az geçiş yapılması nedeniyle potansiyel olarak daha hızlı koddur.

Tek Kaynaklı RenderScript'te, çekirdekleri RenderScript Kernel'i Yazma konusunda açıklandığı gibi yazarsınız. Daha sonra, bunları başlatmak için rsForEach() çağrısı yapan bir çağrılabilir işlev yazarsınız. Bu API, ilk parametre olarak bir çekirdek işlevini ve ardından giriş ve çıkış ayırmalarını alır. Benzer bir API rsForEachWithOptions() için rs_script_call_t türünde ek bir bağımsız değişken kullanılır. Bu bağımsız değişken, çekirdek işlevinin işlemesi için giriş ve çıkış ayırmalarındaki öğelerin bir alt kümesini belirtir.

RenderScript hesaplamasını başlatmak için Java'dan çağrılabilir işlevi çağırırsınız. RenderScript'i Java Code'dan 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.

Ayırmalar genellikle bir çekirdek lansmanından diğerine ara sonuçları kaydetmek ve geçirmek için gereklidir. 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ünü, W ise öğenin vektör genişliğini belirtir. 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 boyutu atlanabilir. Örneğin rsCreateAllocation_uchar4(16384), her biri uchar4 türünde olan 16384 öğe için 1D ayırma oluşturur.

Ayırmalar sistem tarafından otomatik olarak yönetilir. Bunları açıkça yayınlamanız veya serbest bırakmanız gerekmez. Ancak temel ayırma için alloc tanıtıcısına artık ihtiyacınız olmadığını belirtmek üzere rsClearObject(rs_allocation* alloc) yöntemini çağırabilirsiniz. Böylece sistemin kaynakları mümkün olduğunca erken serbest bırakmasını sağlayabilirsiniz.

RenderScript Kernelleri Yazma bölümü, bir görüntüyü ters çeviren örnek bir çekirdek içerir. Aşağıdaki örnekte, Tek Kaynaklı RenderScript kullanılarak bir resme birden fazla efekt uygulanacak şekilde genişletilir. Renkli bir görüntüyü siyah beyaz yapan greyscale adlı başka bir çekirdeği içerir. Daha sonra çağrılabilir bir işlev (process()), bu iki çekirdeği art arda bir giriş görüntüsüne uygular ve bir çıktı görüntüsü üretir. Hem giriş hem de çıkış için ayırmalar, rs_allocation türündeki bağımsız değişkenler olarak aktarılır.

// File: singlesource.rs

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

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

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

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

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

process() işlevini Java veya Kotlin'den 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 lansmanı içeren bir algoritmanın, RenderScript dilinin kendisinde nasıl tamamen uygulanabileceği gösterilmektedir. Tek Kaynaklı RenderScript olmadan, her iki çekirdeği de Java kodundan başlatmanız gerekir. Bu sayede, çekirdek başlatma işlemleri çekirdek tanımlarından ayrılır ve algoritmanın tamamının anlaşılması güçleşir. Tek Kaynaklı RenderScript kodunun daha kolay okunmasını sağlamakla kalmaz, aynı zamanda çekirdek lansmanlarında Java ile komut dosyası arasındaki geçişi de ortadan kaldırır. Bazı yinelemeli algoritmalar, çekirdekleri yüzlerce kez başlatabilir. Bu da bu tür geçişin ek yükünü önemli ölçüde artırır.

Komut Dosyası Globalleri

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

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

  • var komut dosyasında statik bir başlatıcı varsa bu, hem Java hem de komut dosyasında var ilk değerini belirtir. Aksi takdirde, bu başlangıç değeri sıfır olur.
  • Komut dosyası içindeki var işlevine erişim, komut dosyası değerini okuma ve yazma
  • get_var yöntemi, Java değerini okur.
  • set_var yöntemi (varsa) Java değerini hemen yazar ve komut dosyası değerini eşzamansız olarak yazar.

NOT: Bu, komut dosyasındaki herhangi bir statik başlatıcı haricinde, bir komut dosyasının içinden global değere yazılan değerlerin Java tarafından görülememesi anlamına gelir.

Derinlikteki Çekirdek Azaltma

Azaltma, bir veri koleksiyonunu tek bir değer altında birleştirme işlemidir. Bu, paralel programlamada aşağıdakiler gibi uygulamalarda kullanışlı bir temel öğedir:

  • tüm veriler üzerinden toplamı veya çarpımı hesaplayarak
  • mantıksal işlemleri (and, or, xor) işleme
  • verilerdeki minimum veya maksimum değeri bulma
  • belirli bir değeri arama veya veriler içindeki belirli bir değerin koordinatını arama

RenderScript, Android 7.0 (API düzeyi 24) ve sonraki sürümlerde kullanıcı tarafından yazılan azaltma algoritmalarının verimli şekilde kullanılmasını sağlamak için azaltma çekirdeklerini destekler. 1, 2 veya 3 boyutlu girişlerde çekirdekleri azaltmayı başlatabilirsiniz.

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

#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: Kısaltma çekirdekleriyle ilgili daha fazla örneği burada bulabilirsiniz.

RenderScript çalışma zamanı, azaltma çekirdeğini çalıştırmak için azaltma işleminin durumunu korumak amacıyla toplayıcı veri öğeleri adı verilen bir veya daha fazla değişken oluşturur. RenderScript çalışma zamanı, performansı en üst düzeye çıkaracak şekilde toplayıcı veri öğelerinin sayısını seçer. Toplayıcı veri öğelerinin (accumType) türü, çekirdeğin toplayıcı işlevi tarafından belirlenir. Bu işlevin ilk bağımsız değişkeni, bir toplayıcı veri öğesine işaret eder. Varsayılan olarak, her toplayıcı veri öğesi sıfır olarak başlatılır (ör. memset tarafından). Ancak farklı bir şey yapmak için bir başlatma işlevi yazabilirsiniz.

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

Örnek: findMinAndMax çekirdeğinde, şimdiye kadar bulunan minimum ve maksimum değerleri izlemek için toplayıcı veri öğeleri (MinAndMax türünde) kullanılır. Bunları sırasıyla LONG_MAX ve LONG_MIN olarak ayarlamak ve bu değerlerin konumlarını -1 olarak ayarlamak için bir başlatıcı işlevi vardır. Böylece değerler, işlenen girişin (boş) bölümünde bulunmuyor.

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

Örnek: addint çekirdeğinde toplayıcı işlevi, bir giriş Öğesinin değerini toplayıcı veri öğesine ekler.

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

Toplayıcı işlevi, girişlerdeki her koordinat için bir kez çağrıldıktan sonra, RenderScript'in toplayıcı veri öğelerini tek bir toplayıcı veri öğesi halinde birleştirmesi gerekir. Bunu yapmak için bir birleştirici işlevi yazabilirsiniz. Toplayıcı işlevinde tek bir giriş varsa ve özel bağımsız değişkenler yoksa bir birleştirici işlevi yazmanız gerekmez. RenderScript, toplayıcı veri öğelerini birleştirmek için toplayıcı işlevini kullanır. (Bu varsayılan davranış istediğiniz gibi değilse yine de bir birleştirici işlevi yazabilirsiniz.)

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

Örnek: findMinAndMax çekirdeğinde birleştirici işlevi, *val "kaynak" toplayıcı veri öğesinde kaydedilen minimum değerin, "hedef" toplayıcı veri öğesinde *accum kaydedilen minimum değerden düşük olup olmadığını kontrol eder ve buna göre *accum değerini günceller. Maksimum değer için benzer bir işlem yapar. Bu işlem, *accum değerini bazı giriş değerleri *accum ve bazıları *val altında toplanmak yerine tüm giriş değerlerinde toplanmaları durumunda olacak şekilde günceller.*accum

Toplayıcı veri öğelerinin tümü birleştirildikten sonra, RenderScript, Java'ya dönmek için yapılacak azalmanın sonucunu belirler. Bunu yapmak için bir dış dönüştürücü işlevi yazabilirsiniz. Birleştirilmiş toplayıcı veri öğelerinin nihai değerinin azalmanın sonucu olmasını istiyorsanız dış dönüştürücü işlevi yazmanız gerekmez.

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

Örnek: findMinAndMax çekirdeğinde dış dönüştürücü işlevi, tüm toplayıcı veri öğelerinin kombinasyonundan kaynaklanan minimum ve maksimum değerlerin konumlarını tutmak için bir int2 sonuç değerini başlatır.

Kısaltma çekirdeği yazma

#pragma rs reduce, azaltma çekirdeğini, adını ve çekirdeği oluşturan işlevlerin adlarını ve rollerini belirterek tanımlar. 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 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 anlamları aşağıdaki gibidir:

  • reduce(kernelName) (zorunlu): Bir azaltma çekirdeğinin tanımlanmakta olduğunu belirtir. Yansıtılan bir Java yöntemi (reduce_kernelName) çekirdeği başlatır.
  • initializer(initializerName) (isteğe bağlı): Bu azaltma çekirdeği için ilkleştirici işlevinin adını belirtir. Çekirdeği başlattığınızda, RenderScript her bir toplayıcı veri öğesi için bu işlevi bir kez çağırır. İşlev şu şekilde tanımlanmalıdır:

    static void initializerName(accumType *accum) { … }

    accum, bu işlevin başlatılması için toplayıcı veri öğesinin işaretçisidir.

    Bir başlatıcı işlevi sağlamazsanız RenderScript, her toplayıcı veri öğesini sıfır olarak (memset ile olduğu gibi) başlatır ve aşağıdaki gibi bir başlatıcı işlevi varmış gibi davranır:

    static void initializerName(accumType *accum) {
      memset(accum, 0, sizeof(*accum));
    }
  • accumulator(accumulatorName) (zorunlu): Bu azaltma çekirdeği için toplayıcı işlevinin adını belirtir. Çekirdeği başlattığınızda RenderScript, girişlerdeki her koordinat için bu işlevi bir kez çağırarak bir toplayıcı veri öğesini girişlere göre bir şekilde günceller. İşlev şu şekilde tanımlanmalıdır:

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

    accum, bu işlevin değiştirmesi için toplayıcı veri öğesine işaret eder. in1-inN arası, ç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 verilir. Toplayıcı işlevi, isteğe bağlı olarak özel bağımsız değişkenlerden herhangi birini alabilir.

    Çoklu girişe sahip çekirdek örneği: dotProduct.

  • combiner(combinerName)

    (isteğe bağlı): Bu azaltma çekirdeği için birleştirici işlevinin adını belirtir. RenderScript, girişlerdeki her koordinat için toplayıcı işlevini bir kez çağırdıktan sonra, tüm toplayıcı veri öğelerini tek bir toplayıcı veri öğesinde birleştirmek için bu işlevi gerektiği kadar çağırır. İşlev şu şekilde tanımlanmalıdır:

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

    accum, bu işlevin değiştirebileceği "hedef" toplayıcı veri öğesine işaret eder. other, bu işlevin *accum ile "birleştirilmesi" için bir "kaynak" toplayıcı veri öğesine işaret eder.

    NOT: *accum, *other veya her ikisi de başlatılmış ancak toplayıcı işlevine hiç geçirilmemiş olabilir. Diğer bir deyişle, biri veya her ikisi de herhangi bir giriş verisine göre hiç güncellenmemiş olabilir. Örneğin, findMinAndMax çekirdeğinde fMMCombiner birleştirici işlevi, değeri INITVAL olan böyle bir toplayıcı veri öğesini belirttiğinden idx < 0 açıkça kontrol eder.

    Bir birleştirici işlevi sağlamazsanız RenderScript onun yerine toplama işlevini kullanır ve aşağıdaki gibi görünen bir birleştirici işlevi varmış gibi davranır:

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

    Çekirdekte birden fazla giriş varsa, giriş verisi türü toplayıcı veri türüyle aynı değilse veya toplayıcı işlevi bir ya da daha fazla özel bağımsız değişken alırsa birleştirici işlevi zorunludur.

  • outconverter(outconverterName) (isteğe bağlı): Bu azaltma çekirdeği için çıkış dönüştürücü işlevinin adını belirtir. RenderScript, toplayıcı veri öğelerinin tümünü birleştirdikten sonra, Java'ya dönmek üzere indirgemenin 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 (atanmış ancak RenderScript çalışma zamanı tarafından başlatılmamış) işaret eden bir işarettir. resultType, ilgili veri öğesinin türüdür ve accumType ile aynı olması gerekmez. accum, birleştirici işlevi tarafından hesaplanan son toplayıcı veri öğesine işaret eder.

    Bir dış dönüştürücü işlevi sağlamazsanız RenderScript, aşağıdaki gibi görünen bir dış dönüştürücü işlevi varmış gibi davranarak son toplayıcı veri öğesini sonuç veri öğesine kopyalar:

    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 öğe türü ve sonuç türü olduğunu ve hiçbirinin aynı olması gerekmediğini unutmayın. Örneğin, findMinAndMax çekirdeğinde long giriş türü, toplayıcı veri öğesi türü MinAndMax ve int2 sonuç türünün tümü farklıdır.

Neleri varsayamazsınız?

Belirli bir çekirdek lansmanı için RenderScript tarafından oluşturulan toplayıcı verisi öğelerinin sayısına güvenmemelisiniz. Aynı girişlerle aynı çekirdeğin iki lansmanının aynı sayıda toplayıcı veri öğesi oluşturacağı garanti edilmez.

RenderScript'in başlatıcı, toplayıcı ve birleştirici işlevlerini çağırdığı sıraya güvenmemelisiniz. Bu işlevlerden bazılarını paralel olarak çağırabilir. Aynı girişe sahip olan aynı çekirdeğin iki lansmanının aynı sırayı takip edeceği garanti edilmez. Tek garanti, başlatılmamış bir toplayıcı veri öğesini yalnızca başlatıcı işlevinin görebileceğidir. Örneğin:

  • Tüm toplayıcı verisi öğelerinin, toplayıcı işlevi çağrılmadan önce başlatılacağına dair bir garanti yoktur. Ancak bu işlev, yalnızca başlatılmış bir toplayıcı veri öğesinde çağrılır.
  • Giriş öğelerinin toplayıcı işlevine aktarılma sırası konusunda herhangi bir garanti yoktur.
  • Birleştirici işlevi çağrılmadan önce toplayıcı işlevinin tüm giriş Öğeleri için çağrılacağına dair bir garanti yoktur.

Bunun bir sonucu da findMinAndMax çekirdeğinin belirleyici olmamasıdır: Giriş, aynı minimum veya maksimum değerin birden fazla tekrarını içeriyorsa çekirdeğin hangi tekrarı bulacağını bilemezsiniz.

Neyi garanti etmeniz gerekir?

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

Aşağıdaki kurallarda genellikle iki toplayıcı veri öğesinin "aynı değere" sahip olması gerektiği belirtilmektedir. Bu ne anlama geliyor? Bu, çekirdeğin ne yapmasını istediğinize bağlıdır. addint gibi matematiksel bir indirgeme için "aynı"nın matematiksel eşitlik anlamına gelmesi genellikle mantıklıdır. Aynı giriş değerlerinin birden fazla kez göründüğü findMinAndMax ("minimum ve maksimum giriş değerlerinin konumunu bul") gibi bir "herhangi birini seç" araması için belirli bir giriş değerinin tüm konumları "aynı" olarak kabul edilmelidir. "en soldaki minimum ve maksimum giriş değerlerinin konumunu bul" şeklinde benzer bir çekirdek yazabilirsiniz. Burada, (örneğin) 200 konumundaki özdeş bir minimum değer yerine 100 konumundaki minimum değer tercih edilir; bu çekirdek için "aynı" değeri, yalnızca aynı value değil, özdeş konum anlamına gelir ve toplayıcı ile birleştirici işlevleri MinAndAnd için farklı olmalıdır.

Başlatıcı işlevi bir kimlik değeri oluşturmalıdır. Yani I ve A, başlatıcı işlevi tarafından başlatılan toplayıcı veri öğeleriyse ve I toplayıcı işlevine hiç geçirilmediyse (ancak A geçmiş olabilir) bu durumda
  • combinerName(&A, &I), A alanını aynı bırakmalıdır
  • combinerName(&I, &A), I alanını A ile aynı bırakmalıdır

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

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

  • I INITVAL olduğundan fMMCombiner(&A, &I), A değerini aynı bırakır.
  • I değeri INITVAL olduğundan fMMCombiner(&I, &A), I değerini A olarak ayarlıyor.

Dolayısıyla, INITVAL gerçekten de bir kimlik değeridir.

Birleştirici işlevi değişimli olmalıdır. Yani A ve B, başlatıcı işlevi tarafından başlatılan toplayıcı veri öğeleriyse ve bu veriler toplayıcı işlevine sıfır veya daha fazla kez geçirildiyse combinerName(&A, &B), A değerini combinerName(&B, &A) ile B olarak ayarlanan aynı değere ayarlamalıdır.

Örnek: addint çekirdeğinde birleştirici işlevi, iki toplayıcı verisi öğe değerini ekler; toplama ise değişmelidir.

Örnek: findMinAndMax çekirdeğinde fMMCombiner(&A, &B), A = minmax(A, B) ile aynıdır. minmax değişimli olduğu için fMMCombiner da değişkendir.

Birleştirici işlevi ilişkisel olmalıdır. Yani A, B ve C, başlatıcı işlevi tarafından başlatılan toplayıcı veri öğeleriyse ve bu öğeler toplayıcı işlevine sıfır ya da daha fazla kez geçirilmiş olabilir. Bu durumda aşağıdaki iki kod dizisi, A değerini aynı değere ayarlamalıdır:

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

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

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

Toplama işlemi ilişkilendirmedir. Dolayısıyla, birleştirici işlevi de ilişkiseldir.

Örnek: findMinAndMax çekirdeğinde

fMMCombiner(&A, &B)
,
A = minmax(A, B)
ile aynıdır. Dolayısıyla, iki dizi

  • A = minmax(A, B)
    A = minmax(A, C)
    // Same as
    //   A = minmax(minmax(A, B), C)
    
  • B = minmax(B, C)
    A = minmax(A, B)
    // Same as
    //   A = minmax(A, minmax(B, C))
    //   B = minmax(B, C)
    

minmax ilişkilendirildiği için fMMCombiner de ilişkiseldir.

Toplayıcı işlevi ve birleştirici işlevi birlikte temel katlama kuralına uymalıdır. Yani A ve B toplayıcı veri öğeleriyse A, başlatıcı işlevi tarafından başlatıldıysa ve toplayıcı işlevine sıfır veya daha fazla kez geçmiş olabilir, B başlatılmamış ve bağımsız değişkenler toplayıcı işlevine yapılan belirli bir çağrının giriş bağımsız değişkenleri ile özel bağımsız değişkenlerinin listesidir. Bu durumda aşağıdaki iki kod dizisinde A değeri şu şekilde ayarlanmalıdır: :

  • accumulatorName(&A, args);  // statement 1
    
  • initializerName(&B);        // statement 2
    accumulatorName(&B, args);  // statement 3
    combinerName(&A, &B);       // statement 4
    

Örnek: addint çekirdeğinde V giriş değeri için:

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

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

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

  • İfade 1, A = minmax(A, IndexedVal(V, X)) ile aynı
  • İfade 2, B = INITVAL ile aynı
  • İfade 3,
    B = minmax(B, IndexedVal(V, X))
    
    ile aynıdır çünkü B başlangıç değeri olduğundan
    B = IndexedVal(V, X)
    
    ile aynıdır.
  • İfade 4,
    A = minmax(A, IndexedVal(V, X))
    
    ile aynı olan
    A = minmax(A, B)
    
    ile aynıdır.

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

Java kodundan 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 üç yöntem yansıtılır:

Kotlin

// Function 1
fun reduce_kernelName(ain1: Allocation, …,
                               ainN: Allocation): javaFutureType

// Function 2
fun reduce_kernelName(ain1: Allocation, …,
                               ainN: Allocation,
                               sc: Script.LaunchOptions): javaFutureType

// Function 3
fun reduce_kernelName(in1: Array<devecSiIn1Type>, …,
                               inN: Array<devecSiInNType>): javaFutureType

Java

// Method 1
public javaFutureType reduce_kernelName(Allocation ain1, …,
                                        Allocation ainN);

// Method 2
public javaFutureType reduce_kernelName(Allocation ain1, …,
                                        Allocation ainN,
                                        Script.LaunchOptions sc);

// Method 3
public javaFutureType reduce_kernelName(devecSiIn1Type[] in1, …,
                                        devecSiInNType[] inN);

Aşağıda, addint çekirdeğinin çağrılmasıyla ilgili bazı örnekler verilmiştir:

Kotlin

val script = ScriptC_example(renderScript)

// 1D array
//   and obtain answer immediately
val input1 = intArrayOf()
val sum1: Int = script.reduce_addint(input1).get()  // Method 3

// 2D allocation
//   and do some additional work before obtaining answer
val typeBuilder = Type.Builder(RS, Element.I32(RS)).apply {
    setX()
    setY()
}
val input2: Allocation = Allocation.createTyped(RS, typeBuilder.create()).also {
    populateSomehow(it) // fill in input Allocation with data
}
val result2: ScriptC_example.result_int = script.reduce_addint(input2)  // Method 1
doSomeAdditionalWork() // might run at same time as reduction
val sum2: Int = result2.get()

Java

ScriptC_example script = new ScriptC_example(renderScript);

// 1D array
//   and obtain answer immediately
int input1[] = ;
int sum1 = script.reduce_addint(input1).get();  // Method 3

// 2D allocation
//   and do some additional work before obtaining answer
Type.Builder typeBuilder =
  new Type.Builder(RS, Element.I32(RS));
typeBuilder.setX();
typeBuilder.setY();
Allocation input2 = createTyped(RS, typeBuilder.create());
populateSomehow(input2);  // fill in input Allocation with data
ScriptC_example.result_int result2 = script.reduce_addint(input2);  // Method 1
doSomeAdditionalWork(); // might run at same time as reduction
int sum2 = result2.get();

1. yöntemde, çekirdeğin toplayıcı işlevindeki her giriş bağımsız değişkeni için bir giriş Allocation bağımsız değişkeni bulunur. RenderScript çalışma zamanı, tüm giriş Allocations'larının aynı boyutlara sahip olduğundan ve her bir giriş Allocations'ın Element türünün, toplayıcı işlevine ait prototipin ilgili giriş bağımsız değişkeniyle eşleştiğinden emin olur. Bu kontrollerden herhangi biri başarısız olursa RenderScript bir istisna atar. Çekirdek, bu boyutlardaki her koordinat üzerinde yürütülür.

2. Yöntem, Yöntem 1'inkiyle aynıdır. Tek fark, Yöntem 2'nin, çekirdek çalıştırmasını koordinatların bir alt kümesiyle sınırlamak için kullanılabilecek ek bir sc bağımsız değişkeni almasıdır.

3. Yöntem, Yöntem 1 ile aynıdır ancak Ayırma girişleri yerine Java dizisi girişleri alır. Bu özellik, sizi açıkça bir Allocation oluşturmak ve bir Java dizisinden buna veri kopyalamak için kod yazma zahmetinden kurtarır. Bununla birlikte, Yöntem 1 yerine Yöntem 3'ün kullanılması kodun performansını artırmaz. Yöntem 3, her bir giriş dizisi için uygun Element türü ve setAutoPadding(boolean) etkinken geçici bir 1 boyutlu Ayırma oluşturur ve diziyi, Allocation özelliğinin uygun copyFrom() yöntemini kullanıyormuş gibi Ayırma'ya kopyalar. Daha sonra bu geçici Ayırmaları ileterek 1. Yöntem'i çağırır.

NOT: Uygulamanız, aynı diziyle veya aynı boyutlar ve Element türünde farklı dizilerle birden fazla çekirdek çağrısı yapacaksa Ayırmaları 3. Yöntem'i kullanmak yerine açık şekilde oluşturarak, 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 yerleştirilmiş sınıftır. Çekirdek çalıştırmasının azaltılmasının gelecekteki sonucunu temsil eder. Çalıştırmanın gerçek sonucunu elde etmek için ilgili sınıfın get() yöntemini çağırın. Bu yöntem, javaResultType türünde bir değer döndürür. get() eşzamanlı.

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 parametresinden belirlenir. resultType imzasız bir tür (skalar, vektör veya dizi) olmadığı sürece javaResultType doğrudan karşılık gelen Java türüdür. resultType imzalanmamış bir türse ve daha büyük bir Java imzalı türü varsa javaResultType daha büyük Java imzalı türdür; aksi takdirde, doğrudan karşılık gelen Java türü olur. Örneğin:

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

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

  • resultType bir dizi türü değilse javaFutureType result_resultType olur.
  • resultType, memberType türünde üyelerle birlikte Count uzunluğunda bir diziyse javaFutureType değeri 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 edemez ve bir azaltma çekirdeği temsil edilemez bir değer oluşturursa javaFutureType.get() bir istisna bildirir.

3. Yöntem ve devecSiInXType

devecSiInXType, toplayıcı işlevinin ilgili bağımsız değişkeninin inXType'ına karşılık gelen Java türüdür. inXType imzasız bir tür veya vektör türü değilse doğrudan karşılık gelen Java türü devecSiInXType'tır. inXType imzasız skaler bir türse devecSiInXType, aynı boyuttaki imzalı skaler türe doğrudan karşılık gelen Java türüdür. inXType imzalı bir vektör türüyse devecSiInXType, vektör bileşeni türüne doğrudan karşılık gelen Java türüdür. inXType imzasız bir vektör türüyse devecSiInXType, vektör bileşeni türüyle aynı boyuttaki işaretli skaler türe doğrudan karşılık gelen Java türüdür. Örneğin:

  • inXType için int değeri belirlenirse devecSiInXType için int değeri kullanılır.
  • inXType, int2 ise devecSiInXType ise int olur. Dizi, düzleştirilmiş bir gösterimdir: Ayırma'nın 2 bileşenli vektör Öğelerine göre iki kat daha fazla skalar Öğe içerir. Bu, copyFrom() Allocation yöntemlerinin çalışma biçimiyle aynıdır.
  • inXType uint ise deviceSiInXType int olur. Java dizisindeki imzalı değer, Ayırma'daki aynı bit kalıbının imzalanmamış bir değeri olarak yorumlanır. Bu, copyFrom() Allocation yöntemlerinin çalışma biçimiyle aynıdır.
  • inXType uint2 ise deviceSiInXType int olur. Bu, int2 ve uint öğelerinin bir birleşimidir: Dizi, düzleştirilmiş bir gösterimdir ve Java dizisi imzalı değerleri, RenderScript imzalanmamış Element değerleri olarak yorumlanır.

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

  • Bir komut dosyasının vektör girdisi Java tarafında düz hale gelirken, komut dosyasının vektör sonucu düzleşmez.
  • Bir komut dosyasının imzasız girişi, Java tarafında aynı boyutta imzalı bir giriş olarak gösterilirken, komut dosyasının imzalanmamış sonucu Java tarafında genişletilmiş işaretli bir tür olarak temsil edilir (ulong durumu hariç).

Kısaltma çekirdeklerine dair diğer örnekler

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

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

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

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

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

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

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

#define BUCKETS 256
typedef uint32_t Histogram[BUCKETS];

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

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

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

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

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

Ek kod örnekleri

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