RenderScript की खास जानकारी

RenderScript, Android पर बेहतर परफ़ॉर्मेंस के साथ, ज़्यादा कंप्यूटिंग वाले टास्क चलाने के लिए एक फ़्रेमवर्क है. RenderScript मुख्य रूप से डेटा-पаралल कैलकुलेशन के साथ इस्तेमाल करने के लिए है. हालांकि, सीरियल वर्कलोड को भी इससे फ़ायदा मिल सकता है. RenderScript रनटाइम, डिवाइस पर उपलब्ध प्रोसेसर पर एक साथ काम करता है. जैसे, मल्टी-कोर सीपीयू और जीपीयू. इससे आपको काम को शेड्यूल करने के बजाय, एल्गोरिदम को बेहतर बनाने पर फ़ोकस करने में मदद मिलती है. RenderScript, इमेज प्रोसेसिंग, कंप्यूटेशनल फ़ोटोग्राफ़ी या कंप्यूटर विज़न की सुविधा देने वाले ऐप्लिकेशन के लिए खास तौर पर मददगार है.

RenderScript का इस्तेमाल शुरू करने के लिए, आपको दो मुख्य कॉन्सेप्ट समझने होंगे:

  • भाषा, C99 से ली गई एक भाषा है. इसका इस्तेमाल, बेहतर परफ़ॉर्मेंस वाला कंप्यूट कोड लिखने के लिए किया जाता है. RenderScript के लिए कर्नल लिखना लेख में, कंप्यूट कर्नल लिखने के लिए, RenderScript का इस्तेमाल करने का तरीका बताया गया है.
  • कंट्रोल एपीआई का इस्तेमाल, RenderScript संसाधनों के लाइफ़टाइम को मैनेज करने और कोर को कंट्रोल करने के लिए किया जाता है. यह तीन अलग-अलग भाषाओं में उपलब्ध है: Java, Android NDK में C++, और C99 से ली गई कर्नेल भाषा. Java कोड से RenderScript का इस्तेमाल करना और सिंगल-सोर्स RenderScript, पहले और तीसरे विकल्प के बारे में बताते हैं.

RenderScript कर्नेल लिखना

