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

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

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

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

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

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

  • एक प्रैगमा डिक्लेरेशन (#pragma version(1)), जो इस स्क्रिप्ट में इस्तेमाल की गई RenderScript कर्नेल भाषा के वर्शन के बारे में बताता है. फ़िलहाल, सिर्फ़ 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 आर्ग्युमेंट, कर्नेल लॉन्च को पास किए गए इनपुट Allocation के आधार पर अपने-आप भर जाता है. x और y आर्ग्युमेंट के बारे में नीचे बताया गया है. कर्नल से मिली वैल्यू, आउटपुट Allocation में सही जगह पर अपने-आप लिख दी जाती है. डिफ़ॉल्ट रूप से, यह कर्नेल अपने पूरे इनपुट Allocation पर चलाया जाता है. इसमें Allocation में हर Element के लिए, कर्नेल फ़ंक्शन को एक बार चलाया जाता है.

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

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

      अगर आपको कर्नल में मौजूद इनपुट या आउटपुट 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 के आधार पर अपने-आप भर जाता है. एक्युमलेटर डेटा आइटम, 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 (अगर कुछ भी तय नहीं किया गया है, तो यह डिफ़ॉल्ट तौर पर सेट होता है): यह उन ऐप्लिकेशन के लिए है जिन्हें 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 एपीआई ऐक्सेस करना

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

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

यहां कुछ नुकसान बताए गए हैं:

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

RenderScript Support Library के एपीआई इस्तेमाल करना

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

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

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

Android SDK Manager में जाकर, इन टूल के इंस्टॉल किए गए वर्शन को देखा और अपडेट किया जा सकता है.

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

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

      Groovy

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

      Kotlin

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

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

      • renderscriptTargetApi - इससे पता चलता है कि बाइट कोड का कौनसा वर्शन जनरेट करना है. हमारा सुझाव है कि आप इस वैल्यू को सबसे कम एपीआई लेवल पर सेट करें, ताकि आपको वे सभी सुविधाएं मिल सकें जिनका इस्तेमाल किया जा रहा है. साथ ही, renderscriptSupportModeEnabled को true पर सेट करें. इस सेटिंग के लिए मान्य वैल्यू, 11 से लेकर हाल ही में रिलीज़ किए गए एपीआई लेवल तक की कोई भी पूर्णांक वैल्यू हो सकती है. अगर आपके ऐप्लिकेशन मेनिफ़ेस्ट में बताए गए एसडीके के कम से कम वर्शन को किसी दूसरी वैल्यू पर सेट किया गया है, तो उस वैल्यू को अनदेखा कर दिया जाता है. साथ ही, एसडीके के कम से कम वर्शन को सेट करने के लिए, बिल्ड फ़ाइल में मौजूद टारगेट वैल्यू का इस्तेमाल किया जाता है.
      • 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 कोड में ऐरे को पास करने की अनुमति देते हैं. साथ ही, RenderScript कोड से Java कोड में ऐरे को पास करने की अनुमति देते हैं. Allocation ऑब्जेक्ट, आम तौर पर createTyped() या createFromBitmap() का इस्तेमाल करके बनाए जाते हैं.
  3. ज़रूरी स्क्रिप्ट बनाएं. RenderScript का इस्तेमाल करते समय, आपके पास दो तरह की स्क्रिप्ट उपलब्ध होती हैं:
    • ScriptC: ये उपयोगकर्ता की तय की गई स्क्रिप्ट होती हैं. इनके बारे में ऊपर RenderScript Kernel लिखना सेक्शन में बताया गया है. हर स्क्रिप्ट में एक Java क्लास होती है, जिसे RenderScript कंपाइलर दिखाता है. इससे Java कोड से स्क्रिप्ट को ऐक्सेस करना आसान हो जाता है. इस क्लास का नाम ScriptC_filename है. उदाहरण के लिए, अगर ऊपर दिया गया मैपिंग कर्नेल invert.rs में मौजूद है और RenderScript कॉन्टेक्स्ट पहले से ही mRenderScript में मौजूद है, तो स्क्रिप्ट को इंस्टैंशिएट करने के लिए 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() नाम के तरीके होते हैं. ये लॉन्च एसिंक्रोनस होते हैं. कर्नल के आर्ग्युमेंट के आधार पर, यह तरीका एक या उससे ज़्यादा ऐलोकेशन लेता है. इन सभी के डाइमेंशन एक जैसे होने चाहिए. डिफ़ॉल्ट रूप से, कर्नल उन डाइमेंशन के हर कोऑर्डिनेट पर लागू होता है. उन कोऑर्डिनेट के सबसेट पर कर्नल लागू करने के लिए, forEach या reduce तरीके के आखिरी आर्ग्युमेंट के तौर पर सही Script.LaunchOptions पास करें.

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

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

एसिंक्रोनस एक्ज़ीक्यूशन मॉडल

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

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

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

सिंगल-सोर्स रेंडरस्क्रिप्ट

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

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

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

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

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

रेंडरस्क्रिप्ट कर्नेल लिखना सेक्शन में, इमेज को इनवर्ट करने वाले कर्नेल का उदाहरण दिया गया है. नीचे दिए गए उदाहरण में, सिंगल-सोर्स रेंडरस्क्रिप्ट का इस्तेमाल करके, किसी इमेज पर एक से ज़्यादा इफ़ेक्ट लागू करने का तरीका बताया गया है. इसमें एक और कर्नेल, 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 कोड से दोनों कर्नेल लॉन्च करने होंगे. इससे कर्नेल लॉन्च, कर्नेल की परिभाषाओं से अलग हो जाएंगे और पूरे एल्गोरिदम को समझना मुश्किल हो जाएगा. सिंगल-सोर्स रेंडरस्क्रिप्ट कोड को पढ़ना आसान है. साथ ही, यह कर्नेल लॉन्च के दौरान, 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 रिडक्शन कर्नेल दिया गया है, जो 1-डाइमेंशन वाले 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 रनटाइम एक या उससे ज़्यादा वैरिएबल बनाता है. इन्हें एक्युमलेटर डेटा आइटम कहा जाता है. ये वैरिएबल, रिडक्शन प्रोसेस की स्थिति को सेव रखते हैं. रेंडरस्क्रिप्ट रनटाइम, एक्युमलेटर डेटा आइटम की संख्या को इस तरह चुनता है कि परफ़ॉर्मेंस को बेहतर बनाया जा सके. एक्युमलेटर डेटा आइटम (accumType) का टाइप, कर्नल के एक्युमलेटर फ़ंक्शन से तय होता है -- उस फ़ंक्शन का पहला आर्ग्युमेंट, एक्युमलेटर डेटा आइटम का पॉइंटर होता है. डिफ़ॉल्ट रूप से, हर एक्युमलेटर डेटा आइटम को शून्य पर सेट किया जाता है (जैसे कि memset से); हालांकि, कुछ अलग करने के लिए इनिशलाइज़र फ़ंक्शन लिखा जा सकता है.

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

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

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

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

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

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

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

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

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

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

    static void initializerName(accumType *accum) { … }

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

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

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

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

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

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

  • combiner(combinerName)

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

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

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

    ध्यान दें: ऐसा हो सकता है कि *accum, *other या दोनों को शुरू किया गया हो, लेकिन उन्हें कभी भी एक्युमलेटर फ़ंक्शन में पास न किया गया हो. इसका मतलब है कि किसी भी इनपुट डेटा के हिसाब से, एक या दोनों को कभी अपडेट नहीं किया गया. उदाहरण के लिए, 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, नतीजे के डेटा आइटम का पॉइंटर है. इसे RenderScript रनटाइम ने असाइन किया है, लेकिन शुरू नहीं किया है. इस फ़ंक्शन को रिडक्शन के नतीजे के साथ शुरू करने के लिए, ऐसा किया गया है. resultType, उस डेटा आइटम का टाइप है. यह ज़रूरी नहीं है कि यह accumType जैसा ही हो. accum, कंबाइनर फ़ंक्शन से कैलकुलेट किए गए फ़ाइनल एक्युमलेटर डेटा आइटम का पॉइंटर है.

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

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

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

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

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

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

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

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

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

आपको किस बात की गारंटी देनी होगी?

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

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

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

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

उदाहरण: findMinAndMax कर्नेल में, एक्युमलेटर डेटा आइटम को 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 के जैसा है
  • दूसरा स्टेटमेंट 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();

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

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

तीसरा तरीका, पहले तरीके जैसा ही है. हालांकि, इसमें ऐलोकेशन इनपुट के बजाय, Java कलेक्शन इनपुट का इस्तेमाल किया जाता है. यह एक ऐसी सुविधा है जो आपको साफ़ तौर पर ऐलोकेशन बनाने और Java कलेक्शन से डेटा कॉपी करने के लिए कोड लिखने से बचाती है. हालांकि, पहले तरीके के बजाय तीसरे तरीके का इस्तेमाल करने से, कोड की परफ़ॉर्मेंस बेहतर नहीं होती. हर इनपुट ऐरे के लिए, तीसरा तरीका सही Element टाइप और setAutoPadding(boolean) चालू होने के साथ, कुछ समय के लिए 1-डाइमेंशन वाला ऐलोकेशन बनाता है. साथ ही, ऐरे को ऐलोकेशन में कॉपी करता है, जैसे कि 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, 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 सैंपल, इस पेज पर बताए गए एपीआई के इस्तेमाल के बारे में ज़्यादा जानकारी देते हैं.