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

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

RenderScript के साथ शुरू करने के लिए, आपको दो मुख्य अवधारणाओं को समझना चाहिए:

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

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

आम तौर पर, RenderScript केर्नेल <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 के कलेक्शन पर काम करता है. डिफ़ॉल्ट रूप से, यह उन डाइमेंशन में हर निर्देशांक के लिए एक बार लागू होता है. आम तौर पर, इसे (लेकिन खास तौर पर नहीं) इनपुट के कलेक्शन को, इनपुट Allocations के कलेक्शन को आउटपुट Allocation में एक बार में एक Element में बदलने के लिए इस्तेमाल किया जाता है.

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

      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 के कलेक्शन पर काम करता है. डिफ़ॉल्ट रूप से, इसका अक्यूम्युलेटर फ़ंक्शन उन डाइमेंशन में हर कोऑर्डिनेट के लिए एक बार काम करता है. आम तौर पर, इसका इस्तेमाल इनपुट 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), किसी अक्यूम्युलेटर डेटा आइटम का पॉइंटर है और दूसरा (इस उदाहरण में val) कर्नेल लॉन्च को दिए गए Allocation इनपुट के आधार पर अपने-आप भरा जाता है. Accumulator डेटा आइटम, RenderScript रनटाइम से बनाया जाता है. डिफ़ॉल्ट रूप से, इसे शून्य पर शुरू किया जाता है. डिफ़ॉल्ट रूप से, यह कर्नेल इसके पूरे इनपुट Allocation पर चलाया जाता है. Allocation में हर Element के लिए, अक्युमिलेटर फ़ंक्शन एक बार चलाया जाता है. डिफ़ॉल्ट रूप से, अक्यूमुलेटर डेटा आइटम की फ़ाइनल वैल्यू को घटाने के नतीजे के तौर पर माना जाता है और इसे Java में दिखाया जाता है. RenderScript रनटाइम जांच करके यह पक्का करता है कि इनपुट ऐलोकेशन का Element टाइप, अक्यूम्युलेटर फ़ंक्शन के प्रोटोटाइप से मेल खाता है; अगर यह मेल नहीं खाता, तो 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 (अगर कुछ भी तय नहीं किया गया है, तो डिफ़ॉल्ट तौर पर): यह विकल्प उन ऐप्लिकेशन के लिए होता है जिन्हें आईईईई 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 के ज़रिए उपलब्ध नहीं हैं.
  • अगर Support Library API का इस्तेमाल किया जाता है, तो नेटिव (android.renderscript) एपीआई का इस्तेमाल करने पर, आपको मिलने वाले APKs के मुकाबले शायद बड़े साइज़ के 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 मैनेजर में जाएं.

सपोर्ट लाइब्रेरी 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 ऑब्जेक्ट को इनपुट और आउटपुट के तौर पर लेते हैं. साथ ही, स्क्रिप्ट ग्लोबल के तौर पर बाउंड होने पर, rsGetElementAt_type() और rsSetElementAt_type() का इस्तेमाल करके Allocation ऑब्जेक्ट को कर्नेल में ऐक्सेस किया जा सकता है. Allocation ऑब्जेक्ट की मदद से, ऐरे को Java कोड से RenderScript कोड में और इसके उलट भेजा जा सकता है. आम तौर पर, Allocation ऑब्जेक्ट को createTyped() या createFromBitmap() का इस्तेमाल करके बनाया जाता है.
  3. ज़रूरी स्क्रिप्ट बनाएं. RenderScript का इस्तेमाल करते समय, आपके पास दो तरह की स्क्रिप्ट उपलब्ध होती हैं:
    • ScriptC: ये उपयोगकर्ता की ओर से तय की गई स्क्रिप्ट हैं, जैसा कि ऊपर RenderScript कर्नेल लिखना में बताया गया है. हर स्क्रिप्ट में एक 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() का इस्तेमाल करके इन्हें बनाया जा सकता है. इस एपीआई का एक आसानी से इस्तेमाल किया जा सकता है, जिसमें किसी एलिमेंट का डेटा टाइप T होता है और एलिमेंट की वेक्टर चौड़ाई W होती है. rsCreateAllocation_<T><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 भाषा में पूरी तरह से कैसे लागू किया जा सकता है. सिंगल-सोर्स 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 वैल्यू लिखता है और स्क्रिप्ट वैल्यू को एसिंक्रोनस तरीके से लिखता है.

ध्यान दें: इसका मतलब है कि स्क्रिप्ट में मौजूद किसी भी स्टैटिक शुरू करने वाले के अलावा, स्क्रिप्ट में ग्लोबल से लिखी गई वैल्यू, 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 के बनाए गए इकट्ठा किए गए डेटा आइटम की संख्या पर भरोसा नहीं करना चाहिए. इस बात की कोई गारंटी नहीं है कि एक ही इनपुट के साथ एक ही केरल के दो लॉन्च से, एक ही संख्या में इकट्ठा किए गए डेटा आइटम बनेंगे.

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

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

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

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

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

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

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

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

उदाहरण: 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, इनीशियलाइज़र फ़ंक्शन से शुरू किए गए अक्युमिलेटर डेटा आइटम हैं और जिन्हें शून्य या उससे ज़्यादा बार अक्यूम्युलेटर फ़ंक्शन को पास किया गया है, तो इन दो कोड सीक्वेंस को 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 जैसा ही है
  • स्टेटमेंट 2 और 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) के साथ कुछ समय के लिए 1-डाइमेंशन वाला ऐलोकेशन बनाता है. साथ ही, अरे को एलोकेशन में कॉपी करता है, जैसे कि Allocation के सही copyFrom() तरीके से किया गया हो. इसके बाद, यह उन अस्थायी आवंटनों को पास करके, पहला तरीका इस्तेमाल करता है.

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

javaFutureType, reflected reduction methods का रिटर्न टाइप है. यह 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 के कुछ मान हैं जिन्हें javaresultsType के ज़रिए नहीं दिखाया जा सकता.

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

  • अगर resultType कोई सरणी प्रकार नहीं है, तो javaFutureType result_resultType है.
  • अगर resultType प्रकार memberType के सदस्यों के साथ लंबाई Count का कलेक्शन है, तो 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 एक Java टाइप है. यह सीधे तौर पर, साइन किए गए स्केलर टाइप के एक ही साइज़ के से मेल खाता है. अगर 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 अरे की साइन की हुई वैल्यू को RenderScript के बिना साइन वाले एलिमेंट की वैल्यू के रूप में समझा जाता है.

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

  • स्क्रिप्ट का सदिश इनपुट 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 के सैंपल, इस पेज पर बताए गए एपीआई के इस्तेमाल को और बेहतर तरीके से दिखाते हैं.