आम तौर पर, RenderScript के kernel, <project_root>/src/rs डायरेक्ट्री में मौजूद .rs फ़ाइल में मौजूद होते हैं. हर .rs फ़ाइल को स्क्रिप्ट कहा जाता है. हर स्क्रिप्ट में, कर्नेल, फ़ंक्शन, और वैरिएबल का अपना सेट होता है. स्क्रिप्ट में ये चीज़ें शामिल हो सकती हैं:

  • एक प्रैगमा एलान (#pragma version(1)), जो इस स्क्रिप्ट में इस्तेमाल की गई RenderScript kernel language के वर्शन के बारे में बताता है. फ़िलहाल, सिर्फ़ 1 वैल्यू मान्य है.
  • एक प्रैगमा एलान (#pragma rs java_package_name(com.example.app)), जो इस स्क्रिप्ट से दिखने वाली Java क्लास के पैकेज का नाम बताता है. ध्यान दें कि आपकी .rs फ़ाइल, आपके ऐप्लिकेशन पैकेज का हिस्सा होनी चाहिए, न कि किसी लाइब्रेरी प्रोजेक्ट का.
  • शून्य या उससे ज़्यादा इंवोक किए जा सकने वाले फ़ंक्शन. कॉल किया जा सकने वाला फ़ंक्शन, एक थ्रेड वाला RenderScript फ़ंक्शन होता है. इसे अपने Java कोड से, मनमुताबिक आर्ग्युमेंट के साथ कॉल किया जा सकता है. ये अक्सर बड़ी प्रोसेसिंग पाइपलाइन में, शुरुआती सेटअप या सीरियल कैलकुलेशन के लिए काम के होते हैं.
  • शून्य या उससे ज़्यादा स्क्रिप्ट ग्लोबल. स्क्रिप्ट ग्लोबल, C में ग्लोबल वैरिएबल जैसा ही होता है. Java कोड से स्क्रिप्ट ग्लोबल को ऐक्सेस किया जा सकता है. आम तौर पर, इनका इस्तेमाल RenderScript केर्नेल में पैरामीटर पास करने के लिए किया जाता है. स्क्रिप्ट ग्लोबल के बारे में ज़्यादा जानकारी यहां दी गई है.

  • शून्य या उससे ज़्यादा कंप्यूट कर्नेल. कंप्यूट कर्नेल, एक फ़ंक्शन या फ़ंक्शन का कलेक्शन होता है. इसकी मदद से, RenderScript रनटाइम को डेटा के कलेक्शन में एक साथ कई काम करने के लिए निर्देश दिया जा सकता है. कंप्यूट कर्नल दो तरह के होते हैं: मैपिंग कर्नेल (इन्हें foreach कर्नेल भी कहा जाता है) और रिडक्शन कर्नेल.

    मैपिंग कर्नेल एक पैरलल फ़ंक्शन है, जो एक ही डाइमेंशन के Allocations के कलेक्शन पर काम करता है. डिफ़ॉल्ट रूप से, यह उन डाइमेंशन में हर निर्देशांक के लिए एक बार लागू होता है. आम तौर पर, इसका इस्तेमाल एक बार में एक Element के तौर पर, इनपुट Allocations के कलेक्शन को आउटपुट Allocation में बदलने के लिए किया जाता है. हालांकि, इसका इस्तेमाल सिर्फ़ इस काम के लिए नहीं किया जाता.

    • यहां एक आसान मैपिंग केर्नेल का उदाहरण दिया गया है:

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

      ज़्यादातर मामलों में, यह स्टैंडर्ड C फ़ंक्शन के जैसा ही होता है. फ़ंक्शन प्रोटोटाइप पर लागू की गई RS_KERNEL प्रॉपर्टी से पता चलता है कि फ़ंक्शन, इस्तेमाल किए जा सकने वाले फ़ंक्शन के बजाय, RenderScript मैपिंग कर्नेल है. in आर्ग्युमेंट, kernel launch को पास किए गए इनपुट Allocation के आधार पर अपने-आप भर जाता है. x और y आर्ग्युमेंट के बारे में यहां बताया गया है. कर्नेल से मिली वैल्यू, आउटपुट Allocation में सही जगह पर अपने-आप लिख जाती है. डिफ़ॉल्ट रूप से, यह केरल पूरे इनपुट पर चलाया जाता है Allocation. साथ ही, Allocation में हर Element के लिए, केरल फ़ंक्शन को एक बार चलाया जाता है.

      मैपिंग केर्नेल में एक या उससे ज़्यादा इनपुट Allocations, एक आउटपुट Allocation या दोनों हो सकते हैं. RenderScript रनटाइम यह पक्का करने के लिए जांच करता है कि सभी इनपुट और आउटपुट ऐलोकेशन के डाइमेंशन एक जैसे हों. साथ ही, यह भी पक्का करता है कि इनपुट और आउटपुट ऐलोकेशन के Element टाइप, कर्नेल के प्रोटोटाइप से मेल खाते हों. अगर इनमें से कोई भी जांच पूरी नहीं होती है, तो RenderScript एक अपवाद दिखाता है.

      ध्यान दें: Android 6.0 (एपीआई लेवल 23) से पहले, मैपिंग कर्नेल में एक से ज़्यादा इनपुट Allocation नहीं हो सकते.

      अगर आपको कोर में मौजूद Allocations से ज़्यादा इनपुट या आउटपुट Allocations चाहिए, तो उन ऑब्जेक्ट को rs_allocation स्क्रिप्ट ग्लोबल के साथ बाउंड किया जाना चाहिए. साथ ही, rsGetElementAt_type() या rsSetElementAt_type() के ज़रिए, कोर या किसी ऐसे फ़ंक्शन से ऐक्सेस किया जाना चाहिए जिसे कॉल किया जा सकता है.

      ध्यान दें: RS_KERNEL एक मैक्रो है, जो आपकी सुविधा के लिए, RenderScript के ज़रिए अपने-आप तय होता है:

      #define RS_KERNEL __attribute__((kernel))

    रिडक्शन कर्नेल, फ़ंक्शन का एक फ़ैमिली होता है. यह एक ही डाइमेंशन के इनपुट Allocations के कलेक्शन पर काम करता है. डिफ़ॉल्ट रूप से, उन डाइमेंशन में मौजूद हर निर्देशांक के लिए, इसका accumulator फ़ंक्शन एक बार काम करता है. आम तौर पर, इसका इस्तेमाल इनपुट Allocations के कलेक्शन को एक वैल्यू में "कम करने" के लिए किया जाता है. हालांकि, इसका इस्तेमाल सिर्फ़ इस काम के लिए नहीं किया जाता.

    • यहां एक आसान कम करने वाले केर्नल का उदाहरण दिया गया है, जो अपने इनपुट के Elements को जोड़ता है:

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

      रिडक्शन कर्नेल में, उपयोगकर्ता के लिखे गए एक या उससे ज़्यादा फ़ंक्शन होते हैं. #pragma rs reduce का इस्तेमाल, कोर के नाम (इस उदाहरण में addint) और कोर बनाने वाले फ़ंक्शन के नाम और भूमिकाओं (इस उदाहरण में accumulator फ़ंक्शन addintAccum) की जानकारी देकर, कोर को तय करने के लिए किया जाता है. ऐसे सभी फ़ंक्शन static होने चाहिए. रिडक्शन कर्नेल के लिए, accumulator फ़ंक्शन का होना ज़रूरी है. इसमें अन्य फ़ंक्शन भी हो सकते हैं. यह इस बात पर निर्भर करता है कि आपको कर्नेल से क्या करना है.

      रिडक्शन कर्नेल इक्यूमुलेटर फ़ंक्शन, void दिखाना चाहिए. साथ ही, इसमें कम से कम दो आर्ग्युमेंट होने चाहिए. पहला आर्ग्युमेंट (इस उदाहरण में accum), accumulator डेटा आइटम का पॉइंटर होता है. दूसरा आर्ग्युमेंट (इस उदाहरण में val), kernel लॉन्च के लिए पास किए गए इनपुट Allocation के आधार पर अपने-आप भर जाता है. Accumulator डेटा आइटम, RenderScript रनटाइम से बनाया जाता है. डिफ़ॉल्ट रूप से, इसे शून्य पर शुरू किया जाता है. डिफ़ॉल्ट रूप से, यह कर्नेल अपने पूरे इनपुटAllocation पर चलता है. साथ ही, Allocation में हर Element के लिए एक इकट्ठा करने वाले फ़ंक्शन को एक बार चलाया जाता है. डिफ़ॉल्ट रूप से, अक्यूमुलेटर डेटा आइटम की फ़ाइनल वैल्यू को घटाने के नतीजे के तौर पर माना जाता है और इसे Java में दिखाया जाता है. RenderScript रनटाइम यह पक्का करता है कि इनपुट ऐलोकेशन का Element टाइप, Accumulator फ़ंक्शन के प्रोटोटाइप से मैच करता हो. अगर यह मैच नहीं होता है, तो RenderScript एक अपवाद दिखाता है.

      रिडक्शन कर्नेल में एक या उससे ज़्यादा इनपुट Allocations होते हैं, लेकिन कोई आउटपुट Allocations नहीं होता.

      रिडक्शन कर्नेल के बारे में ज़्यादा जानकारी यहां दी गई है.

      रिडक्शन कर्नेल, Android 7.0 (एपीआई लेवल 24) और उसके बाद के वर्शन पर काम करते हैं.

    मैपिंग केर्नेल फ़ंक्शन या रिडक्शन केर्नेल इक्यूमुलेटर फ़ंक्शन, खास आर्ग्युमेंट x, y, और z का इस्तेमाल करके, मौजूदा एक्सीक्यूशन के निर्देशांक ऐक्सेस कर सकता है. ये आर्ग्युमेंट int या uint32_t टाइप के होने चाहिए. ये आर्ग्युमेंट ज़रूरी नहीं हैं.

    मैपिंग केर्नेल फ़ंक्शन या रिडक्शन केर्नेल इक्यूमुलेटर फ़ंक्शन में, rs_kernel_context टाइप का वैकल्पिक खास आर्ग्युमेंट context भी लिया जा सकता है. इसकी ज़रूरत रनटाइम एपीआई की फ़ैमिली को होती है. इनका इस्तेमाल, मौजूदा एक्सीक्यूशन की कुछ प्रॉपर्टी के बारे में क्वेरी करने के लिए किया जाता है. उदाहरण के लिए, rsGetDimX. (context आर्ग्युमेंट, Android 6.0 (एपीआई लेवल 23) और उसके बाद के वर्शन में उपलब्ध है.)

  • init() फ़ंक्शन का इस्तेमाल करना ज़रूरी नहीं है. init() फ़ंक्शन, एक खास तरह का ऐसा फ़ंक्शन है जिसे इस्तेमाल किया जा सकता है. यह फ़ंक्शन, स्क्रिप्ट को पहली बार इंस्टैंशिएट करने पर RenderScript को चलाता है. इससे स्क्रिप्ट बनाते समय, कुछ गणना अपने-आप हो जाती है.
  • शून्य या उससे ज़्यादा स्टैटिक स्क्रिप्ट ग्लोबल और फ़ंक्शन. स्टैटिक स्क्रिप्ट ग्लोबल, स्क्रिप्ट ग्लोबल के बराबर होती है. हालांकि, इसे Java कोड से ऐक्सेस नहीं किया जा सकता. स्टैटिक फ़ंक्शन एक स्टैंडर्ड C फ़ंक्शन होता है. इसे स्क्रिप्ट में मौजूद किसी भी कर्नेल या कॉल किए जा सकने वाले फ़ंक्शन से कॉल किया जा सकता है. हालांकि, इसे Java API के लिए उपलब्ध नहीं कराया जाता. अगर किसी स्क्रिप्ट ग्लोबल या फ़ंक्शन को Java कोड से ऐक्सेस करने की ज़रूरत नहीं है, तो हमारा सुझाव है कि उसे static के तौर पर घोषित करें.

फ़्लोटिंग पॉइंट की सटीक वैल्यू सेट करना

किसी स्क्रिप्ट में, फ़्लोटिंग पॉइंट ऐक्युरसी के लिए ज़रूरी लेवल को कंट्रोल किया जा सकता है. यह तब काम आता है, जब डिफ़ॉल्ट रूप से इस्तेमाल किए जाने वाले पूरे IEEE 754-2008 स्टैंडर्ड की ज़रूरत न हो. यहां दिए गए प्रैगमा, फ़्लोटिंग पॉइंट की सटीक जानकारी का अलग लेवल सेट कर सकते हैं:

  • #pragma rs_fp_full (अगर कोई वैल्यू नहीं दी गई है, तो डिफ़ॉल्ट रूप से लागू होता है): उन ऐप्लिकेशन के लिए जिन्हें IEEE 754-2008 स्टैंडर्ड के मुताबिक, फ़्लोटिंग पॉइंट की सटीक वैल्यू की ज़रूरत होती है.
  • #pragma rs_fp_relaxed: उन ऐप्लिकेशन के लिए जिन्हें IEEE 754-2008 के सख्त दिशा-निर्देशों का पालन करने की ज़रूरत नहीं है और जिन्हें सटीक नतीजे देने की ज़रूरत नहीं है. यह मोड, डीनॉर्म और राउंड-टू-ज़ीरो के लिए, शून्य पर फ़्लश करने की सुविधा चालू करता है.
  • #pragma rs_fp_imprecise: उन ऐप्लिकेशन के लिए जिनमें सटीक जानकारी इकट्ठा करने से जुड़ी ज़रूरी शर्तें नहीं हैं. इस मोड में, rs_fp_relaxed में मौजूद सभी सुविधाओं के साथ-साथ ये सुविधाएं भी चालू हो जाती हैं:
    • -0.0 के नतीजे वाले ऑपरेशन के बजाय, +0.0 दिख सकता है.
    • INF और NAN पर ऑपरेशन की जानकारी नहीं दी गई है.

ज़्यादातर ऐप्लिकेशन, rs_fp_relaxed का इस्तेमाल बिना किसी साइड इफ़ेक्ट के कर सकते हैं. यह कुछ आर्किटेक्चर के लिए काफ़ी फ़ायदेमंद हो सकता है. इसकी वजह यह है कि ज़्यादा सटीक जानकारी के साथ ही अतिरिक्त ऑप्टिमाइज़ेशन उपलब्ध होते हैं. जैसे, SIMD सीपीयू निर्देश.

Java से RenderScript API ऐक्सेस करना

RenderScript का इस्तेमाल करने वाले Android ऐप्लिकेशन को डेवलप करते समय, Java से इसके एपीआई को ऐक्सेस करने के लिए, इनमें से कोई एक तरीका अपनाएं:

  • android.renderscript - इस क्लास पैकेज में मौजूद एपीआई, Android 3.0 (एपीआई लेवल 11) और इसके बाद के वर्शन वाले डिवाइसों पर उपलब्ध हैं.
  • android.support.v8.renderscript - इस पैकेज में मौजूद एपीआई, सहायता लाइब्रेरी के ज़रिए उपलब्ध हैं. इसकी मदद से, Android 2.3 (एपीआई लेवल 9) और उसके बाद के वर्शन वाले डिवाइसों पर इनका इस्तेमाल किया जा सकता है.

यहां कुछ फ़ायदे और नुकसान दिए गए हैं:

  • अगर Support Library API का इस्तेमाल किया जाता है, तो आपके ऐप्लिकेशन का RenderScript हिस्सा, Android 2.3 (एपीआई लेवल 9) और उसके बाद के वर्शन वाले डिवाइसों पर काम करेगा. भले ही, आपने RenderScript की कौनसी सुविधाओं का इस्तेमाल किया हो. इससे आपका ऐप्लिकेशन, नेटिव (android.renderscript) एपीआई का इस्तेमाल करने की तुलना में ज़्यादा डिवाइसों पर काम कर सकता है.
  • RenderScript की कुछ सुविधाएं, Support Library API के ज़रिए उपलब्ध नहीं हैं.
  • अगर नेटिव (android.renderscript) एपीआई का इस्तेमाल करने के बजाय, सहायता लाइब्रेरी एपीआई का इस्तेमाल किया जाता है, तो आपको APKs का साइज़ ज़्यादा (शायद काफ़ी ज़्यादा) मिलेगा.

RenderScript सपोर्ट लाइब्रेरी के एपीआई का इस्तेमाल करना

Support Library RenderScript API का इस्तेमाल करने के लिए, आपको अपने डेवलपमेंट एनवायरमेंट को कॉन्फ़िगर करना होगा, ताकि आप उन्हें ऐक्सेस कर सकें. इन एपीआई का इस्तेमाल करने के लिए, यहां दिए गए Android SDK टूल ज़रूरी हैं:

  • Android SDK टूल का 22.2 या इसके बाद का वर्शन
  • Android SDK Build-tools का 18.1.0 या इसके बाद का वर्शन

ध्यान दें कि Android SDK Build-tools 24.0.0 से, Android 2.2 (एपीआई लेवल 8) पर काम नहीं करता.

इन टूल के इंस्टॉल किए गए वर्शन की जांच करने और उन्हें अपडेट करने के लिए, Android SDK मैनेजर में जाएं.

Support Library के RenderScript API का इस्तेमाल करने के लिए:

  1. पक्का करें कि आपने Android SDK का ज़रूरी वर्शन इंस्टॉल किया हो.
  2. RenderScript सेटिंग शामिल करने के लिए, Android बिल्ड प्रोसेस की सेटिंग अपडेट करें:
    • अपने ऐप्लिकेशन मॉड्यूल के ऐप्लिकेशन फ़ोल्डर में build.gradle फ़ाइल खोलें.
    • फ़ाइल में RenderScript की ये सेटिंग जोड़ें:

      Groovy

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

      Kotlin

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

      ऊपर दी गई सेटिंग, Android बिल्ड प्रोसेस में खास व्यवहार को कंट्रोल करती हैं:

      • renderscriptTargetApi - जनरेट किए जाने वाले बाइटकोड वर्शन के बारे में बताता है. हमारा सुझाव है कि आप इस वैल्यू को एपीआई के सबसे निचले लेवल पर सेट करें, जो आपके इस्तेमाल किए जा रहे सभी फ़ंक्शन उपलब्ध करा सके. साथ ही, renderscriptSupportModeEnabled को true पर सेट करें. इस सेटिंग के लिए, 11 से लेकर हाल ही में रिलीज़ किए गए एपीआई लेवल तक की कोई भी पूर्णांक वैल्यू मान्य है. अगर आपके ऐप्लिकेशन मेनिफ़ेस्ट में बताए गए SDK टूल के कम से कम वर्शन को किसी दूसरी वैल्यू पर सेट किया गया है, तो उस वैल्यू को अनदेखा कर दिया जाएगा. साथ ही, SDK टूल के कम से कम वर्शन को सेट करने के लिए, बिल्ड फ़ाइल में मौजूद टारगेट वैल्यू का इस्तेमाल किया जाएगा.
      • renderscriptSupportModeEnabled - इससे यह तय होता है कि जनरेट किए गए बाइटकोड को, काम करने वाले किसी ऐसे वर्शन पर स्विच कर दिया जाए जिस पर टारगेट वर्शन काम न करता हो.
  3. RenderScript का इस्तेमाल करने वाली अपनी ऐप्लिकेशन क्लास में, सहायता लाइब्रेरी की क्लास के लिए इंपोर्ट जोड़ें:

    Kotlin

    import android.support.v8.renderscript.*

    Java

    import android.support.v8.renderscript.*;

Java या Kotlin कोड से RenderScript का इस्तेमाल करना

Java या Kotlin कोड से RenderScript का इस्तेमाल करने के लिए, android.renderscript या android.support.v8.renderscript पैकेज में मौजूद एपीआई क्लास का इस्तेमाल किया जाता है. ज़्यादातर ऐप्लिकेशन, इस्तेमाल के एक ही बुनियादी पैटर्न का पालन करते हैं:

  1. RenderScript कॉन्टेक्स्ट को शुरू करना. create(Context) की मदद से बनाया गया RenderScript कॉन्टेक्स्ट, यह पक्का करता है कि RenderScript का इस्तेमाल किया जा सकता है. साथ ही, यह बाद में इस्तेमाल होने वाले सभी RenderScript ऑब्जेक्ट के लाइफ़टाइम को कंट्रोल करने के लिए एक ऑब्जेक्ट उपलब्ध कराता है. आपको कॉन्टेक्स्ट बनाने की प्रोसेस को लंबे समय तक चलने वाली प्रोसेस मानना चाहिए, क्योंकि यह अलग-अलग हार्डवेयर पर रिसॉर्स बना सकती है. अगर हो सके, तो इसे ऐप्लिकेशन के क्रिटिकल पाथ में शामिल नहीं किया जाना चाहिए. आम तौर पर, किसी ऐप्लिकेशन में एक बार में सिर्फ़ एक RenderScript कॉन्टेक्स्ट होता है.
  2. स्क्रिप्ट में पास करने के लिए, कम से कम एक Allocation बनाएं. Allocation एक RenderScript ऑब्जेक्ट है, जो तय डेटा के लिए स्टोरेज उपलब्ध कराता है. स्क्रिप्ट में मौजूद कर्नेल, Allocation ऑब्जेक्ट को इनपुट और आउटपुट के तौर पर इस्तेमाल करते हैं. साथ ही, स्क्रिप्ट ग्लोबल के तौर पर बाउंड होने पर, Allocation ऑब्जेक्ट को rsGetElementAt_type() और rsSetElementAt_type() का इस्तेमाल करके कर्नेल में ऐक्सेस किया जा सकता है. Allocation ऑब्जेक्ट की मदद से, ऐरे को Java कोड से RenderScript कोड में और इसके उलट, RenderScript कोड से Java कोड में पास किया जा सकता है. आम तौर पर, Allocation ऑब्जेक्ट को createTyped() या createFromBitmap() का इस्तेमाल करके बनाया जाता है.
  3. ज़रूरी स्क्रिप्ट बनाएं. RenderScript का इस्तेमाल करते समय, आपके पास दो तरह की स्क्रिप्ट उपलब्ध होती हैं:
    • ScriptC: ये उपयोगकर्ता की तय की गई स्क्रिप्ट होती हैं. इनके बारे में ऊपर RenderScript Kernel लिखना में बताया गया है. हर स्क्रिप्ट में एक Java क्लास होती है, जिसे RenderScript कंपाइलर दिखाता है. इससे Java कोड से स्क्रिप्ट को आसानी से ऐक्सेस किया जा सकता है. इस क्लास का नाम ScriptC_filename होता है. उदाहरण के लिए, अगर ऊपर दिया गया मैपिंग कर्नेल invert.rs में मौजूद है और mRenderScript में पहले से ही RenderScript का कोई कॉन्टेक्स्ट मौजूद है, तो स्क्रिप्ट को इंस्टैंशिएट करने के लिए Java या Kotlin कोड इस तरह का होगा:

      Kotlin

      val invert = ScriptC_invert(renderScript)

      Java

      ScriptC_invert invert = new ScriptC_invert(renderScript);
    • ScriptIntrinsic: ये सामान्य कार्रवाइयों के लिए, पहले से मौजूद RenderScript के कोर हैं. जैसे, गॉसियन ब्लर, कन्वोल्यूशन, और इमेज ब्लेंडिंग. ज़्यादा जानकारी के लिए, ScriptIntrinsic के सबक्लास देखें.
  4. बंटवारे की जानकारी को डेटा से पॉप्युलेट करना. createFromBitmap() की मदद से बनाए गए ऐलोकेशन को छोड़कर, किसी ऐलोकेशन को पहली बार बनाने पर उसमें खाली डेटा भर जाता है. किसी ऐलोकेशन को पॉप्युलेट करने के लिए, Allocation में "कॉपी करें" के किसी एक तरीके का इस्तेमाल करें. "कॉपी" करने के तरीके सिंक्रोनस होते हैं.
  5. ज़रूरी स्क्रिप्ट ग्लोबल सेट करें. set_globalname नाम वाली एक ही ScriptC_filename क्लास में मौजूद तरीकों का इस्तेमाल करके, ग्लोबल सेट किए जा सकते हैं. उदाहरण के लिए, threshold नाम का int वैरिएबल सेट करने के लिए, Java के set_threshold(int) तरीके का इस्तेमाल करें. साथ ही, lookup नाम का rs_allocation वैरिएबल सेट करने के लिए, Java के set_lookup(Allocation) तरीके का इस्तेमाल करें. set के तरीके एसिंक्रोनस होते हैं.
  6. ज़रूरी कर्नेल और कॉल किए जा सकने वाले फ़ंक्शन लॉन्च करें.

    किसी दिए गए कर्नेल को लॉन्च करने के तरीके, ScriptC_filename क्लास में दिखते हैं. इन तरीकों के नाम forEach_mappingKernelName() या reduce_reductionKernelName() होते हैं. ये लॉन्च एक साथ नहीं होते. कोर के आर्ग्युमेंट के आधार पर, यह तरीका एक या उससे ज़्यादा ऐलोकेशन लेता है. इन सभी ऐलोकेशन के डाइमेंशन एक जैसे होने चाहिए. डिफ़ॉल्ट रूप से, कोई भी kernel उन डाइमेंशन के हर निर्देशांक पर लागू होता है. उन निर्देशांक के सबसेट पर कोई kernel लागू करने के लिए, forEach या reduce तरीके के आखिरी आर्ग्युमेंट के तौर पर कोई सही Script.LaunchOptions पास करें.

    उसी ScriptC_filename क्लास में मौजूद invoke_functionName तरीकों का इस्तेमाल करके, कॉल किए जा सकने वाले फ़ंक्शन लॉन्च करें. ये लॉन्च एक साथ नहीं होते.

  7. Allocation ऑब्जेक्ट और javaFutureType ऑब्जेक्ट से डेटा पाएं. Java कोड से Allocation में मौजूद डेटा को ऐक्सेस करने के लिए, आपको Allocation में मौजूद "कॉपी" के किसी एक तरीके का इस्तेमाल करके, उस डेटा को वापस Java में कॉपी करना होगा. रिडक्शन कर्नेल का नतीजा पाने के लिए, आपको javaFutureType.get() तरीके का इस्तेमाल करना होगा. "कॉपी" और get() तरीके सिंक्रोनस होते हैं.
  8. RenderScript कॉन्टेक्स्ट को हटाएं. destroy() का इस्तेमाल करके या RenderScript कॉन्टेक्स्ट ऑब्जेक्ट को ग़ैर-ज़रूरी डेटा के तौर पर हटाकर, RenderScript कॉन्टेक्स्ट को मिटाया जा सकता है. इससे उस कॉन्टेक्स्ट से जुड़े किसी भी ऑब्जेक्ट का इस्तेमाल करने पर, अपवाद दिखता है.

एसिंक्रोनस तरीके से लागू करने का मॉडल

forEach, invoke, reduce, और set के तरीके एसिंक्रोनस होते हैं. अनुरोध की गई कार्रवाई पूरी करने से पहले, इनमें से कोई भी तरीका Java पर वापस आ सकता है. हालांकि, अलग-अलग कार्रवाइयों को उसी क्रम में क्रम से लगाया जाता है जिस क्रम में उन्हें लॉन्च किया जाता है.

Allocation क्लास, डेटा को ऐलोकेशन में और उससे कॉपी करने के लिए "कॉपी" तरीके उपलब्ध कराती है. "कॉपी" तरीका सिंक्रोनस होता है. साथ ही, इसे ऊपर दी गई उन सभी एसिंक्रोनस कार्रवाइयों के हिसाब से क्रम में लगाया जाता है जो एक ही ऐलोकेशन को छूती हैं.

रिफ़्लेक्ट की गई javaFutureType क्लास, रिडक्शन का नतीजा पाने के लिए get() तरीका उपलब्ध कराती हैं. get() सिंक्रोनस है और इसे कम करने के हिसाब से क्रम में लगाया जाता है, जो एसिंक्रोनस है.

सिंगल-सोर्स RenderScript

Android 7.0 (एपीआई लेवल 24) में, प्रोग्रामिंग की एक नई सुविधा जोड़ी गई है. इसका नाम सिंगल-सोर्स रेंडरस्क्रिप्ट है. इसमें, कर्नेल को Java के बजाय उस स्क्रिप्ट से लॉन्च किया जाता है जहां उन्हें तय किया गया है. फ़िलहाल, यह तरीका सिर्फ़ मैपिंग कर्नेल के लिए उपलब्ध है. कम शब्दों में बताने के लिए, इस सेक्शन में इन्हें सिर्फ़ "कर्नेल" कहा गया है. इस नई सुविधा की मदद से, स्क्रिप्ट में जाकर भी टाइप rs_allocation के ऐलोकेशन बनाए जा सकते हैं. अब पूरी स्क्रिप्ट में ही पूरा एल्गोरिदम लागू किया जा सकता है. भले ही, इसके लिए एक से ज़्यादा कर्नेल लॉन्च करने की ज़रूरत हो. इससे दो फ़ायदे मिलते हैं: कोड को पढ़ने में आसानी होती है, क्योंकि यह एक ही भाषा में एल्गोरिदम को लागू करता है. साथ ही, कोड तेज़ी से काम करता है, क्योंकि एक से ज़्यादा कर्नेल लॉन्च करने पर, Java और RenderScript के बीच कम ट्रांज़िशन होते हैं.

सिंगल-सोर्स RenderScript में, RenderScript के लिए कोर लिखना में बताए गए तरीके से कोर लिखे जाते हैं. इसके बाद, एक ऐसा फ़ंक्शन लिखें जिसे कॉल किया जा सके. यह फ़ंक्शन, rsForEach() को लॉन्च करने के लिए कॉल करता है. यह एपीआई, पहले पैरामीटर के तौर पर एक कर्नेल फ़ंक्शन लेता है. इसके बाद, इनपुट और आउटपुट के लिए एलोकेशन लेता है. मिलता-जुलता एक एपीआई rsForEachWithOptions(), rs_script_call_t टाइप का एक अतिरिक्त आर्ग्युमेंट लेता है. यह आर्ग्युमेंट, कोर फ़ंक्शन के लिए प्रोसेस करने के लिए, इनपुट और आउटपुट के एलोकेशन से एलिमेंट का सबसेट तय करता है.

RenderScript कैलकुलेशन शुरू करने के लिए, Java से invokable फ़ंक्शन को कॉल किया जाता है. Java कोड से RenderScript का इस्तेमाल करना में दिया गया तरीका अपनाएं. सही कर्नेल लॉन्च करें चरण में, invoke_function_name() का इस्तेमाल करके, कॉल किए जा सकने वाले फ़ंक्शन को कॉल करें. इससे, कर्नेल लॉन्च करने के साथ-साथ पूरी गिनती शुरू हो जाएगी.

आम तौर पर, एक कर्नेल लॉन्च से दूसरे में इंटरमीडिएट नतीजे सेव करने और पास करने के लिए, ऐलोकेशन की ज़रूरत होती है. इन्हें बनाने के लिए, rsCreateAllocation() का इस्तेमाल करें. इस एपीआई का इस्तेमाल करना आसान है. इसमें rsCreateAllocation_<T><W>(…), एलिमेंट के लिए डेटा टाइप है और W, एलिमेंट के लिए वेक्टर की चौड़ाई है. एपीआई, डाइमेंशन X, Y, और Z में साइज़ को आर्ग्युमेंट के तौर पर लेता है. 1D या 2D ऐलोकेशन के लिए, डाइमेंशन Y या Z का साइज़ छोड़ा जा सकता है. उदाहरण के लिए, rsCreateAllocation_uchar4(16384), 16384 एलिमेंट का एक 1D ऐलोकेशन बनाता है. इनमें से हर एलिमेंट uchar4 टाइप का होता है.

ऐलोकेशन, सिस्टम की मदद से अपने-आप मैनेज होते हैं. आपको उन्हें साफ़ तौर पर रिलीज़ करने या मुक्त करने की ज़रूरत नहीं है. हालांकि, rsClearObject(rs_allocation* alloc) को कॉल करके यह बताया जा सकता है कि आपको अब मौजूदा ऐलोकेशन के लिए, हैंडल alloc की ज़रूरत नहीं है, ताकि सिस्टम जल्द से जल्द रिसॉर्स खाली कर सके.

RenderScript के लिए कोर लिखना सेक्शन में, इमेज को उलटा करने वाले कोर का उदाहरण दिया गया है. यहां दिए गए उदाहरण में, एक इमेज पर एक से ज़्यादा इफ़ेक्ट लागू करने के लिए, सिंगल-सोर्स RenderScript का इस्तेमाल करने का तरीका बताया गया है. इसमें एक और कर्नेल, greyscale शामिल है, जो रंगीन इमेज को ब्लैक ऐंड व्हाइट में बदल देता है. इसके बाद, इनवोक किया जा सकने वाला फ़ंक्शन process(), उन दो कर्नेल को किसी इनपुट इमेज पर लगातार लागू करता है और एक आउटपुट इमेज बनाता है. इनपुट और आउटपुट, दोनों के लिए ऐलोकेशन को rs_allocation टाइप के आर्ग्युमेंट के तौर पर पास किया जाता है.

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

Java या Kotlin से process() फ़ंक्शन को इस तरह कॉल किया जा सकता है:

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

इस उदाहरण में दिखाया गया है कि दो कर्नेल लॉन्च वाले एल्गोरिदम को, RenderScript भाषा में पूरी तरह से कैसे लागू किया जा सकता है. सिंगल-सोर्स रेंडरस्क्रिप्ट के बिना, आपको Java कोड से दोनों कर्नेल लॉन्च करने होंगे. इससे, कर्नेल लॉन्च को कर्नेल डेफ़िनिशन से अलग किया जा सकता है. साथ ही, पूरे एल्गोरिदम को समझना मुश्किल हो जाता है. सिंगल-सोर्स RenderScript कोड को पढ़ना आसान है. साथ ही, यह कोड, कर्नेल लॉन्च के दौरान Java और स्क्रिप्ट के बीच ट्रांज़िशन को भी खत्म करता है. कुछ बार-बार इस्तेमाल होने वाले एल्गोरिदम, कर्नेल को सैकड़ों बार लॉन्च कर सकते हैं. इससे, इस तरह के ट्रांज़िशन का ओवरहेड काफ़ी ज़्यादा हो जाता है.

स्क्रिप्ट ग्लोबल

स्क्रिप्ट ग्लोबल, स्क्रिप्ट (.rs) फ़ाइल में मौजूद एक सामान्य नॉन-static ग्लोबल वैरिएबल होता है. filename.rs फ़ाइल में तय की गई var नाम की स्क्रिप्ट ग्लोबल के लिए, ScriptC_filename क्लास में एक तरीका get_var दिखेगा. जब तक ग्लोबल वैल्यू const नहीं होती, तब तक एक तरीका set_var भी होगा.

किसी स्क्रिप्ट ग्लोबल की दो अलग-अलग वैल्यू होती हैं -- Java वैल्यू और स्क्रिप्ट वैल्यू. इन वैल्यू का इस्तेमाल इस तरह किया जाता है:

  • अगर स्क्रिप्ट में var के लिए स्टैटिक शुरू करने वाला फ़ंक्शन है, तो यह Java और स्क्रिप्ट, दोनों में var की शुरुआती वैल्यू तय करता है. ऐसा न होने पर, शुरुआती वैल्यू शून्य होती है.
  • स्क्रिप्ट में var को ऐक्सेस करने पर, स्क्रिप्ट की वैल्यू को पढ़ा और लिखा जाता है.
  • get_var तरीका, Java की वैल्यू पढ़ता है.
  • set_var तरीका (अगर मौजूद है) तुरंत Java वैल्यू लिखता है और स्क्रिप्ट वैल्यू को असाइनॉनस्क्रोनस तरीके से लिखता है.

ध्यान दें: इसका मतलब है कि स्क्रिप्ट में किसी भी स्टैटिक initialiser के अलावा, स्क्रिप्ट में ग्लोबल से लिखी गई वैल्यू, Java को नहीं दिखती हैं.

रीडक्शन कर्नेल के बारे में ज़्यादा जानकारी

कम करना, डेटा के कलेक्शन को एक वैल्यू में जोड़ने की प्रोसेस है. यह पैरलल प्रोग्रामिंग में एक काम का प्राइमिटिव है. इसमें इन जैसे ऐप्लिकेशन शामिल हैं:

  • पूरे डेटा का योग या गुणनफल निकालना
  • पूरे डेटा पर लॉजिकल ऑपरेशन (and, or, xor) का हिसाब लगाना
  • डेटा में सबसे कम या सबसे ज़्यादा वैल्यू ढूंढना
  • डेटा में किसी खास वैल्यू या किसी खास वैल्यू के कोऑर्डिनेट को खोजना

Android 7.0 (एपीआई लेवल 24) और इसके बाद के वर्शन में, RenderScript रिडक्शन कर्नेल के साथ काम करता है. इससे, उपयोगकर्ता के लिखे गए बेहतर रिडक्शन एल्गोरिदम का इस्तेमाल किया जा सकता है. 1, 2 या 3 डाइमेंशन वाले इनपुट पर, डेटा कम करने वाले केर्नल लॉन्च किए जा सकते हैं.

ऊपर दिए गए उदाहरण में, addint का एक आसान रिडक्शन कर्नेल दिखाया गया है. यहां एक ज़्यादा मुश्किल findMinAndMax रिडक्शन कर्नेल दिया गया है, जो एक डाइमेंशन वाले Allocation में, long की सबसे कम और सबसे ज़्यादा वैल्यू की जगहें ढूंढता है:

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

ध्यान दें: कम करने वाले और भी उदाहरण के लिए, यहां जाएं.

रिडक्शन कर्नेल चलाने के लिए, RenderScript रनटाइम एक या उससे ज़्यादा वैरिएबल बनाता है. इन्हें ऐक्यूमुलेटर डेटा आइटम कहा जाता है. इनका इस्तेमाल, रिडक्शन प्रोसेस की स्थिति को सेव करने के लिए किया जाता है. RenderScript रनटाइम, परफ़ॉर्मेंस को बेहतर बनाने के लिए, इकट्ठा किए गए डेटा आइटम की संख्या को इस तरह से चुनता है. Accumulator डेटा आइटम (accumType) का टाइप, kernel के accumulator फ़ंक्शन से तय होता है -- उस फ़ंक्शन का पहला आर्ग्युमेंट, Accumulator डेटा आइटम का पॉइंटर होता है. डिफ़ॉल्ट रूप से, हर इकट्ठा करने वाले डेटा आइटम को शून्य पर शुरू किया जाता है (जैसे कि memset से); हालांकि, कुछ अलग करने के लिए, इनिलाइज़र फ़ंक्शन लिखा जा सकता है.

उदाहरण: addint कर्नल में, इनपुट वैल्यू जोड़ने के लिए, int टाइप के इकट्ठा करने वाले डेटा आइटम का इस्तेमाल किया जाता है. इसमें कोई शुरुआती फ़ंक्शन नहीं है, इसलिए हर इकट्ठा करने वाले डेटा आइटम को शून्य पर शुरू किया जाता है.

उदाहरण: findMinAndMax केर्नेल में, MinAndMax टाइप के इकट्ठा किए गए डेटा आइटम का इस्तेमाल, अब तक मिली सबसे छोटी और सबसे बड़ी वैल्यू का ट्रैक रखने के लिए किया जाता है. इन वैल्यू को क्रमशः LONG_MAX और LONG_MIN पर सेट करने के लिए, एक इनिशलाइज़र फ़ंक्शन होता है. साथ ही, इन वैल्यू की जगहों को -1 पर सेट करने के लिए, यह दिखाया जाता है कि प्रोसेस किए गए इनपुट के (खाली) हिस्से में वैल्यू मौजूद नहीं हैं.

RenderScript, इनपुट में मौजूद हर निर्देशांक के लिए, आपके Accumulator फ़ंक्शन को एक बार कॉल करता है. आम तौर पर, आपके फ़ंक्शन को इनपुट के हिसाब से, इकट्ठा किए गए डेटा आइटम को किसी तरह से अपडेट करना चाहिए.

उदाहरण: addint कर्नल में, Accumulator फ़ंक्शन, Accumulator डेटा आइटम में किसी इनपुट एलिमेंट की वैल्यू जोड़ता है.

उदाहरण: findMinAndMax केर्नेल में, Accumulator फ़ंक्शन यह जांच करता है कि किसी इनपुट एलिमेंट की वैल्यू, Accumulator डेटा आइटम में रिकॉर्ड की गई सबसे कम वैल्यू से कम या उसके बराबर है या नहीं और/या Accumulator डेटा आइटम में रिकॉर्ड की गई सबसे ज़्यादा वैल्यू से ज़्यादा या उसके बराबर है या नहीं. इसके हिसाब से, Accumulator डेटा आइटम को अपडेट किया जाता है.

इनपुट में मौजूद हर निर्देशांक के लिए, Accumulator फ़ंक्शन को एक बार कॉल करने के बाद, RenderScript को Accumulator डेटा आइटम को एक Accumulator डेटा आइटम में जोड़ना होगा. ऐसा करने के लिए, combiner function लिखा जा सकता है. अगर Accumulator फ़ंक्शन में एक इनपुट है और कोई खास आर्ग्युमेंट नहीं है, तो आपको Combiner फ़ंक्शन लिखने की ज़रूरत नहीं है. RenderScript, Accumulator डेटा आइटम को जोड़ने के लिए Accumulator फ़ंक्शन का इस्तेमाल करेगा. (अगर आपको यह डिफ़ॉल्ट तरीका पसंद नहीं है, तो अब भी आपके पास कॉम्बाइनर फ़ंक्शन लिखने का विकल्प है.)

उदाहरण: addint कर्नल में, कोई कंबाइनर फ़ंक्शन नहीं है. इसलिए, इसमें इकट्ठा करने वाले फ़ंक्शन का इस्तेमाल किया जाएगा. यह सही तरीका है, क्योंकि अगर हम वैल्यू के कलेक्शन को दो हिस्सों में बांटते हैं और उन दो हिस्सों की वैल्यू को अलग-अलग जोड़ते हैं, तो उन दो योगों को जोड़ने का मतलब पूरे कलेक्शन को जोड़ने से है.

उदाहरण: findMinAndMax केर्नेल में, कंबाइनर फ़ंक्शन यह जांच करता है कि "सोर्स" इकट्ठा करने वाले डेटा आइटम *val में रिकॉर्ड की गई सबसे छोटी वैल्यू, "डेस्टिनेशन" इकट्ठा करने वाले डेटा आइटम *accum में रिकॉर्ड की गई सबसे छोटी वैल्यू से कम है या नहीं. साथ ही, *accum को उसी हिसाब से अपडेट करता है. यह ज़्यादा से ज़्यादा वैल्यू के लिए भी इसी तरह काम करता है. इससे *accum को उस स्थिति में अपडेट कर दिया जाता है जो तब होती, जब सभी इनपुट वैल्यू को *accum में इकट्ठा किया जाता, न कि कुछ को *accum और कुछ को *val में.

सभी इकट्ठा किए गए डेटा आइटम को जोड़ने के बाद, RenderScript यह तय करता है कि Java में वापस जाने के लिए, डेटा को कितना कम करना है. ऐसा करने के लिए, outconverter फ़ंक्शन लिखा जा सकता है. अगर आपको कम किए गए डेटा के नतीजे के तौर पर, इकट्ठा किए गए डेटा आइटम की कुल वैल्यू चाहिए, तो आपको आउटकन्वर्टर फ़ंक्शन लिखने की ज़रूरत नहीं है.

उदाहरण: addint कर्नेल में, outconverter फ़ंक्शन नहीं है. एक साथ जोड़े गए डेटा आइटम की आखिरी वैल्यू, इनपुट के सभी एलिमेंट का योग होती है. यह वह वैल्यू होती है जिसे हमें दिखाना होता है.

उदाहरण: findMinAndMax केर्नेल में, outconverter फ़ंक्शन int2 की नतीजा वैल्यू को शुरू करता है, ताकि सभी इकट्ठा किए गए डेटा आइटम के कॉम्बिनेशन से मिली सबसे छोटी और सबसे बड़ी वैल्यू की जगहों को सेव किया जा सके.

रिडक्शन कर्नेल लिखना

#pragma rs reduce, रिडक्शन कर्नेल के नाम और उन फ़ंक्शन के नाम और भूमिकाओं के बारे में बताकर, कर्नेल की परिभाषा तय करता है जिनसे कर्नेल बनता है. ऐसे सभी फ़ंक्शन static होने चाहिए. रिडक्शन कर्नेल के लिए, हमेशा accumulator फ़ंक्शन की ज़रूरत होती है. हालांकि, आपको कर्नेल से जो काम कराना है उसके हिसाब से, कुछ या सभी अन्य फ़ंक्शन को छोड़ा जा सकता है.

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

#pragma में मौजूद आइटम का मतलब इस तरह है:

  • reduce(kernelName) (ज़रूरी है): इससे पता चलता है कि रिडक्शन कर्नेल तय किया जा रहा है. रिफ़्लेक्ट किया गया Java तरीका reduce_kernelName, कोर को लॉन्च करेगा.
  • initializer(initializerName) (ज़रूरी नहीं): इस रिडक्शन कर्नेल के लिए, initializer फ़ंक्शन का नाम बताता है. Kernel लॉन्च करने पर, RenderScript हर accumulator डेटा आइटम के लिए, इस फ़ंक्शन को एक बार कॉल करता है. फ़ंक्शन को इस तरह से तय किया जाना चाहिए:

    static void initializerName(accumType *accum) {  }

    accum, इस फ़ंक्शन को शुरू करने के लिए, इकट्ठा किए गए डेटा आइटम का पॉइंटर है.

    अगर कोई इनिशलाइज़र फ़ंक्शन नहीं दिया जाता है, तो RenderScript हर इकट्ठा किए गए डेटा आइटम को शून्य पर शुरू करता है (जैसे कि memset के ज़रिए), जैसे कि कोई इनिशलाइज़र फ़ंक्शन इस तरह का हो:

    static void initializerName(accumType *accum) {
      memset(accum, 0, sizeof(*accum));
    }
  • accumulator(accumulatorName) (ज़रूरी है): इस रिडक्शन कर्नेल के लिए, इकट्ठा करने वाले फ़ंक्शन का नाम बताता है. जब Kernel को लॉन्च किया जाता है, तो RenderScript, इनपुट में मौजूद हर निर्देशांक के लिए, इस फ़ंक्शन को एक बार कॉल करता है. ऐसा, इनपुट के हिसाब से किसी तरह से Accumulator डेटा आइटम को अपडेट करने के लिए किया जाता है. फ़ंक्शन को इस तरह परिभाषित किया जाना चाहिए:

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

    accum, इस फ़ंक्शन में बदलाव करने के लिए, इकट्ठा किए गए डेटा आइटम का पॉइंटर है. in1 से inN तक एक या उससे ज़्यादा आर्ग्युमेंट होते हैं. ये आर्ग्युमेंट, कर्नेल लॉन्च के लिए दिए गए इनपुट के आधार पर अपने-आप भर जाते हैं. हर इनपुट के लिए एक आर्ग्युमेंट. Accumulator फ़ंक्शन में, खास आर्ग्युमेंट में से कोई भी आर्ग्युमेंट इस्तेमाल किया जा सकता है.

    एक से ज़्यादा इनपुट वाले केर्नल का उदाहरण dotProduct है.

  • combiner(combinerName)

    (ज़रूरी नहीं): इस रिडक्शन कर्नेल के लिए, कॉम्बाइनर फ़ंक्शन का नाम बताता है. RenderScript, इनपुट में मौजूद हर निर्देशांक के लिए एक बार Accumulator फ़ंक्शन को कॉल करने के बाद, इस फ़ंक्शन को ज़रूरत के मुताबिक कई बार कॉल करता है, ताकि सभी Accumulator डेटा आइटम को एक Accumulator डेटा आइटम में जोड़ा जा सके. फ़ंक्शन को इस तरह से तय किया जाना चाहिए:

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

    accum, "डेस्टिनेशन" इकट्ठा करने वाले डेटा आइटम का पॉइंटर है, ताकि इस फ़ंक्शन में बदलाव किया जा सके. other, *accum में "जोड़ने" के लिए, इस फ़ंक्शन के "सोर्स" इकट्ठा करने वाले डेटा आइटम का पॉइंटर है.

    ध्यान दें: ऐसा हो सकता है कि *accum, *other या दोनों को शुरू किया गया हो, लेकिन उन्हें कभी भी Accumulator फ़ंक्शन में पास न किया गया हो. इसका मतलब है कि किसी भी इनपुट डेटा के हिसाब से, एक या दोनों को कभी अपडेट नहीं किया गया है. उदाहरण के लिए, findMinAndMax केर्नेल में, fMMCombiner फ़ंक्शन, idx < 0 की साफ़ तौर पर जांच करता है, क्योंकि यह ऐसे इकट्ठा किए गए डेटा आइटम की जानकारी देता है जिसकी वैल्यू INITVAL होती है.

    अगर आपने कोई कॉम्बाइनर फ़ंक्शन नहीं दिया है, तो RenderScript उसकी जगह पर इकट्ठा करने वाले फ़ंक्शन का इस्तेमाल करता है. यह ऐसा तब होता है, जब कॉम्बाइनर फ़ंक्शन इस तरह का हो:

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

    अगर कोर में एक से ज़्यादा इनपुट हैं, इनपुट डेटा का टाइप और इकट्ठा करने वाले फ़ंक्शन का डेटा टाइप एक जैसा नहीं है या इकट्ठा करने वाले फ़ंक्शन में एक या उससे ज़्यादा खास आर्ग्युमेंट हैं, तो कॉम्बाइनर फ़ंक्शन ज़रूरी है.

  • outconverter(outconverterName) (ज़रूरी नहीं): इस रिडक्शन कर्नेल के लिए, आउटकन्वर्टर फ़ंक्शन का नाम बताता है. RenderScript, सभी इकट्ठा किए गए डेटा आइटम को जोड़ने के बाद, इस फ़ंक्शन को कॉल करता है. इससे, Java में वापस जाने के लिए डेटा कम करने का नतीजा तय होता है. फ़ंक्शन को इस तरह से तय करना चाहिए:

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

    result, नतीजे के डेटा आइटम का पॉइंटर है. इसे रेंडरस्क्रिप्ट रनटाइम ने ऐलोकेट किया है, लेकिन इसे शुरू नहीं किया है. इस फ़ंक्शन को कम करने के नतीजे के साथ शुरू करने के लिए, result का इस्तेमाल किया जाता है. resultType, उस डेटा आइटम का टाइप है. यह accumType से अलग हो सकता है. accum, कम्बाइनर फ़ंक्शन से कैलकुलेट किए गए आखिरी इकट्ठा किए गए डेटा आइटम का पॉइंटर है.

    अगर कोई आउटकन्वर्टर फ़ंक्शन नहीं दिया जाता है, तो RenderScript आखिरी इकट्ठा किए गए डेटा आइटम को नतीजे के डेटा आइटम में कॉपी कर देता है. ऐसा करने पर, यह ऐसा लगता है जैसे कोई आउटकन्वर्टर फ़ंक्शन मौजूद है, जो इस तरह दिखता है:

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

    अगर आपको इकट्ठा किए गए डेटा के टाइप के बजाय कोई दूसरा नतीजा चाहिए, तो आउटकन्वर्टर फ़ंक्शन का इस्तेमाल करना ज़रूरी है.

ध्यान दें कि किसी कर्नेल में इनपुट टाइप, इकट्ठा किए गए डेटा आइटम का टाइप, और नतीजे का टाइप होता है. इनमें से किसी भी टाइप का एक जैसा होना ज़रूरी नहीं है. उदाहरण के लिए, findMinAndMax केर्नेल में, इनपुट टाइप long, इकट्ठा किए गए डेटा आइटम का टाइप MinAndMax, और नतीजे का टाइप int2 अलग-अलग होते हैं.

क्या नहीं माना जा सकता?

किसी दिए गए कर्नेल लॉन्च के लिए, आपको RenderScript के बनाए गए इकट्ठा किए गए डेटा आइटम की संख्या पर भरोसा नहीं करना चाहिए. इस बात की कोई गारंटी नहीं है कि एक ही इनपुट के साथ एक ही केरल के दो लॉन्च से, एक ही संख्या में इकट्ठा किए गए डेटा आइटम बनेंगे.

आपको इस बात पर भरोसा नहीं करना चाहिए कि RenderScript, initializer, accumulator, और combiner फ़ंक्शन को किस क्रम में कॉल करता है. यह उनमें से कुछ को एक साथ भी कॉल कर सकता है. इस बात की कोई गारंटी नहीं है कि एक ही इनपुट वाले एक ही कर्नेल के दो लॉन्च, एक ही क्रम में होंगे. सिर्फ़ इतनी गारंटी है कि सिर्फ़ initializer फ़ंक्शन को कभी भी बिना शुरू किए गए Accumulator डेटा आइटम दिखेगा. उदाहरण के लिए:

  • इस बात की कोई गारंटी नहीं है कि Accumulator फ़ंक्शन को कॉल करने से पहले, सभी Accumulator डेटा आइटम को शुरू किया जाएगा. हालांकि, इसे सिर्फ़ शुरू किए गए Accumulator डेटा आइटम पर कॉल किया जाएगा.
  • इस बात की कोई गारंटी नहीं है कि इनपुट एलिमेंट, Accumulator फ़ंक्शन में किस क्रम में पास किए जाएंगे.
  • इस बात की कोई गारंटी नहीं है कि combiner फ़ंक्शन को कॉल करने से पहले, सभी इनपुट एलिमेंट के लिए Accumulator फ़ंक्शन को कॉल किया गया हो.

इसका एक नतीजा यह है कि findMinAndMax कोर को नतीजा तय करने के लिए कोई फ़ॉर्मूला नहीं दिया जाता: अगर इनपुट में एक ही कम से कम या ज़्यादा से ज़्यादा वैल्यू एक से ज़्यादा बार दिखती है, तो यह पता नहीं चलता कि कोर किस वैल्यू को ढूंढेगा.

आपको क्या गारंटी देनी होगी?

RenderScript सिस्टम, किसी भी कोर को कई अलग-अलग तरीकों से चला सकता है. इसलिए, आपको कुछ नियमों का पालन करना होगा, ताकि आपका कोर आपकी पसंद के मुताबिक काम करे. इन नियमों का पालन न करने पर, आपको गलत नतीजे मिल सकते हैं, प्रोग्राम का व्यवहार अनिश्चित हो सकता है या रनटाइम की गड़बड़ियां हो सकती हैं.

नीचे दिए गए नियमों में अक्सर कहा जाता है कि दो इकट्ठा करने वाले डेटा आइटम में "एक ही वैल्यू" होनी चाहिए. इसका क्या अर्थ है? यह इस बात पर निर्भर करता है कि आपको केरल को क्या करना है. addint जैसे गणितीय कम करने के लिए, आम तौर पर "एक जैसा" का मतलब गणितीय समानता होता है. "कोई भी चुनें" खोज के लिए, findMinAndMax ("इनपुट वैल्यू की सबसे कम और सबसे ज़्यादा वैल्यू की जगह ढूंढें") जैसी खोज में, एक जैसी इनपुट वैल्यू एक से ज़्यादा बार हो सकती हैं. ऐसे में, किसी इनपुट वैल्यू की सभी जगहों को "एक जैसा" माना जाना चाहिए. "सबसे बाईं ओर मौजूद सबसे छोटी और सबसे बड़ी इनपुट वैल्यू की जगह ढूंढने" के लिए, एक ऐसा ही केर्नल लिखा जा सकता है. उदाहरण के लिए, 100वें स्थान पर मौजूद सबसे छोटी वैल्यू को 200वें स्थान पर मौजूद एक जैसी सबसे छोटी वैल्यू के मुकाबले प्राथमिकता दी जाती है. इस केर्नल के लिए, "एक जैसी" का मतलब एक जैसी जगह से होगा, न कि सिर्फ़ एक जैसी वैल्यू से. साथ ही, findMinAndMax के लिए इस्तेमाल किए जाने वाले इकट्ठा करने वाले और जोड़ने वाले फ़ंक्शन, यहां इस्तेमाल किए जाने वाले फ़ंक्शन से अलग होने चाहिए.

इनिटिलाइज़र फ़ंक्शन को आइडेंटिटी वैल्यू बनानी होगी. इसका मतलब है कि अगर I और A, इकट्ठा करने वाले ऐसे डेटा आइटम हैं जिन्हें शुरू करने वाले फ़ंक्शन से शुरू किया गया है और I को कभी भी इकट्ठा करने वाले फ़ंक्शन में पास नहीं किया गया है (हालांकि, A को पास किया जा सकता है), तो

उदाहरण: addint केरल में, एक्यूमुलेटर डेटा आइटम को शून्य पर शुरू किया जाता है. इस केर्नेल के लिए, कॉम्बाइनर फ़ंक्शन जोड़ता है; जोड़ने के लिए, शून्य आइडेंटिटी वैल्यू है.

उदाहरण: findMinAndMax के kernel में, INITVAL पर एक इकट्ठा करने वाले डेटा आइटम को शुरू किया जाता है.

  • fMMCombiner(&A, &I), A को वैसा ही रहने देता है, क्योंकि I, INITVAL है.
  • fMMCombiner(&I, &A), I को A पर सेट करता है, क्योंकि I INITVAL है.

इसलिए, INITVAL असल में पहचान की वैल्यू है.

कॉम्बाइनर फ़ंक्शन, कम्यूटेटिव होना चाहिए. इसका मतलब है कि अगर A और B, एक ही वैल्यू पर सेट किए गए ऐसे इकट्ठा करने वाले डेटा आइटम हैं जिन्हें एक ही वैल्यू पर सेट करने वाले फ़ंक्शन से शुरू किया गया है और जिन्हें इकट्ठा करने वाले फ़ंक्शन में शून्य या उससे ज़्यादा बार पास किया गया है, तो combinerName(&A, &B) को A को उसी वैल्यू पर सेट करना होगा जिस पर combinerName(&B, &A), B को सेट करता है.

उदाहरण: addint केर्नेल में, कॉम्बाइनर फ़ंक्शन दो इकट्ठा करने वाले डेटा आइटम की वैल्यू जोड़ता है; जोड़ने का तरीका, वैल्यू के क्रम के हिसाब से बदलता नहीं है.

उदाहरण: findMinAndMax केर्नेल में, fMMCombiner(&A, &B) और A = minmax(A, B) एक जैसे हैं. साथ ही, minmax कम्यूटेटिव है, इसलिए fMMCombiner भी कम्यूटेटिव है.

कॉम्बाइनर फ़ंक्शन, असोसिएटिव होना चाहिए. इसका मतलब है कि अगर A, B, और C, एक ही वैल्यू पर सेट किए गए ऐसे इकट्ठा करने वाले डेटा आइटम हैं जिन्हें initializer फ़ंक्शन से शुरू किया गया है और जिन्हें इकट्ठा करने वाले फ़ंक्शन में शून्य या उससे ज़्यादा बार पास किया गया है, तो नीचे दिए गए दो कोड क्रम में A को एक ही वैल्यू पर सेट करना होगा:

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

उदाहरण: addint केर्नेल में, कम्बाइनर फ़ंक्शन, दो इकट्ठा किए गए डेटा आइटम की वैल्यू जोड़ता है:

  • 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

जोड़ना एक असोसिएटिव ऑपरेशन है. इसलिए, कॉम्बाइनर फ़ंक्शन भी ऐसा ही है.

उदाहरण: findMinAndMax केर्नेल में,

fMMCombiner(&A, &B)
इसी तरह
A = minmax(A, B)
इसलिए, दोनों क्रम ये हैं
  • 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 असोसिएटिव है और इसलिए fMMCombiner भी है.

एक साथ, इकट्ठा करने वाले फ़ंक्शन और इकट्ठा करने वाले फ़ंक्शन को बुनियादी फ़ोल्डिंग नियम का पालन करना चाहिए. इसका मतलब है कि अगर A और B, इकट्ठा करने वाले डेटा आइटम हैं, A को शुरू करने वाले फ़ंक्शन से शुरू किया गया है, और हो सकता है कि उसे इकट्ठा करने वाले फ़ंक्शन में शून्य या उससे ज़्यादा बार पास किया गया हो, B को शुरू नहीं किया गया है, और args, इकट्ठा करने वाले फ़ंक्शन के किसी खास कॉल के लिए इनपुट आर्ग्युमेंट और खास आर्ग्युमेंट की सूची है, तो नीचे दिए गए दो कोड क्रम में A को एक ही वैल्यू पर सेट करना होगा:

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

उदाहरण: addint केरल में, इनपुट वैल्यू V के लिए:

  • स्टेटमेंट 1, A += V जैसा ही है
  • दूसरा स्टेटमेंट, B = 0 जैसा ही है
  • तीसरा स्टेटमेंट, B += V जैसा ही है, जो B = V जैसा ही है
  • चौथा स्टेटमेंट, A += B जैसा ही है, जो A += V जैसा ही है

स्टेटमेंट 1 और 4, A को एक ही वैल्यू पर सेट करते हैं. इसलिए, यह केरल फ़ोल्ड करने के बुनियादी नियम का पालन करता है.

उदाहरण: findMinAndMax केर्नेल में, निर्देशांक X पर इनपुट वैल्यू V के लिए:

  • स्टेटमेंट 1, A = minmax(A, IndexedVal(V, X)) जैसा ही है
  • दूसरा स्टेटमेंट, B = INITVAL जैसा ही है
  • तीसरा स्टेटमेंट,
    B = minmax(B, IndexedVal(V, X))
    जो B की शुरुआती वैल्यू होने की वजह से,
    B = IndexedVal(V, X)
  • चौथा स्टेटमेंट,
    A = minmax(A, B)
    जो कि
    A = minmax(A, IndexedVal(V, X))

स्टेटमेंट 1 और 4, A को एक ही वैल्यू पर सेट करते हैं. इसलिए, यह केरल फ़ोल्ड करने के बुनियादी नियम का पालन करता है.

Java कोड से रिडक्शन कर्नेल को कॉल करना

फ़ाइल filename.rs में बताए गए kernelName नाम के रिडक्शन कर्नेल के लिए, क्लास ScriptC_filename में तीन तरीके दिखते हैं:

Kotlin

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

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

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

Java

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

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

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

addint कर्नेल को कॉल करने के कुछ उदाहरण यहां दिए गए हैं:

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

पहले तरीके में, कोर के accumulator फ़ंक्शन में हर इनपुट आर्ग्युमेंट के लिए एक इनपुट Allocation आर्ग्युमेंट होता है. RenderScript रनटाइम यह पक्का करने के लिए जांच करता है कि सभी इनपुट ऐलोकेशन के डाइमेंशन एक ही हों और हर इनपुट ऐलोकेशन का Element टाइप, Accumulator फ़ंक्शन के प्रोटोटाइप के इनपुट आर्ग्युमेंट से मेल खाता हो. अगर इनमें से कोई भी जांच पूरी नहीं होती है, तो RenderScript एक अपवाद दिखाता है. kernel उन डाइमेंशन में मौजूद हर कोऑर्डिनेट पर लागू होता है.

दूसरा तरीका, पहले तरीके जैसा ही है. हालांकि, दूसरे तरीके में एक और आर्ग्युमेंट sc होता है. इसका इस्तेमाल, कोर कोऑर्डिनेट के सबसेट में चलाने के लिए किया जा सकता है.

तीसरा तरीका, पहला तरीका जैसा ही है. हालांकि, इसमें ऐलोकेशन इनपुट के बजाय, Java ऐरे इनपुट का इस्तेमाल किया जाता है. यह एक सुविधा है, जिसकी मदद से आपको ऐलोकेशन बनाने और Java कलेक्शन से डेटा को कॉपी करने के लिए कोड लिखने की ज़रूरत नहीं पड़ती. हालांकि, पहले तरीके के बजाय तीसरे तरीके का इस्तेमाल करने से, कोड की परफ़ॉर्मेंस बेहतर नहीं होती. तीसरा तरीका, हर इनपुट ऐरे के लिए एक-डाइमेंशन वाला अस्थायी ऐलोकेशन बनाता है. इसमें, Element टाइप और setAutoPadding(boolean) चालू होता है. साथ ही, ऐरे को ऐलोकेशन में कॉपी करता है, जैसे कि Allocation के सही copyFrom() तरीके से किया गया हो. इसके बाद, यह उन अस्थायी आवंटनों को पास करके, पहला तरीका इस्तेमाल करता है.

ध्यान दें: अगर आपका ऐप्लिकेशन एक ही ऐरे के साथ कई कर्नेल कॉल करेगा या एक ही डाइमेंशन और एलिमेंट टाइप के अलग-अलग ऐरे के साथ करेगा, तो तीसरे तरीके का इस्तेमाल करने के बजाय, खुद एलोकेशन बनाकर, उन्हें पॉप्युलेट करके, और फिर से इस्तेमाल करके परफ़ॉर्मेंस को बेहतर बनाया जा सकता है.

javaFutureType, रिफ़्लेक्ट किए गए रिडक्शन मैथड का रिटर्न टाइप है. यह ScriptC_filename क्लास में, रिफ़्लेक्ट की गई स्टैटिक नेस्ट की गई क्लास है. यह कम किए गए कर्नेल के रन के आने वाले समय के नतीजे को दिखाता है. रन का असल नतीजा पाने के लिए, उस क्लास के get() तरीके को कॉल करें, जो javaResultType टाइप की वैल्यू दिखाता है. get() सिंक्रोनस है.

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 फ़ंक्शन के resultType से तय होता है. अगर resultType, बिना साइन वाला टाइप (स्केलर, वेक्टर या ऐरे) नहीं है, तो javaResultType, सीधे तौर पर उससे जुड़ा Java टाइप होता है. अगर resultType बिना हस्ताक्षर वाला टाइप है और कोई बड़ा Java साइन वाला टाइप है, तो javaResultType वह बड़ा Java साइन वाला टाइप है. अगर ऐसा नहीं है, तो यह सीधे तौर पर उससे जुड़ा Java टाइप है. उदाहरण के लिए:

  • अगर resultType int, int2 या int[15] है, तो javaResultType int, Int2 या int[] है. resultType की सभी वैल्यू को javaResultType से दिखाया जा सकता है.
  • अगर resultType uint, uint2 या uint[15] है, तो javaResultType long, Long2 या long[] है. resultType की सभी वैल्यू को javaResultType से दिखाया जा सकता है.
  • अगर resultType ulong, ulong2 या ulong[15] है, तो javaResultType long, Long2 या long[] है. resultType की कुछ वैल्यू ऐसी होती हैं जिन्हें javaResultType से दिखाया नहीं जा सकता.

javaFutureType, आने वाले समय में मिलने वाले नतीजे का टाइप है. यह outconverter फ़ंक्शन के resultType से जुड़ा होता है.

  • अगर resultType, ऐरे टाइप नहीं है, तो javaFutureType result_resultType है.
  • अगर resultType, Count लंबाई का एक कलेक्शन है, जिसमें memberType टाइप के सदस्य हैं, तो javaFutureType resultArrayCount_memberType है.

उदाहरण के लिए:

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 कोई ऑब्जेक्ट टाइप है (इसमें कलेक्शन टाइप भी शामिल है), तो एक ही इंस्टेंस पर javaFutureType.get() को हर बार कॉल करने पर, एक ही ऑब्जेक्ट दिखेगा.

अगर javaResultType, resultType टाइप की सभी वैल्यू को दिखा नहीं सकता और कोई कम करने वाला केर्नल ऐसी वैल्यू दिखाता है जिसे दिखाया नहीं जा सकता, तो javaFutureType.get() एक अपवाद दिखाता है.

तीसरा तरीका और devecSiInXType

devecSiInXType, accumulator फ़ंक्शन के संबंधित आर्ग्युमेंट के inXType से जुड़ा Java टाइप है. अगर inXType, बिना साइन वाला टाइप या वैक्टर टाइप नहीं है, तो devecSiInXType, सीधे तौर पर उससे जुड़ा Java टाइप है. अगर inXType, बिना साइन वाला स्केलर टाइप है, तो devecSiInXType, साइन वाला स्केलर टाइप है. यह टाइप, साइन वाले स्केलर टाइप के साइज़ के हिसाब से होता है. अगर inXType, साइन वाला वेक्टर टाइप है, तो devecSiInXType, वेक्टर कॉम्पोनेंट टाइप से सीधे तौर पर जुड़ा Java टाइप है. अगर inXType बिना साइन वाला वेक्टर टाइप है, तो devecSiInXType, सीधे तौर पर साइन वाले स्केलर टाइप से जुड़ा Java टाइप है. यह टाइप, वेक्टर कॉम्पोनेंट टाइप के साइज़ के बराबर होता है. उदाहरण के लिए:

  • अगर inXType int है, तो devecSiInXType int है.
  • अगर inXType int2 है, तो devecSiInXType int है. ऐरे, फ़्लैट किया गया वर्शन होता है: इसमें स्केलर एलिमेंट की संख्या, ऐलोकेशन के वेक्टर एलिमेंट की संख्या से दोगुनी होती है. यह उसी तरह से काम करता है जिस तरह Allocation के copyFrom() तरीके काम करते हैं.
  • अगर inXType uint है, तो deviceSiInXType int है. Java ऐरे में साइन वाली वैल्यू को, ऐलोकेशन में उसी बिटपैटर्न की बिना साइन वाली वैल्यू के तौर पर समझा जाता है. यह उसी तरह से काम करता है जिस तरह Allocation के copyFrom() तरीके काम करते हैं.
  • अगर inXType uint2 है, तो deviceSiInXType int है. यह int2 और uint को मैनेज करने के तरीके का कॉम्बिनेशन है: अरे, फ़्लैट किया गया रिप्रज़ेंटेशन है और Java अरे की साइन वाली वैल्यू को, रेंडरस्क्रिप्ट की साइन नहीं वाली एलिमेंट वैल्यू के तौर पर समझा जाता है.

ध्यान दें कि तीसरे तरीके के लिए, इनपुट टाइप को नतीजों के टाइप से अलग तरीके से मैनेज किया जाता है:

  • स्क्रिप्ट का वेक्टर इनपुट, Java साइड पर फ़्लैट किया जाता है, जबकि स्क्रिप्ट का वेक्टर नतीजा नहीं किया जाता.
  • स्क्रिप्ट के बिना हस्ताक्षर वाले इनपुट को Java के साइड पर, साइन किए गए इनपुट के तौर पर दिखाया जाता है. हालांकि, स्क्रिप्ट के बिना हस्ताक्षर वाले नतीजे को Java के साइड पर, साइन किए गए नतीजे के तौर पर दिखाया जाता है. हालांकि, ulong के मामले में ऐसा नहीं होता.

डेटा कम करने वाले अन्य कर्नेल के उदाहरण

#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];
}

अन्य कोड सैंपल

BasicRenderScript, RenderScriptIntrinsic, और Hello Compute के सैंपल, इस पेज पर बताए गए एपीआई के इस्तेमाल को और बेहतर तरीके से दिखाते हैं.