RenderScript, Android पर कंप्यूटेशनल इंटेंसिव टास्क को हाई परफ़ॉर्मेंस पर चलाने के लिए एक फ़्रेमवर्क है. RenderScript को मुख्य रूप से डेटा-पैरलल कंप्यूटेशन के साथ इस्तेमाल करने के लिए बनाया गया है. हालांकि, सीरियल वर्कलोड से भी फ़ायदा मिल सकता है. RenderScript रनटाइम, डिवाइस पर उपलब्ध प्रोसेसर के बीच काम को बांट देता है. जैसे, मल्टी-कोर सीपीयू और जीपीयू. इससे आपको काम शेड्यूल करने के बजाय, एल्गोरिदम को समझने पर फ़ोकस करने में मदद मिलती है. RenderScript, इमेज प्रोसेसिंग, कंप्यूटेशनल फ़ोटोग्राफ़ी या कंप्यूटर विज़न से जुड़े ऐप्लिकेशन के लिए खास तौर पर फ़ायदेमंद है.
RenderScript का इस्तेमाल शुरू करने से पहले, आपको इन दो मुख्य कॉन्सेप्ट के बारे में पता होना चाहिए:
- भाषा, C99 से ली गई भाषा है. इसका इस्तेमाल, हाई परफ़ॉर्मेंस कंप्यूट कोड लिखने के लिए किया जाता है. RenderScript कर्नेल लिखना में बताया गया है कि इसका इस्तेमाल करके, कंप्यूट कर्नेल कैसे लिखे जाते हैं.
- कंट्रोल एपीआई का इस्तेमाल, RenderScript संसाधनों के लाइफ़टाइम को मैनेज करने और कर्नल के एक्ज़ीक्यूशन को कंट्रोल करने के लिए किया जाता है. यह तीन अलग-अलग भाषाओं में उपलब्ध है: Java, Android NDK में C++, और C99 से ली गई कर्नल भाषा. Java कोड से RenderScript का इस्तेमाल करना और सिंगल-सोर्स RenderScript में, पहले और तीसरे विकल्प के बारे में बताया गया है.
RenderScript कर्नेल लिखना
RenderScript कर्नल आम तौर पर .rs
फ़ाइल में मौजूद होता है. यह फ़ाइल <project_root>/src/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
(अगर कुछ भी नहीं बताया गया है, तो यह डिफ़ॉल्ट वैल्यू होती है): यह उन ऐप्लिकेशन के लिए है जिन्हें आईईईई 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
- इस पैकेज में मौजूद एपीआई, Support Library के ज़रिए उपलब्ध हैं. इनकी मदद से, Android 2.3 (एपीआई लेवल 9) और इसके बाद के वर्शन पर काम करने वाले डिवाइसों पर इनका इस्तेमाल किया जा सकता है.
यहां कुछ समस्याएं दी गई हैं:
- Support Library API का इस्तेमाल करने पर, आपके ऐप्लिकेशन का RenderScript हिस्सा, Android 2.3 (एपीआई लेवल 9) और उसके बाद के वर्शन पर काम करने वाले डिवाइसों के साथ काम करेगा. भले ही, आपने RenderScript की किसी भी सुविधा का इस्तेमाल किया हो. इससे आपका ऐप्लिकेशन, नेटिव (
android.renderscript
) एपीआई का इस्तेमाल करने के मुकाबले ज़्यादा डिवाइसों पर काम कर पाता है. - Support Library API के ज़रिए, RenderScript की कुछ सुविधाएं उपलब्ध नहीं हैं.
- Support Library API का इस्तेमाल करने पर, आपको नेटिव (
android.renderscript
) API के मुकाबले बड़े APK मिलेंगे. ऐसा हो सकता है कि ये APK काफ़ी बड़े हों.
RenderScript Support Library के एपीआई का इस्तेमाल करना
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 का इस्तेमाल करने के लिए:
- पक्का करें कि आपने Android SDK का ज़रूरी वर्शन इंस्टॉल किया हो.
- Android बिल्ड प्रोसेस के लिए सेटिंग अपडेट करें, ताकि RenderScript सेटिंग शामिल की जा सकें:
- अपने ऐप्लिकेशन मॉड्यूल के ऐप्लिकेशन फ़ोल्डर में मौजूद
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
- इससे यह तय होता है कि जनरेट किया गया बाइटकोड, किसी ऐसे वर्शन पर वापस चला जाना चाहिए जो डिवाइस के साथ काम करता हो. ऐसा तब होना चाहिए, जब डिवाइस पर टारगेट वर्शन काम न करता हो.
- अपने ऐप्लिकेशन मॉड्यूल के ऐप्लिकेशन फ़ोल्डर में मौजूद
- RenderScript का इस्तेमाल करने वाली अपनी ऐप्लिकेशन क्लास में, Support Library क्लास के लिए इंपोर्ट जोड़ें:
Kotlin
import android.support.v8.renderscript.*
Java
import android.support.v8.renderscript.*;
Java या Kotlin कोड से RenderScript का इस्तेमाल करना
Java या Kotlin कोड से RenderScript का इस्तेमाल करने के लिए, android.renderscript
या android.support.v8.renderscript
पैकेज में मौजूद एपीआई क्लास पर निर्भर रहना पड़ता है. ज़्यादातर ऐप्लिकेशन, इस्तेमाल करने के एक ही बुनियादी पैटर्न को फ़ॉलो करते हैं:
- RenderScript कॉन्टेक्स्ट को शुरू करें.
create(Context)
की मदद से बनाया गयाRenderScript
कॉन्टेक्स्ट यह पक्का करता है कि RenderScript का इस्तेमाल किया जा सके. साथ ही, यह एक ऐसा ऑब्जेक्ट उपलब्ध कराता है जिससे बाद के सभी RenderScript ऑब्जेक्ट के लाइफ़टाइम को कंट्रोल किया जा सकता है. आपको कॉन्टेक्स्ट बनाने की प्रोसेस को लंबे समय तक चलने वाली प्रोसेस के तौर पर देखना चाहिए, क्योंकि यह अलग-अलग हार्डवेयर पर संसाधन बना सकती है. अगर हो सके, तो इसे ऐप्लिकेशन के क्रिटिकल पाथ में नहीं होना चाहिए. आम तौर पर, किसी ऐप्लिकेशन में एक समय में सिर्फ़ एक RenderScript कॉन्टेक्स्ट होता है. - स्क्रिप्ट को पास करने के लिए, कम से कम एक
Allocation
बनाएं.Allocation
एक RenderScript ऑब्जेक्ट है. यह तय किए गए डेटा को सेव करने की सुविधा देता है. स्क्रिप्ट में मौजूद कर्नल,Allocation
ऑब्जेक्ट को इनपुट और आउटपुट के तौर पर लेते हैं. साथ ही, स्क्रिप्ट के ग्लोबल के तौर पर बाइंड किए जाने पर,rsGetElementAt_type()
औरrsSetElementAt_type()
का इस्तेमाल करके कर्नल मेंAllocation
ऑब्जेक्ट को ऐक्सेस किया जा सकता है.Allocation
ऑब्जेक्ट, Java कोड से RenderScript कोड और इसके उलट ऐरे पास करने की अनुमति देते हैं. आम तौर पर,Allocation
ऑब्जेक्ट,createTyped()
याcreateFromBitmap()
का इस्तेमाल करके बनाए जाते हैं. - ज़रूरत के मुताबिक स्क्रिप्ट बनाएं. RenderScript का इस्तेमाल करते समय, आपके पास दो तरह की स्क्रिप्ट उपलब्ध होती हैं:
- ScriptC: ये उपयोगकर्ता के हिसाब से तय की गई स्क्रिप्ट हैं. इनके बारे में ऊपर RenderScript कर्नेल लिखना में बताया गया है. हर स्क्रिप्ट में एक 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
के सबक्लास देखें.
- ScriptC: ये उपयोगकर्ता के हिसाब से तय की गई स्क्रिप्ट हैं. इनके बारे में ऊपर RenderScript कर्नेल लिखना में बताया गया है. हर स्क्रिप्ट में एक Java क्लास होती है. इसे RenderScript कंपाइलर दिखाता है, ताकि Java कोड से स्क्रिप्ट को आसानी से ऐक्सेस किया जा सके. इस क्लास का नाम
- डेटा की मदद से, बजट के बंटवारे की जानकारी भरें.
createFromBitmap()
की मदद से बनाए गए बंटन को छोड़कर, जब कोई बंटन पहली बार बनाया जाता है, तो उसमें कोई डेटा नहीं होता. किसी ऐलोकेशन को भरने के लिए,Allocation
में "कॉपी करें" तरीकों में से किसी एक का इस्तेमाल करें. "copy" तरीके सिंक्रोनस होते हैं. - ज़रूरी स्क्रिप्ट ग्लोबल सेट करें. एक ही
ScriptC_filename
क्लास में मौजूद तरीकों का इस्तेमाल करके, ग्लोबल सेट किए जा सकते हैं. इस क्लास का नामset_globalname
है. उदाहरण के लिए,threshold
नाम काint
वैरिएबल सेट करने के लिए, Java केset_threshold(int)
तरीके का इस्तेमाल करें. इसी तरह,lookup
नाम काrs_allocation
वैरिएबल सेट करने के लिए, Java केset_lookup(Allocation)
तरीके का इस्तेमाल करें.set
के तरीके एसिंक्रोनस होते हैं. - सही कर्नल और कॉल किए जा सकने वाले फ़ंक्शन लॉन्च करें.
किसी दिए गए कर्नल को लॉन्च करने के तरीके,
ScriptC_filename
क्लास में दिखते हैं. इन तरीकों के नामforEach_mappingKernelName()
याreduce_reductionKernelName()
होते हैं. ये लॉन्च एसिंक्रोनस होते हैं. कर्नल के तर्कों के आधार पर, यह तरीका एक या उससे ज़्यादा ऐलोकेशन लेता है. इन सभी के डाइमेंशन एक जैसे होने चाहिए. डिफ़ॉल्ट रूप से, कर्नल उन डाइमेंशन के हर कोऑर्डिनेट पर काम करता है. अगर आपको उन कोऑर्डिनेट के सबसेट पर कर्नल को लागू करना है, तोforEach
याreduce
तरीके के आखिरी आर्ग्युमेंट के तौर पर, सहीScript.LaunchOptions
पास करें.उसी
ScriptC_filename
क्लास में दिखाए गएinvoke_functionName
तरीकों का इस्तेमाल करके, कॉल किए जा सकने वाले फ़ंक्शन लॉन्च करें. ये लॉन्च एसिंक्रोनस होते हैं. Allocation
ऑब्जेक्ट और javaFutureType ऑब्जेक्ट से डेटा वापस पाएं. Java कोड सेAllocation
का डेटा ऐक्सेस करने के लिए, आपको उस डेटा कोAllocation
में "कॉपी" करने के तरीकों में से किसी एक का इस्तेमाल करके, वापस Java में कॉपी करना होगा. रिडक्शन कर्नल का नतीजा पाने के लिए, आपकोjavaFutureType.get()
तरीके का इस्तेमाल करना होगा. "कॉपी करें" औरget()
तरीके सिंक्रोनस होते हैं.- RenderScript कॉन्टेक्स्ट को बंद करें. RenderScript कॉन्टेक्स्ट को
destroy()
की मदद से या RenderScript कॉन्टेक्स्ट ऑब्जेक्ट को गार्बेज इकट्ठा करने की अनुमति देकर डिस्ट्रॉय किया जा सकता है. इससे उस कॉन्टेक्स्ट से जुड़े किसी भी ऑब्जेक्ट का आगे इस्तेमाल करने पर, अपवाद दिखता है.
एसिंक्रोनस एक्ज़ीक्यूशन मॉडल
forEach
, invoke
, reduce
, और set
के रिफ़्लेक्टेड तरीके एसिंक्रोनस होते हैं. इनमें से हर तरीका, अनुरोध की गई कार्रवाई पूरी होने से पहले Java पर वापस आ सकता है. हालांकि, अलग-अलग कार्रवाइयों को उसी क्रम में दिखाया जाता है जिस क्रम में उन्हें लॉन्च किया गया है.
Allocation
क्लास, डेटा को Allocations में और Allocations से कॉपी करने के लिए "copy" तरीके उपलब्ध कराता है. "copy" तरीका सिंक्रोनस होता है. साथ ही, ऊपर दी गई किसी भी एसिंक्रोनस कार्रवाई के हिसाब से क्रम से लगाया जाता है. ये कार्रवाइयां एक ही Allocation को ऐक्सेस करती हैं.
javaFutureType क्लास, रिडक्शन का नतीजा पाने के लिए get()
मेथड उपलब्ध कराती हैं. get()
सिंक्रोनस है और इसे रिडक्शन (जो एसिंक्रोनस है) के हिसाब से क्रम से लगाया जाता है.
सिंगल-सोर्स RenderScript
Android 7.0 (एपीआई लेवल 24) में, सिंगल-सोर्स
RenderScript नाम की नई प्रोग्रामिंग सुविधा दी गई है. इसमें कर्नेल को उस स्क्रिप्ट से लॉन्च किया जाता है जहां उन्हें तय किया गया है. कर्नेल को Java से लॉन्च नहीं किया जाता. फ़िलहाल, यह तरीका सिर्फ़ मैपिंग कर्नल के लिए उपलब्ध है. इस सेक्शन में, इन्हें "कर्नल" कहा गया है. इस नई सुविधा की मदद से, स्क्रिप्ट में
rs_allocation
टाइप के नए असाइनमेंट भी बनाए जा सकते हैं. अब किसी स्क्रिप्ट में पूरे एल्गोरिदम को लागू किया जा सकता है. भले ही, इसके लिए कई कर्नल लॉन्च करने की ज़रूरत हो.
इसके दो फ़ायदे हैं: कोड को पढ़ना आसान हो जाता है, क्योंकि यह किसी एल्गोरिदम को एक ही भाषा में लागू करता है. साथ ही, यह कोड को ज़्यादा तेज़ी से प्रोसेस कर सकता है. ऐसा इसलिए होता है, क्योंकि कई कर्नल लॉन्च के दौरान Java और RenderScript के बीच कम ट्रांज़िशन होते हैं.
सिंगल-सोर्स RenderScript में, कर्नेल को
RenderScript कर्नेल लिखना में बताए गए तरीके से लिखा जाता है. इसके बाद, एक ऐसा फ़ंक्शन लिखें जिसे कॉल किया जा सके. यह फ़ंक्शन, उन्हें लॉन्च करने के लिए
rsForEach()
को कॉल करता है. यह एपीआई, कर्नल फ़ंक्शन को पहले पैरामीटर के तौर पर लेता है. इसके बाद, इनपुट और आउटपुट के लिए मेमोरी तय करता है. इसी तरह का एक अन्य एपीआई
rsForEachWithOptions()
,
rs_script_call_t
टाइप का एक अतिरिक्त आर्ग्युमेंट लेता है. यह आर्ग्युमेंट, कर्नल फ़ंक्शन को प्रोसेस करने के लिए, इनपुट और आउटपुट के लिए तय किए गए एलिमेंट का सबसेट तय करता है.
RenderScript कंप्यूटेशन शुरू करने के लिए, Java से invokable फ़ंक्शन को कॉल करें.
Java कोड से RenderScript का इस्तेमाल करना में दिया गया तरीका अपनाएं.
सही कर्नल लॉन्च करें चरण में, invoke_function_name()
का इस्तेमाल करके कॉल किए जा सकने वाले फ़ंक्शन को कॉल करें. इससे पूरी कंप्यूटिंग शुरू हो जाएगी. इसमें कर्नल लॉन्च करना भी शामिल है.
एक कर्नल लॉन्च से दूसरे कर्नल लॉन्च तक इंटरमीडिएट नतीजों को सेव करने और पास करने के लिए, अक्सर ऐलोकेशन की ज़रूरत होती है. इन्हें
rsCreateAllocation() का इस्तेमाल करके बनाया जा सकता है. इस एपीआई का इस्तेमाल करना आसान है. इसका एक फ़ॉर्म
rsCreateAllocation_<T><W>(…)
है. इसमें T, एलिमेंट के लिए डेटा टाइप है और W, एलिमेंट के लिए वेक्टर की चौड़ाई है. एपीआई, X, Y, और Z डाइमेंशन में साइज़ को आर्ग्युमेंट के तौर पर लेता है. एक डाइमेंशन या दो डाइमेंशन वाले असाइनमेंट के लिए, Y या Z डाइमेंशन का साइज़ छोड़ा जा सकता है. उदाहरण के लिए, rsCreateAllocation_uchar4(16384)
16,384 एलिमेंट का 1D ऐलोकेशन बनाता है. इनमें से हर एलिमेंट, uchar4
टाइप का होता है.
सिस्टम, बजट के बंटवारे को अपने-आप मैनेज करता है. आपको उन्हें साफ़ तौर पर रिलीज़ करने या हटाने की ज़रूरत नहीं है. हालांकि,
rsClearObject(rs_allocation* alloc)
को कॉल करके यह बताया जा सकता है कि अब आपको हैंडल alloc
की ज़रूरत नहीं है, ताकि सिस्टम जल्द से जल्द संसाधनों को खाली कर सके.
RenderScript कर्नेल लिखना सेक्शन में, इमेज को उलटा करने वाले कर्नेल का उदाहरण दिया गया है. यहां दिए गए उदाहरण में, एक से ज़्यादा इफ़ेक्ट को किसी इमेज पर लागू करने के लिए, Single-Source 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); }
process()
फ़ंक्शन को Java या Kotlin से इस तरह कॉल किया जा सकता है:
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 रिडक्शन कर्नल के साथ काम करता है. इससे उपयोगकर्ता, रिडक्शन के एल्गोरिदम को बेहतर तरीके से लिख पाते हैं. एक, दो या तीन डाइमेंशन वाले इनपुट पर, कर्नेल को कम करने की प्रोसेस शुरू की जा सकती है.
ऊपर दिए गए उदाहरण में, 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 रनटाइम, परफ़ॉर्मेंस को बेहतर बनाने के लिए, ऐक्युमुलेटर डेटा आइटम की संख्या चुनता है. ऐक्युमुलेटर डेटा आइटम (accumType) का टाइप, कर्नल के ऐक्युमुलेटर फ़ंक्शन से तय होता है. इस फ़ंक्शन का पहला आर्ग्युमेंट, ऐक्युमुलेटर डेटा आइटम का पॉइंटर होता है. डिफ़ॉल्ट रूप से, हर एक्युमुलेटर डेटा आइटम को शून्य पर सेट किया जाता है. ऐसा memset
की वजह से होता है. हालांकि, कुछ अलग करने के लिए, इनिशियलाइज़र फ़ंक्शन लिखा जा सकता है.
उदाहरण: addint कर्नल में, एक्युमुलेटर डेटा आइटम (टाइप int
) का इस्तेमाल इनपुट वैल्यू को जोड़ने के लिए किया जाता है. कोई इनिशियलाइज़र फ़ंक्शन नहीं है. इसलिए, हर एक्युमुलेटर डेटा आइटम को शून्य पर सेट किया जाता है.
उदाहरण: findMinAndMax कर्नल में, ऐक्युमुलेटर डेटा आइटम (MinAndMax
टाइप के) का इस्तेमाल, अब तक मिली कम से कम और ज़्यादा से ज़्यादा वैल्यू का ट्रैक रखने के लिए किया जाता है. इन वैल्यू को क्रमशः LONG_MAX
और LONG_MIN
पर सेट करने के लिए, एक इनिशियलाइज़र फ़ंक्शन होता है. साथ ही, इन वैल्यू की जगहों को -1 पर सेट करने के लिए भी एक फ़ंक्शन होता है. इससे पता चलता है कि प्रोसेस किए गए इनपुट के (खाली) हिस्से में वैल्यू मौजूद नहीं हैं.
RenderScript, इनपुट में मौजूद हर कोऑर्डिनेट के लिए, आपके ऐक्युमुलेटर फ़ंक्शन को एक बार कॉल करता है. आम तौर पर, आपके फ़ंक्शन को इनपुट के हिसाब से, एक्युमुलेटर डेटा आइटम को किसी तरह से अपडेट करना चाहिए.
उदाहरण: addint कर्नल में, ऐक्युमुलेटर फ़ंक्शन, इनपुट एलिमेंट की वैल्यू को ऐक्युमुलेटर डेटा आइटम में जोड़ता है.
उदाहरण: findMinAndMax कर्नल में, ऐक्युमुलेटर फ़ंक्शन यह देखता है कि इनपुट एलिमेंट की वैल्यू, ऐक्युमुलेटर डेटा आइटम में रिकॉर्ड की गई कम से कम वैल्यू से कम या इसके बराबर है या नहीं. साथ ही, यह देखता है कि यह वैल्यू, ऐक्युमुलेटर डेटा आइटम में रिकॉर्ड की गई ज़्यादा से ज़्यादा वैल्यू से ज़्यादा या इसके बराबर है या नहीं. इसके बाद, यह ऐक्युमुलेटर डेटा आइटम को अपडेट करता है.
इनपुट में मौजूद हर कोऑर्डिनेट के लिए, एक बार ऐक्युमुलेटर फ़ंक्शन को कॉल करने के बाद, RenderScript को ऐक्युमुलेटर डेटा आइटम को एक साथ जोड़ना होगा, ताकि एक ऐक्युमुलेटर डेटा आइटम बनाया जा सके. इसके लिए, कॉम्बाइनर फ़ंक्शन लिखा जा सकता है. अगर ऐक्युमुलेटर फ़ंक्शन में सिर्फ़ एक इनपुट है और कोई खास आर्ग्युमेंट नहीं है, तो आपको कंबाइनर फ़ंक्शन लिखने की ज़रूरत नहीं है. RenderScript, ऐक्युमुलेटर फ़ंक्शन का इस्तेमाल करके ऐक्युमुलेटर डेटा आइटम को कंबाइन करेगा. (अगर आपको डिफ़ॉल्ट व्यवहार पसंद नहीं है, तो आपके पास अब भी कॉम्बाइनर फ़ंक्शन लिखने का विकल्प है.)
उदाहरण: addint कर्नल में, कंबाइनर फ़ंक्शन नहीं है. इसलिए, ऐक्युमुलेटर फ़ंक्शन का इस्तेमाल किया जाएगा. यह सही तरीका है, क्योंकि अगर हम वैल्यू के कलेक्शन को दो हिस्सों में बांटते हैं और उन दो हिस्सों में वैल्यू को अलग-अलग जोड़ते हैं, तो उन दो योगों को जोड़ने का मतलब पूरे कलेक्शन को जोड़ने जैसा ही होता है.
उदाहरण: findMinAndMax कर्नेल में, कंबाइनर फ़ंक्शन यह देखता है कि "source" ऐक्युमुलेटर डेटा आइटम *val
में रिकॉर्ड की गई सबसे छोटी वैल्यू, "destination" ऐक्युमुलेटर डेटा आइटम *accum
में रिकॉर्ड की गई सबसे छोटी वैल्यू से कम है या नहीं. इसके बाद, वह *accum
को अपडेट करता है. यह ज़्यादा से ज़्यादा वैल्यू के लिए भी इसी तरह काम करता है. इससे *accum
अपडेट हो जाता है. यह उस स्थिति में अपडेट होता है जब सभी इनपुट वैल्यू को *accum
में इकट्ठा किया गया हो. ऐसा तब होता है, जब कुछ वैल्यू को *accum
और कुछ को *val
में इकट्ठा किया गया हो.
सभी एक्युमुलेटर डेटा आइटम को एक साथ जोड़ने के बाद, RenderScript यह तय करता है कि Java को कौनसे नतीजे भेजने हैं. इसके लिए, outconverter फ़ंक्शन लिखा जा सकता है. अगर आपको कंबाइन किए गए एक्युमुलेटर डेटा आइटम की फ़ाइनल वैल्यू को रिडक्शन का नतीजा बनाना है, तो आपको 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)
(ज़रूरी नहीं): यह इस रिडक्शन कर्नल के लिए, इनिशियलाइज़र फ़ंक्शन का नाम तय करता है. कर्नल लॉन्च करने पर, 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)
(ज़रूरी नहीं): यह इस रिडक्शन कर्नल के लिए outconverter फ़ंक्शन का नाम तय करता है. RenderScript, ऐक्युमुलेटर के सभी डेटा आइटम को एक साथ जोड़ने के बाद, इस फ़ंक्शन को कॉल करता है. इससे यह तय किया जाता है कि Java को कौनसी वैल्यू भेजी जाएगी. फ़ंक्शन को इस तरह से तय किया जाना चाहिए:static void outconverterName(resultType *result, const accumType *accum) { … }
result
, नतीजे के डेटा आइटम का पॉइंटर है. इसे RenderScript रनटाइम ने मेमोरी में जगह दी है, लेकिन अभी तक शुरू नहीं किया है. इस फ़ंक्शन को इसे रिडक्शन के नतीजे के साथ शुरू करना है. resultType, उस डेटा आइटम का टाइप है. यह ज़रूरी नहीं है कि यह accumType के जैसा हो.accum
, कंबाइनर फ़ंक्शन से कंप्यूट किए गए फ़ाइनल एक्युमुलेटर डेटा आइटम का पॉइंटर है.अगर आपने outconverter फ़ंक्शन नहीं दिया है, तो RenderScript, फ़ाइनल ऐक्युमुलेटर डेटा आइटम को नतीजे के डेटा आइटम में कॉपी करता है. ऐसा तब होता है, जब कोई outconverter फ़ंक्शन मौजूद हो और वह इस तरह दिखता हो:
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
को पास किया जा सकता है), तो
combinerName(&A, &I)
कोA
उसी तरह छोड़ना होगाcombinerName(&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
के जैसा है - स्टेटमेंट 2,
B = 0
के जैसा ही है - स्टेटमेंट 3,
B += V
के जैसा है. यहB = V
के जैसा है - स्टेटमेंट 4,
A += B
के जैसा है. यहA += V
के जैसा है
पहले और चौथे स्टेटमेंट में A
को एक ही वैल्यू पर सेट किया गया है. इसलिए, यह कर्नल फ़ोल्डिंग के बुनियादी नियम का पालन करता है.
उदाहरण: findMinAndMax कर्नल में, कोऑर्डिनेट X पर इनपुट वैल्यू V के लिए:
- स्टेटमेंट 1,
A = minmax(A, IndexedVal(V, X))
के जैसा है - स्टेटमेंट 2,
B = INITVAL
के जैसा ही है - तीसरा स्टेटमेंट,
जो कि B की शुरुआती वैल्यू होने की वजह से, इसके बराबर हैB = minmax(B, IndexedVal(V, X))
B = IndexedVal(V, X)
- स्टेटमेंट 4,
जो कि इसके बराबर हैA = minmax(A, B)
A = minmax(A, IndexedVal(V, X))
पहले और चौथे स्टेटमेंट में 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 ऐरे से डेटा को किसी Allocation में कॉपी करने के लिए, कोड लिखने की ज़रूरत नहीं पड़ती. हालांकि, पहले तरीके के बजाय तीसरे तरीके का इस्तेमाल करने से, कोड की परफ़ॉर्मेंस बेहतर नहीं होती. हर इनपुट ऐरे के लिए, तीसरा तरीका एक डाइमेंशन वाला अस्थायी ऐलोकेशन बनाता है. इसमें सही Element
टाइप होता है और setAutoPadding(boolean)
चालू होता है. साथ ही, यह ऐरे को ऐलोकेशन में कॉपी करता है. ऐसा Allocation
के सही copyFrom()
तरीके से किया जाता है. इसके बाद, यह पहले तरीके को कॉल करता है और उन अस्थायी असाइनमेंट को पास करता है.
ध्यान दें: अगर आपका ऐप्लिकेशन एक ही ऐरे या एक ही डाइमेंशन और एलिमेंट टाइप की अलग-अलग ऐरे के साथ कई कर्नल कॉल करता है, तो परफ़ॉर्मेंस को बेहतर बनाया जा सकता है. इसके लिए, तीसरे तरीके का इस्तेमाल करने के बजाय, खुद ही ऐलोकेशन बनाएं, उनमें डेटा भरें, और उनका फिर से इस्तेमाल करें.
javaFutureType, रिफ़्लेक्ट किए गए रिडक्शन मेथड का रिटर्न टाइप है. यह ScriptC_filename
क्लास में रिफ़्लेक्ट की गई स्टैटिक नेस्टेड क्लास है. यह कर्नेल रन में कमी के बाद मिलने वाले नतीजे को दिखाता है. रन का असल नतीजा पाने के लिए, उस क्लास के get()
तरीके को कॉल करें. यह javaResultType टाइप की वैल्यू दिखाता है. get()
, सिंक्रोनस है.
Kotlin
class ScriptC_filename(rs: RenderScript) : ScriptC(…) { object javaFutureType { fun get(): javaResultType { … } } }
Java
public class ScriptC_filename extends ScriptC { public static class javaFutureType { public javaResultType get() { … } } }
javaResultType, outconverter फ़ंक्शन के resultType से तय होता है. जब तक resultType एक बिना हस्ताक्षर वाला टाइप (स्केलर, वेक्टर या कलेक्शन) नहीं होता, तब तक javaResultType सीधे तौर पर उससे जुड़ा Java टाइप होता है. अगर resultType एक अनसाइंड टाइप है और कोई बड़ा Java साइंड टाइप मौजूद है, तो javaResultType वह बड़ा Java साइंड टाइप होता है. इसके अलावा, यह सीधे तौर पर उससे जुड़ा Java टाइप होता है. उदाहरण के लिए:
- अगर resultType
int
,int2
याint[15]
है, तो javaResultTypeint
,Int2
याint[]
है. resultType की सभी वैल्यू को javaResultType से दिखाया जा सकता है. - अगर resultType,
uint
,uint2
याuint[15]
है, तो javaResultType,long
,Long2
याlong[]
होगा. resultType की सभी वैल्यू को javaResultType से दिखाया जा सकता है. - अगर resultType
ulong
,ulong2
याulong[15]
है, तो javaResultTypelong
,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, Java टाइप है. यह ऐक्युमुलेटर फ़ंक्शन के संबंधित तर्क के inXType से मेल खाता है. जब तक inXType एक बिना हस्ताक्षर वाला टाइप या वेक्टर टाइप नहीं होता, तब तक devecSiInXType सीधे तौर पर उससे मेल खाने वाला Java टाइप होता है. अगर inXType एक अनसाइंड स्केलर टाइप है, तो devecSiInXType, उसी साइज़ के साइंड स्केलर टाइप से सीधे तौर पर मेल खाने वाला Java टाइप है. अगर inXType एक साइंड वेक्टर टाइप है, तो devecSiInXType, वेक्टर कॉम्पोनेंट टाइप से सीधे तौर पर जुड़ा Java टाइप है. अगर inXType एक बिना हस्ताक्षर वाला वेक्टर टाइप है, तो devecSiInXType, वेक्टर कॉम्पोनेंट टाइप के साइज़ के बराबर साइज़ वाले हस्ताक्षरित स्केलर टाइप से सीधे तौर पर मेल खाने वाला Java टाइप है. उदाहरण के लिए:
- अगर inXType
int
है, तो devecSiInXTypeint
है. - अगर inXType
int2
है, तो devecSiInXTypeint
है. यह ऐरे, फ़्लैट किया गया वर्शन है: इसमें, Allocation में मौजूद दो कॉम्पोनेंट वाले वेक्टर एलिमेंट की तुलना में, स्केलर एलिमेंट की संख्या दोगुनी है.Allocation
केcopyFrom()
तरीके भी इसी तरह काम करते हैं. - अगर inXType
uint
है, तो deviceSiInXTypeint
है. Java ऐरे में मौजूद साइंड वैल्यू को, Allocation में उसी बिटपैटर्न की अनसाइंड वैल्यू के तौर पर इंटरप्रेट किया जाता है. यह उसी तरह काम करता है जिस तरहcopyFrom()
केAllocation
तरीके काम करते हैं. - अगर inXType
uint2
है, तो deviceSiInXTypeint
है. यह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 सैंपल से, इस पेज पर बताए गए एपीआई के इस्तेमाल के बारे में ज़्यादा जानकारी मिलती है.