রেন্ডারস্ক্রিপ্ট ওভারভিউ

রেন্ডারস্ক্রিপ্ট হল Android-এ উচ্চ কার্যসম্পাদনে গণনামূলকভাবে নিবিড় কাজ চালানোর জন্য একটি কাঠামো। রেন্ডারস্ক্রিপ্ট প্রাথমিকভাবে ডেটা-সমান্তরাল গণনার সাথে ব্যবহারের জন্য ভিত্তিক, যদিও সিরিয়াল ওয়ার্কলোডগুলিও উপকৃত হতে পারে। রেন্ডারস্ক্রিপ্ট রানটাইম একটি ডিভাইসে উপলব্ধ প্রসেসরের কাজকে সমান্তরাল করে, যেমন মাল্টি-কোর সিপিইউ এবং জিপিইউ। এটি আপনাকে কাজের সময় নির্ধারণের পরিবর্তে অ্যালগরিদম প্রকাশের উপর ফোকাস করতে দেয়। রেন্ডারস্ক্রিপ্ট বিশেষ করে ইমেজ প্রসেসিং, কম্পিউটেশনাল ফটোগ্রাফি বা কম্পিউটার ভিশন সম্পাদনকারী অ্যাপ্লিকেশনের জন্য উপযোগী।

রেন্ডারস্ক্রিপ্ট দিয়ে শুরু করার জন্য, আপনার বোঝা উচিত দুটি প্রধান ধারণা:

একটি রেন্ডারস্ক্রিপ্ট কার্নেল লেখা

একটি RenderScript কার্নেল সাধারণত <project_root>/src/rs ডিরেক্টরির একটি .rs ফাইলে থাকে; প্রতিটি .rs ফাইলকে স্ক্রিপ্ট বলা হয়। প্রতিটি স্ক্রিপ্টে নিজস্ব কার্নেল, ফাংশন এবং ভেরিয়েবলের সেট থাকে। একটি স্ক্রিপ্ট থাকতে পারে:

  • একটি প্রাগমা ঘোষণা ( #pragma version(1) ) যা এই স্ক্রিপ্টে ব্যবহৃত RenderScript কার্নেল ভাষার সংস্করণ ঘোষণা করে। বর্তমানে, 1 হল একমাত্র বৈধ মান।
  • একটি প্রাগমা ঘোষণা ( #pragma rs java_package_name(com.example.app) ) যা এই স্ক্রিপ্ট থেকে প্রতিফলিত জাভা ক্লাসের প্যাকেজ নাম ঘোষণা করে। মনে রাখবেন যে আপনার .rs ফাইলটি আপনার অ্যাপ্লিকেশন প্যাকেজের অংশ হতে হবে, এবং একটি লাইব্রেরি প্রকল্পে নয়।
  • শূন্য বা তার বেশি কার্যকরী ফাংশন । একটি আহ্বানযোগ্য ফাংশন হল একটি একক-থ্রেডেড রেন্ডারস্ক্রিপ্ট ফাংশন যা আপনি নির্বিচারে আর্গুমেন্ট সহ আপনার জাভা কোড থেকে কল করতে পারেন। এগুলি প্রায়শই একটি বৃহত্তর প্রক্রিয়াকরণ পাইপলাইনের মধ্যে প্রাথমিক সেটআপ বা সিরিয়াল গণনার জন্য দরকারী।
  • শূন্য বা তার বেশি স্ক্রিপ্ট গ্লোবাল । একটি স্ক্রিপ্ট গ্লোবাল সি-তে একটি গ্লোবাল ভেরিয়েবলের অনুরূপ। আপনি জাভা কোড থেকে স্ক্রিপ্ট গ্লোবাল অ্যাক্সেস করতে পারেন এবং এটি প্রায়শই রেন্ডারস্ক্রিপ্ট কার্নেলে প্যারামিটার পাস করার জন্য ব্যবহৃত হয়। স্ক্রিপ্ট গ্লোবাল এখানে আরো বিস্তারিতভাবে ব্যাখ্যা করা হয়েছে।

  • শূন্য বা তার বেশি কম্পিউট কার্নেল । একটি কম্পিউট কার্নেল হল একটি ফাংশন বা ফাংশনের সংগ্রহ যা আপনি ডেটার সংগ্রহ জুড়ে সমান্তরালভাবে চালানোর জন্য রেন্ডারস্ক্রিপ্ট রানটাইমকে নির্দেশ করতে পারেন। দুই ধরনের কম্পিউট কার্নেল আছে: ম্যাপিং কার্নেল (যাকে ফোরচ কার্নেলও বলা হয়) এবং রিডাকশন কার্নেল।

    একটি ম্যাপিং কার্নেল একটি সমান্তরাল ফাংশন যা একই মাত্রার 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;
      }

      বেশিরভাগ ক্ষেত্রে, এটি একটি স্ট্যান্ডার্ড সি ফাংশনের অনুরূপ। ফাংশন প্রোটোটাইপে প্রয়োগ করা RS_KERNEL প্রপার্টি নির্দিষ্ট করে যে ফাংশনটি একটি রেন্ডারস্ক্রিপ্ট ম্যাপিং কার্নেল একটি ইনভোকেবল ফাংশনের পরিবর্তে। কার্নেল লঞ্চে পাঠানো ইনপুট Allocation উপর ভিত্তি করে in আর্গুমেন্ট স্বয়ংক্রিয়ভাবে পূরণ করা হয়। আর্গুমেন্ট x এবং y নিচে আলোচনা করা হয়েছে। কার্নেল থেকে প্রত্যাবর্তিত মানটি স্বয়ংক্রিয়ভাবে আউটপুট Allocation উপযুক্ত অবস্থানে লেখা হয়। ডিফল্টরূপে, এই কার্নেলটি তার সম্পূর্ণ ইনপুট Allocation জুড়ে চালিত হয়, Allocation Element প্রতি কার্নেল ফাংশনের একটি সম্পাদনের সাথে।

      একটি ম্যাপিং কার্নেলে এক বা একাধিক ইনপুট Allocations , একটি একক আউটপুট Allocation বা উভয়ই থাকতে পারে। রেন্ডারস্ক্রিপ্ট রানটাইম চেক করে যে সমস্ত ইনপুট এবং আউটপুট বরাদ্দকরণের একই মাত্রা রয়েছে এবং ইনপুট এবং আউটপুট বরাদ্দের Element ধরনগুলি কার্নেলের প্রোটোটাইপের সাথে মেলে; যদি এই চেকগুলির মধ্যে একটি ব্যর্থ হয়, রেন্ডারস্ক্রিপ্ট একটি ব্যতিক্রম নিক্ষেপ করে।

      দ্রষ্টব্য: Android 6.0 (API স্তর 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 উপর ভিত্তি করে স্বয়ংক্রিয়ভাবে পূরণ করা হয়। রেন্ডারস্ক্রিপ্ট রানটাইম দ্বারা সঞ্চয়কারী ডেটা আইটেম তৈরি করা হয়; ডিফল্টরূপে, এটি শূন্য থেকে শুরু হয়। ডিফল্টরূপে, এই কার্নেলটি তার সম্পূর্ণ ইনপুট Allocation জুড়ে চালিত হয়, Allocation Element প্রতি অ্যাকিউমুলেটর ফাংশনের একটি সম্পাদনের সাথে। ডিফল্টরূপে, সঞ্চয়কারী ডেটা আইটেমের চূড়ান্ত মান হ্রাসের ফলাফল হিসাবে বিবেচিত হয় এবং জাভাতে ফেরত দেওয়া হয়। রেন্ডারস্ক্রিপ্ট রানটাইম ইনপুট বরাদ্দকরণের Element ধরণটি সঞ্চয়কারী ফাংশনের প্রোটোটাইপের সাথে মেলে তা নিশ্চিত করতে পরীক্ষা করে; যদি এটি মেলে না, রেন্ডারস্ক্রিপ্ট একটি ব্যতিক্রম নিক্ষেপ করে।

      একটি রিডাকশন কার্নেলে এক বা একাধিক ইনপুট Allocations থাকে কিন্তু কোনো আউটপুট Allocations থাকে না।

      রিডাকশন কার্নেল এখানে আরো বিস্তারিতভাবে ব্যাখ্যা করা হয়েছে।

      হ্রাস কার্নেলগুলি Android 7.0 (API স্তর 24) এবং পরবর্তীতে সমর্থিত।

    একটি ম্যাপিং কার্নেল ফাংশন বা একটি হ্রাস কার্নেল সঞ্চয়কারী ফাংশন বিশেষ আর্গুমেন্ট x , y , এবং z ব্যবহার করে বর্তমান এক্সিকিউশনের স্থানাঙ্কগুলি অ্যাক্সেস করতে পারে, যা অবশ্যই int বা uint32_t টাইপের হতে হবে। এই আর্গুমেন্ট ঐচ্ছিক.

    একটি ম্যাপিং কার্নেল ফাংশন বা একটি রিডাকশন কার্নেল অ্যাকুমুলেটর ফাংশন rs_kernel_context টাইপের ঐচ্ছিক বিশেষ আর্গুমেন্ট context নিতে পারে। এটি রানটাইম API-এর একটি পরিবারের দ্বারা প্রয়োজন যা বর্তমান সম্পাদনের নির্দিষ্ট বৈশিষ্ট্যগুলি অনুসন্ধান করতে ব্যবহৃত হয় -- উদাহরণস্বরূপ, rsGetDimX । ( context যুক্তিটি Android 6.0 (API স্তর 23) এবং পরবর্তীতে উপলব্ধ।)

  • একটি ঐচ্ছিক init() ফাংশন। init() ফাংশন হল একটি বিশেষ ধরনের ইনভোকেবল ফাংশন যা রেন্ডারস্ক্রিপ্ট রান করে যখন স্ক্রিপ্টটি প্রথম ইনস্ট্যান্ট করা হয়। এটি স্ক্রিপ্ট তৈরিতে স্বয়ংক্রিয়ভাবে কিছু গণনার জন্য অনুমতি দেয়।
  • শূন্য বা তার বেশি স্ট্যাটিক স্ক্রিপ্ট গ্লোবাল এবং ফাংশন । একটি স্ট্যাটিক স্ক্রিপ্ট গ্লোবাল একটি স্ক্রিপ্ট গ্লোবালের সমতুল্য তবে এটি জাভা কোড থেকে অ্যাক্সেস করা যায় না। একটি স্ট্যাটিক ফাংশন হল একটি স্ট্যান্ডার্ড সি ফাংশন যা স্ক্রিপ্টের যেকোন কার্নেল বা ইনভোকেবল ফাংশন থেকে কল করা যেতে পারে কিন্তু জাভা API এর সংস্পর্শে আসে না। যদি জাভা কোড থেকে কোনো স্ক্রিপ্ট গ্লোবাল বা ফাংশন অ্যাক্সেস করার প্রয়োজন না হয়, তবে এটিকে static ঘোষণা করার জন্য অত্যন্ত সুপারিশ করা হয়।

ফ্লোটিং পয়েন্ট নির্ভুলতা সেট করা

আপনি একটি স্ক্রিপ্টে ফ্লোটিং পয়েন্ট নির্ভুলতার প্রয়োজনীয় স্তর নিয়ন্ত্রণ করতে পারেন। এটি কার্যকর যদি সম্পূর্ণ IEEE 754-2008 মান (ডিফল্টরূপে ব্যবহৃত) প্রয়োজন না হয়। নিম্নলিখিত pragmas ফ্লোটিং পয়েন্ট নির্ভুলতার একটি ভিন্ন স্তর সেট করতে পারে:

  • #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 CPU নির্দেশাবলী)।

জাভা থেকে রেন্ডারস্ক্রিপ্ট এপিআই অ্যাক্সেস করা

রেন্ডারস্ক্রিপ্ট ব্যবহার করে এমন একটি অ্যান্ড্রয়েড অ্যাপ্লিকেশন ডেভেলপ করার সময়, আপনি দুটি উপায়ে জাভা থেকে এর API অ্যাক্সেস করতে পারেন:

  • android.renderscript - এই ক্লাস প্যাকেজের APIগুলি Android 3.0 (API স্তর 11) এবং উচ্চতর চলমান ডিভাইসগুলিতে উপলব্ধ।
  • android.support.v8.renderscript - এই প্যাকেজের APIগুলি একটি সাপোর্ট লাইব্রেরির মাধ্যমে উপলব্ধ, যা আপনাকে Android 2.3 (API স্তর 9) এবং উচ্চতর চলমান ডিভাইসগুলিতে সেগুলি ব্যবহার করতে দেয়৷

এখানে ট্রেডঅফ রয়েছে:

  • আপনি যদি সাপোর্ট লাইব্রেরি API ব্যবহার করেন, তাহলে আপনার অ্যাপ্লিকেশনের রেন্ডারস্ক্রিপ্ট অংশটি Android 2.3 (API স্তর 9) এবং উচ্চতর চলমান ডিভাইসগুলির সাথে সামঞ্জস্যপূর্ণ হবে, আপনি যে রেন্ডারস্ক্রিপ্ট বৈশিষ্ট্যগুলি ব্যবহার করেন তা নির্বিশেষে৷ আপনি যদি নেটিভ ( android.renderscript ) API ব্যবহার করেন তবে এটি আপনার অ্যাপ্লিকেশনটিকে আরও বেশি ডিভাইসে কাজ করার অনুমতি দেয়৷
  • কিছু রেন্ডারস্ক্রিপ্ট বৈশিষ্ট্য সমর্থন লাইব্রেরি API-এর মাধ্যমে উপলব্ধ নয়।
  • আপনি যদি সাপোর্ট লাইব্রেরি API ব্যবহার করেন, তাহলে আপনি নেটিভ ( android.renderscript ) API ব্যবহার করার চেয়ে (সম্ভবত উল্লেখযোগ্যভাবে) বড় APK পাবেন।

রেন্ডারস্ক্রিপ্ট সাপোর্ট লাইব্রেরি এপিআই ব্যবহার করা

সাপোর্ট লাইব্রেরি রেন্ডারস্ক্রিপ্ট এপিআই ব্যবহার করার জন্য, আপনাকে অবশ্যই আপনার ডেভেলপমেন্ট এনভায়রনমেন্ট কনফিগার করতে হবে যাতে সেগুলি অ্যাক্সেস করা যায়। এই APIগুলি ব্যবহার করার জন্য নিম্নলিখিত Android SDK সরঞ্জামগুলির প্রয়োজন:

  • Android SDK টুলস রিভিশন 22.2 বা উচ্চতর
  • Android SDK বিল্ড-টুলস রিভিশন 18.1.0 বা তার বেশি

মনে রাখবেন যে Android SDK বিল্ড-টুল 24.0.0 থেকে শুরু করে, Android 2.2 (API লেভেল 8) আর সমর্থিত নয়।

আপনি Android SDK ম্যানেজারে এই টুলগুলির ইনস্টল করা সংস্করণটি পরীক্ষা করতে এবং আপডেট করতে পারেন৷

সাপোর্ট লাইব্রেরি রেন্ডারস্ক্রিপ্ট API ব্যবহার করতে:

  1. নিশ্চিত করুন যে আপনার প্রয়োজনীয় Android SDK সংস্করণ ইনস্টল করা আছে।
  2. রেন্ডারস্ক্রিপ্ট সেটিংস অন্তর্ভুক্ত করতে Android বিল্ড প্রক্রিয়ার সেটিংস আপডেট করুন:
    • আপনার অ্যাপ্লিকেশন মডিউলের অ্যাপ ফোল্ডারে build.gradle ফাইলটি খুলুন।
    • ফাইলটিতে নিম্নলিখিত রেন্ডারস্ক্রিপ্ট সেটিংস যোগ করুন:

      গ্রোভি

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

      কোটলিন

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

      উপরে তালিকাভুক্ত সেটিংস অ্যান্ড্রয়েড বিল্ড প্রক্রিয়ায় নির্দিষ্ট আচরণ নিয়ন্ত্রণ করে:

      • renderscriptTargetApi - তৈরি করা বাইটকোড সংস্করণ নির্দিষ্ট করে। আমরা সুপারিশ করি যে আপনি এই মানটিকে সর্বনিম্ন API স্তরে সেট করুন যা আপনি ব্যবহার করছেন এমন সমস্ত কার্যকারিতা প্রদান করতে সক্ষম এবং renderscriptSupportModeEnabled কে true সেট করুন। এই সেটিং এর বৈধ মান হল 11 থেকে অতি সম্প্রতি প্রকাশিত API স্তর পর্যন্ত যেকোনো পূর্ণসংখ্যার মান। আপনার অ্যাপ্লিকেশান ম্যানিফেস্টে নির্দিষ্ট করা আপনার ন্যূনতম SDK সংস্করণটি একটি ভিন্ন মান সেট করা থাকলে, সেই মানটিকে উপেক্ষা করা হয় এবং বিল্ড ফাইলের লক্ষ্য মানটি ন্যূনতম SDK সংস্করণ সেট করতে ব্যবহৃত হয়৷
      • renderscriptSupportModeEnabled - নির্দিষ্ট করে যে জেনারেট করা বাইটকোডটি একটি সামঞ্জস্যপূর্ণ সংস্করণে ফিরে আসা উচিত যদি এটি যে ডিভাইসে চলছে সেটি লক্ষ্য সংস্করণটিকে সমর্থন না করে।
  3. আপনার অ্যাপ্লিকেশন ক্লাসে যেগুলি রেন্ডারস্ক্রিপ্ট ব্যবহার করে, সমর্থন লাইব্রেরি ক্লাসগুলির জন্য একটি আমদানি যোগ করুন:

    কোটলিন

    import android.support.v8.renderscript.*
    

    জাভা

    import android.support.v8.renderscript.*;
    

জাভা বা কোটলিন কোড থেকে রেন্ডারস্ক্রিপ্ট ব্যবহার করা

Java বা Kotlin কোড থেকে RenderScript ব্যবহার করা android.renderscript বা android.support.v8.renderscript প্যাকেজে অবস্থিত API ক্লাসের উপর নির্ভর করে। বেশিরভাগ অ্যাপ্লিকেশন একই মৌলিক ব্যবহার প্যাটার্ন অনুসরণ করে:

  1. একটি রেন্ডারস্ক্রিপ্ট প্রসঙ্গ শুরু করুন। RenderScript কনটেক্সট, create(Context) দিয়ে তৈরি, নিশ্চিত করে যে RenderScript ব্যবহার করা যেতে পারে এবং পরবর্তী সমস্ত RenderScript অবজেক্টের জীবনকাল নিয়ন্ত্রণ করতে একটি অবজেক্ট প্রদান করে। আপনার প্রসঙ্গ তৈরিকে একটি সম্ভাব্য দীর্ঘমেয়াদী অপারেশন হিসাবে বিবেচনা করা উচিত, কারণ এটি হার্ডওয়্যারের বিভিন্ন অংশে সংস্থান তৈরি করতে পারে; যদি সম্ভব হয় তবে এটি একটি অ্যাপ্লিকেশনের সমালোচনামূলক পথে থাকা উচিত নয়। সাধারণত, একটি অ্যাপ্লিকেশনে একটি সময়ে শুধুমাত্র একটি রেন্ডারস্ক্রিপ্ট প্রসঙ্গ থাকবে।
  2. একটি স্ক্রিপ্টে পাস করার জন্য কমপক্ষে একটি Allocation তৈরি করুন৷ একটি Allocation একটি রেন্ডারস্ক্রিপ্ট অবজেক্ট যা একটি নির্দিষ্ট পরিমাণ ডেটার জন্য স্টোরেজ প্রদান করে। স্ক্রিপ্টের কার্নেলগুলি Allocation বস্তুগুলিকে তাদের ইনপুট এবং আউটপুট হিসাবে নেয় এবং স্ক্রিপ্ট গ্লোবাল হিসাবে আবদ্ধ থাকাকালীন rsGetElementAt_ type () এবং rsSetElementAt_ type () ব্যবহার করে Allocation বস্তুগুলি কার্নেলে অ্যাক্সেস করা যেতে পারে। Allocation অবজেক্ট অ্যারেগুলিকে জাভা কোড থেকে রেন্ডারস্ক্রিপ্ট কোডে পাস করার অনুমতি দেয় এবং এর বিপরীতে। Allocation বস্তুগুলি সাধারণত createTyped() বা createFromBitmap() ব্যবহার করে তৈরি করা হয়।
  3. প্রয়োজনীয় স্ক্রিপ্ট তৈরি করুন। রেন্ডারস্ক্রিপ্ট ব্যবহার করার সময় আপনার কাছে দুটি ধরণের স্ক্রিপ্ট উপলব্ধ রয়েছে:
    • স্ক্রিপ্টসি : উপরে রেন্ডারস্ক্রিপ্ট কার্নেল লেখায় বর্ণিত ব্যবহারকারী-সংজ্ঞায়িত স্ক্রিপ্টগুলি। প্রতিটি স্ক্রিপ্টে জাভা কোড থেকে স্ক্রিপ্ট অ্যাক্সেস করা সহজ করার জন্য রেন্ডারস্ক্রিপ্ট কম্পাইলার দ্বারা প্রতিফলিত একটি জাভা ক্লাস রয়েছে; এই শ্রেণীর নাম ScriptC_ filename নাম আছে। উদাহরণস্বরূপ, যদি উপরের ম্যাপিং কার্নেলটি invert.rs এ অবস্থিত হয় এবং একটি RenderScript প্রসঙ্গ ইতিমধ্যে mRenderScript এ অবস্থিত থাকে, তাহলে স্ক্রিপ্টটি ইনস্ট্যান্টিয়েট করার জন্য জাভা বা কোটলিন কোড হবে:

      কোটলিন

      val invert = ScriptC_invert(renderScript)
      

      জাভা

      ScriptC_invert invert = new ScriptC_invert(renderScript);
      
    • ScriptIntrinsic : এগুলি সাধারণ ক্রিয়াকলাপের জন্য অন্তর্নির্মিত রেন্ডারস্ক্রিপ্ট কার্নেল, যেমন গাউসিয়ান ব্লার, কনভোলিউশন এবং ইমেজ ব্লেন্ডিং। আরও তথ্যের জন্য, ScriptIntrinsic এর সাবক্লাসগুলি দেখুন।
  4. ডেটা দিয়ে বরাদ্দ করা। createFromBitmap() দিয়ে তৈরি করা বরাদ্দ ব্যতীত, একটি বরাদ্দ যখন প্রথম তৈরি করা হয় তখন খালি ডেটা দিয়ে তৈরি করা হয়। একটি বরাদ্দ তৈরি করতে, Allocation "কপি" পদ্ধতিগুলির একটি ব্যবহার করুন৷ "কপি" পদ্ধতিগুলি সিঙ্ক্রোনাস
  5. যেকোনো প্রয়োজনীয় স্ক্রিপ্ট গ্লোবাল সেট করুন। আপনি set_ globalname নামের একই ScriptC_ filename ক্লাসে পদ্ধতি ব্যবহার করে গ্লোবাল সেট করতে পারেন। উদাহরণস্বরূপ, threshold নামের একটি int ভেরিয়েবল সেট করতে, জাভা পদ্ধতি ব্যবহার করুন set_threshold(int) ; এবং lookup নামের একটি rs_allocation ভেরিয়েবল সেট করতে, Java মেথড set_lookup(Allocation) ব্যবহার করুন। set পদ্ধতিগুলি অ্যাসিঙ্ক্রোনাস
  6. উপযুক্ত কার্নেল এবং অনিবার্য ফাংশন চালু করুন।

    একটি প্রদত্ত কার্নেল চালু করার পদ্ধতিগুলি একই ScriptC_ filename ক্লাসে প্রতিফলিত হয় যার নাম forEach_ mappingKernelName () বা reduce_ reductionKernelName () । এই লঞ্চগুলি অ্যাসিঙ্ক্রোনাস । কার্নেলের আর্গুমেন্টের উপর নির্ভর করে, পদ্ধতিটি এক বা একাধিক বরাদ্দ নেয়, যার সবকটির অবশ্যই একই মাত্রা থাকতে হবে। ডিফল্টরূপে, একটি কার্নেল ঐ মাত্রার প্রতিটি স্থানাঙ্কের উপর কার্যকর করে; এই স্থানাঙ্কগুলির একটি উপসেটের উপর একটি কার্নেল চালানোর জন্য, একটি উপযুক্ত Script.LaunchOptions forEach বা reduce পদ্ধতির শেষ যুক্তি হিসাবে পাস করুন।

    একই ScriptC_ filename শ্রেণীতে প্রতিফলিত invoke_ functionName পদ্ধতিগুলি ব্যবহার করে ইনভোকেবল ফাংশন চালু করুন। এই লঞ্চগুলি অ্যাসিঙ্ক্রোনাস

  7. Allocation বস্তু এবং javaFutureType অবজেক্ট থেকে ডেটা পুনরুদ্ধার করুন। জাভা কোড থেকে একটি Allocation থেকে ডেটা অ্যাক্সেস করার জন্য, আপনাকে অবশ্যই Allocation "অনুলিপি" পদ্ধতিগুলির একটি ব্যবহার করে সেই ডেটা জাভাতে কপি করতে হবে। একটি হ্রাস কার্নেলের ফলাফল পেতে, আপনাকে অবশ্যই javaFutureType .get() পদ্ধতি ব্যবহার করতে হবে। "কপি" এবং get() পদ্ধতিগুলি সিঙ্ক্রোনাস
  8. রেন্ডারস্ক্রিপ্ট প্রসঙ্গ ছিঁড়ে ফেলুন। আপনি destroy() দিয়ে RenderScript প্রসঙ্গ ধ্বংস করতে পারেন অথবা RenderScript প্রসঙ্গ অবজেক্টকে আবর্জনা সংগ্রহ করার অনুমতি দিয়ে। এটি একটি ব্যতিক্রম নিক্ষেপ করতে সেই প্রসঙ্গের সাথে সম্পর্কিত যেকোন বস্তুর আরও ব্যবহার ঘটায়।

অ্যাসিঙ্ক্রোনাস এক্সিকিউশন মডেল

প্রতিফলিত forEach , invoke , reduce , এবং set পদ্ধতিগুলি অ্যাসিঙ্ক্রোনাস -- প্রতিটি অনুরোধ করা ক্রিয়া শেষ করার আগে জাভাতে ফিরে আসতে পারে৷ যাইহোক, স্বতন্ত্র ক্রিয়াগুলি সেগুলি যে ক্রমানুসারে চালু করা হয় সে অনুসারে ক্রমিক করা হয়।

Allocation শ্রেণী বরাদ্দ এবং থেকে ডেটা অনুলিপি করার জন্য "কপি" পদ্ধতি সরবরাহ করে। একটি "অনুলিপি" পদ্ধতি সিঙ্ক্রোনাস, এবং একই বরাদ্দকে স্পর্শ করে এমন উপরের যেকোনও অ্যাসিঙ্ক্রোনাস অ্যাকশনের ক্ষেত্রে ক্রমিক করা হয়।

প্রতিফলিত javaFutureType ক্লাসগুলি হ্রাসের ফলাফল পেতে একটি get() পদ্ধতি প্রদান করে। get() সিঙ্ক্রোনাস, এবং হ্রাসের সাথে সিরিয়াল করা হয় (যা অ্যাসিঙ্ক্রোনাস)।

একক-উৎস রেন্ডারস্ক্রিপ্ট

অ্যান্ড্রয়েড 7.0 (এপিআই লেভেল 24) সিঙ্গেল-সোর্স রেন্ডারস্ক্রিপ্ট নামে একটি নতুন প্রোগ্রামিং বৈশিষ্ট্য প্রবর্তন করে, যেখানে কার্নেলগুলি জাভা থেকে না হয়ে স্ক্রিপ্ট থেকে শুরু করা হয় যেখানে তারা সংজ্ঞায়িত করা হয়। এই পদ্ধতিটি বর্তমানে ম্যাপিং কার্নেলের মধ্যে সীমাবদ্ধ, যা সংক্ষিপ্ততার জন্য এই বিভাগে কেবল "কার্নেল" হিসাবে উল্লেখ করা হয়েছে। এই নতুন বৈশিষ্ট্যটি স্ক্রিপ্টের ভিতরে থেকে rs_allocation প্রকারের বরাদ্দ তৈরি করতেও সমর্থন করে। একাধিক কার্নেল লঞ্চের প্রয়োজন হলেও শুধুমাত্র একটি স্ক্রিপ্টের মধ্যে সম্পূর্ণ অ্যালগরিদম বাস্তবায়ন করা এখন সম্ভব। সুবিধাটি দ্বিগুণ: আরও পঠনযোগ্য কোড, কারণ এটি একটি ভাষায় একটি অ্যালগরিদম বাস্তবায়ন রাখে; এবং সম্ভাব্য দ্রুত কোড, কারণ একাধিক কার্নেল লঞ্চ জুড়ে জাভা এবং রেন্ডারস্ক্রিপ্টের মধ্যে কম ট্রানজিশন।

একক-উৎস রেন্ডারস্ক্রিপ্টে, আপনি একটি রেন্ডারস্ক্রিপ্ট কার্নেল লেখায় বর্ণিত কার্নেলগুলি লেখেন। তারপরে আপনি একটি অনিবার্য ফাংশন লিখুন যা তাদের চালু করতে rsForEach() কল করে। সেই API প্রথম প্যারামিটার হিসাবে একটি কার্নেল ফাংশন নেয়, তারপরে ইনপুট এবং আউটপুট বরাদ্দ করে। একটি অনুরূপ API rsForEachWithOptions() rs_script_call_t প্রকারের একটি অতিরিক্ত আর্গুমেন্ট নেয়, যা কার্নেল ফাংশন প্রক্রিয়া করার জন্য ইনপুট এবং আউটপুট বরাদ্দ থেকে উপাদানগুলির একটি উপসেট নির্দিষ্ট করে।

রেন্ডারস্ক্রিপ্ট গণনা শুরু করতে, আপনি জাভা থেকে আহ্বানযোগ্য ফাংশনকে কল করুন। জাভা কোড থেকে রেন্ডারস্ক্রিপ্ট ব্যবহার করার ধাপগুলি অনুসরণ করুন। ধাপে উপযুক্ত কার্নেল চালু করুন , invoke_ function_name () ব্যবহার করে ইনভোকেবল ফাংশনটি কল করুন, যা কার্নেল চালু করা সহ পুরো গণনা শুরু করবে।

একটি কার্নেল লঞ্চ থেকে অন্য কার্নেল লঞ্চে মধ্যবর্তী ফলাফল সংরক্ষণ এবং পাস করার জন্য প্রায়শই বরাদ্দের প্রয়োজন হয়। আপনি rsCreateAllocation() ব্যবহার করে এগুলি তৈরি করতে পারেন। সেই API-এর একটি সহজ-ব্যবহারযোগ্য ফর্ম হল rsCreateAllocation_<T><W>(…) , যেখানে T হল একটি উপাদানের ডেটা টাইপ এবং W হল উপাদানটির ভেক্টর প্রস্থ। এপিআই আর্গুমেন্ট হিসাবে X, Y, এবং Z আকারের আকার নেয়। 1D বা 2D বরাদ্দের জন্য, Y বা Z মাত্রার আকার বাদ দেওয়া যেতে পারে। উদাহরণস্বরূপ, rsCreateAllocation_uchar4(16384) 16384টি উপাদানের একটি 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);
}

আপনি জাভা বা কোটলিন থেকে process() ফাংশনটিকে নিম্নরূপ কল করতে পারেন:

কোটলিন

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)

জাভা

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

এই উদাহরণটি দেখায় কিভাবে একটি অ্যালগরিদম যাতে দুটি কার্নেল লঞ্চ জড়িত থাকে তা সম্পূর্ণরূপে রেন্ডারস্ক্রিপ্ট ভাষাতেই প্রয়োগ করা যেতে পারে। একক-উৎস রেন্ডারস্ক্রিপ্ট ছাড়া, আপনাকে জাভা কোড থেকে উভয় কার্নেল চালু করতে হবে, কার্নেল সংজ্ঞা থেকে কার্নেল লঞ্চগুলিকে আলাদা করে এবং পুরো অ্যালগরিদম বোঝা কঠিন করে তোলে। শুধুমাত্র একক-উৎস রেন্ডারস্ক্রিপ্ট কোড পড়া সহজ নয়, এটি কার্নেল লঞ্চ জুড়ে জাভা এবং স্ক্রিপ্টের মধ্যে রূপান্তরকেও দূর করে। কিছু পুনরাবৃত্তিমূলক অ্যালগরিদম শত শত বার কার্নেল চালু করতে পারে, যা এই ধরনের ট্রানজিশনের ওভারহেডকে উল্লেখযোগ্য করে তোলে।

স্ক্রিপ্ট গ্লোবাল

একটি স্ক্রিপ্ট গ্লোবাল একটি স্ক্রিপ্ট ( .rs ) ফাইলে একটি সাধারণ অ- static গ্লোবাল ভেরিয়েবল। একটি স্ক্রিপ্ট গ্লোবাল নামের var নামক ফাইল ফাইলের filename .rs এ সংজ্ঞায়িত করা হয়েছে, সেখানে একটি পদ্ধতি থাকবে get_ var ScriptC_ filename ক্লাসে প্রতিফলিত হবে। গ্লোবাল const না হলে, একটি পদ্ধতি set_ var ও থাকবে।

একটি প্রদত্ত স্ক্রিপ্ট গ্লোবালের দুটি পৃথক মান রয়েছে - একটি জাভা মান এবং একটি স্ক্রিপ্ট মান। এই মানগুলি নিম্নরূপ আচরণ করে:

  • স্ক্রিপ্টে var-এর একটি স্ট্যাটিক ইনিশিয়ালাইজার থাকলে, এটি জাভা এবং স্ক্রিপ্ট উভয় ক্ষেত্রেই var- এর প্রাথমিক মান নির্দিষ্ট করে। অন্যথায়, সেই প্রাথমিক মানটি শূন্য।
  • স্ক্রিপ্টের মধ্যে var- এর অ্যাক্সেসগুলি এর স্ক্রিপ্ট মান পড়তে এবং লিখতে পারে।
  • get_ var পদ্ধতি জাভা মান পড়ে।
  • set_ var পদ্ধতি (যদি এটি বিদ্যমান থাকে) অবিলম্বে জাভা মান লিখে, এবং স্ক্রিপ্ট মান অসিঙ্ক্রোনাসভাবে লেখে।

দ্রষ্টব্য: এর মানে হল স্ক্রিপ্টের যেকোনো স্ট্যাটিক ইনিশিয়ালাইজার ছাড়া, স্ক্রিপ্টের মধ্যে থেকে বিশ্বব্যাপী লেখা মান জাভাতে দৃশ্যমান নয়।

গভীরতা হ্রাস কার্নেল

রিডাকশন হল ডেটার সংগ্রহকে একটি একক মানের মধ্যে একত্রিত করার প্রক্রিয়া। এটি সমান্তরাল প্রোগ্রামিং-এ একটি দরকারী আদিম, নিম্নলিখিতগুলির মতো অ্যাপ্লিকেশন সহ:

  • সমস্ত ডেটার উপর যোগফল বা পণ্য গণনা করা
  • কম্পিউটিং লজিক্যাল অপারেশন ( and , or , xor ) সমস্ত ডেটার উপর
  • ডেটার মধ্যে সর্বনিম্ন বা সর্বোচ্চ মান খুঁজে বের করা
  • একটি নির্দিষ্ট মান বা ডেটার মধ্যে একটি নির্দিষ্ট মানের স্থানাঙ্কের জন্য অনুসন্ধান করা

অ্যান্ড্রয়েড 7.0 (এপিআই স্তর 24) এবং পরবর্তীতে, রেন্ডারস্ক্রিপ্ট দক্ষ ব্যবহারকারী-লিখিত হ্রাস অ্যালগরিদমগুলিকে অনুমতি দেওয়ার জন্য হ্রাস কার্নেলগুলিকে সমর্থন করে। আপনি 1, 2, বা 3 মাত্রা সহ ইনপুটগুলিতে হ্রাস কার্নেল চালু করতে পারেন।

উপরের একটি উদাহরণ একটি সহজ সংযোজন হ্রাস কার্নেল দেখায়। এখানে একটি আরও জটিল 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;
}

দ্রষ্টব্য: এখানে আরও উদাহরণ হ্রাস কার্নেল রয়েছে।

একটি রিডাকশন কার্নেল চালানোর জন্য, রেন্ডারস্ক্রিপ্ট রানটাইম এক বা একাধিক ভেরিয়েবল তৈরি করে যাকে সঞ্চয়কারী ডেটা আইটেম বলা হয় হ্রাস প্রক্রিয়ার অবস্থা ধরে রাখতে। রেন্ডারস্ক্রিপ্ট রানটাইম এমনভাবে সঞ্চয়কারী ডেটা আইটেমের সংখ্যা বাছাই করে যাতে কর্মক্ষমতা সর্বাধিক হয়। অ্যাকিউমুলেটর ডেটা আইটেমগুলির ধরন ( accumType ) কার্নেলের সঞ্চয়কারী ফাংশন দ্বারা নির্ধারিত হয় -- সেই ফাংশনের প্রথম যুক্তি হল একটি সঞ্চয়কারী ডেটা আইটেমের একটি নির্দেশক৷ ডিফল্টরূপে, প্রতিটি সঞ্চয়কারী ডেটা আইটেম শূন্য থেকে শুরু হয় (যেমন memset দ্বারা); যাইহোক, আপনি ভিন্ন কিছু করার জন্য একটি ইনিশিয়ালাইজার ফাংশন লিখতে পারেন।

উদাহরণ: অ্যাডিন্ট কার্নেলে, ইনপুট মান যোগ করার জন্য অ্যাকিউমুলেটর ডেটা আইটেম ( int টাইপ) ব্যবহার করা হয়। কোন ইনিশিয়ালাইজার ফাংশন নেই, তাই প্রতিটি সঞ্চয়কারী ডেটা আইটেম শূন্য থেকে শুরু হয়।

উদাহরণ: FindMinAndMax কার্নেলে, অ্যাকিউমুলেটর ডেটা আইটেমগুলি (টাইপ MinAndMax ) এ পর্যন্ত পাওয়া ন্যূনতম এবং সর্বাধিক মানগুলির ট্র্যাক রাখতে ব্যবহৃত হয়। এগুলিকে যথাক্রমে LONG_MAX এবং LONG_MIN এ সেট করার জন্য একটি ইনিশিয়ালাইজার ফাংশন রয়েছে; এবং এই মানগুলির অবস্থানগুলি -1 তে সেট করতে, ইঙ্গিত করে যে মানগুলি প্রক্রিয়াকৃত ইনপুটের (খালি) অংশে আসলে উপস্থিত নেই।

রেন্ডারস্ক্রিপ্ট ইনপুট(গুলি) প্রতিটি স্থানাঙ্কের জন্য একবার আপনার সঞ্চয়কারী ফাংশনকে কল করে। সাধারণত, আপনার ফাংশন ইনপুট অনুযায়ী কিছু উপায়ে সঞ্চয়কারী ডেটা আইটেম আপডেট করা উচিত।

উদাহরণ: অ্যাডিন্ট কার্নেলে, অ্যাকিউমুলেটর ফাংশন একটি ইনপুট উপাদানের মান সঞ্চয়কারী ডেটা আইটেমে যোগ করে।

উদাহরণ: FindMinAndMax কার্নেলে, সঞ্চয়কারী ফাংশন একটি ইনপুট উপাদানের মান সঞ্চয়কারী ডেটা আইটেমে রেকর্ড করা ন্যূনতম মানের থেকে কম বা সমান এবং/অথবা সঞ্চয়কারীতে রেকর্ড করা সর্বোচ্চ মানের চেয়ে বেশি বা সমান কিনা তা পরীক্ষা করে। ডেটা আইটেম, এবং সেই অনুযায়ী সঞ্চয়কারী ডেটা আইটেম আপডেট করে।

ইনপুট(গুলি) মধ্যে প্রতিটি স্থানাঙ্কের জন্য একুমুলেটর ফাংশন একবার কল করার পরে, রেন্ডারস্ক্রিপ্টকে অবশ্যই সঞ্চয়কারী ডেটা আইটেমগুলিকে একটি একক সঞ্চয়কারী ডেটা আইটেমে একত্রিত করতে হবে। আপনি এটি করতে একটি কম্বাইনার ফাংশন লিখতে পারেন। যদি সঞ্চয়কারী ফাংশনে একটি একক ইনপুট থাকে এবং কোনো বিশেষ আর্গুমেন্ট না থাকে, তাহলে আপনাকে একটি কম্বাইনার ফাংশন লিখতে হবে না; রেন্ডারস্ক্রিপ্ট অ্যাকিউমুলেটর ডেটা আইটেমগুলিকে একত্রিত করতে সঞ্চয়কারী ফাংশন ব্যবহার করবে। (যদি এই ডিফল্ট আচরণটি আপনি চান তা না হলে আপনি এখনও একটি কম্বাইনার ফাংশন লিখতে পারেন।)

উদাহরণ: অ্যাডিন্ট কার্নেলে, কোন কম্বাইনার ফাংশন নেই, তাই অ্যাকিউমুলেটর ফাংশন ব্যবহার করা হবে। এটি সঠিক আচরণ, কারণ আমরা যদি একটি মানের সংগ্রহকে দুটি ভাগে ভাগ করি, এবং আমরা সেই দুটি অংশের মানগুলিকে আলাদাভাবে যোগ করি, তাহলে সেই দুটি যোগফল সম্পূর্ণ সংগ্রহকে যোগ করার সমান।

উদাহরণ: findMinAndMax কার্নেলে, কম্বাইনার ফাংশন "সোর্স" অ্যাকুমুলেটর ডেটা আইটেমে রেকর্ড করা ন্যূনতম মান *val "গন্তব্য" সঞ্চয়কারী ডেটা আইটেমে রেকর্ড করা ন্যূনতম মানের চেয়ে কম কিনা তা পরীক্ষা করে *accum , এবং আপডেট *accum সেই অনুযায়ী এটি সর্বোচ্চ মানের জন্য অনুরূপ কাজ করে। এই আপডেটগুলি *accum এ যে অবস্থায় থাকত, যদি সমস্ত ইনপুট মান *accum এ জমা না হয়ে কিছু *accum এ এবং কিছু *val এ জমা হত।

সমস্ত সঞ্চয়কারী ডেটা আইটেম একত্রিত হওয়ার পরে, রেন্ডারস্ক্রিপ্ট জাভাতে ফিরে যাওয়ার জন্য হ্রাসের ফলাফল নির্ধারণ করে। এটি করার জন্য আপনি একটি আউটকনভার্টার ফাংশন লিখতে পারেন। আপনি যদি চান যে সম্মিলিত সঞ্চয়কারী ডেটা আইটেমগুলির চূড়ান্ত মান হ্রাসের ফলাফল হতে চান তবে আপনাকে একটি আউটকনভার্টার ফাংশন লিখতে হবে না।

উদাহরণ: অ্যাডিন্ট কার্নেলে, কোনও আউটকনভার্টার ফাংশন নেই। সম্মিলিত ডেটা আইটেমগুলির চূড়ান্ত মান হল ইনপুটের সমস্ত উপাদানের সমষ্টি, যা আমরা ফেরত দিতে চাই।

উদাহরণ: FindMinAndMax কার্নেলে, আউটকনভারটার ফাংশন একটি int2 ফলাফলের মান শুরু করে যাতে ন্যূনতম এবং সর্বাধিক মানগুলির অবস্থান ধরে রাখা যায় যা সমস্ত সঞ্চয়কারী ডেটা আইটেমগুলির সংমিশ্রণ থেকে তৈরি হয়।

একটি হ্রাস কার্নেল লেখা

#pragma rs reduce একটি রিডাকশন কার্নেলের নাম এবং কার্নেল তৈরিকারী ফাংশনের নাম ও ভূমিকা উল্লেখ করে সংজ্ঞায়িত করে। এই ধরনের সমস্ত ফাংশন static হতে হবে। একটি হ্রাস কার্নেলের সর্বদা একটি accumulator ফাংশন প্রয়োজন; আপনি কার্নেল কি করতে চান তার উপর নির্ভর করে আপনি কিছু বা অন্যান্য ফাংশন বাদ দিতে পারেন।

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

#pragma আইটেমগুলির অর্থ নিম্নরূপ:

  • reduce( kernelName ) (বাধ্যতামূলক): নির্দিষ্ট করে যে একটি হ্রাস কার্নেল সংজ্ঞায়িত করা হচ্ছে। একটি প্রতিফলিত জাভা পদ্ধতি reduce_ kernelName কার্নেল চালু করবে।
  • initializer( initializerName ) (ঐচ্ছিক): এই রিডাকশন কার্নেলের জন্য ইনিশিয়ালাইজার ফাংশনের নাম উল্লেখ করে। আপনি যখন কার্নেল চালু করেন, রেন্ডারস্ক্রিপ্ট প্রতিটি সঞ্চয়কারী ডেটা আইটেমের জন্য একবার এই ফাংশনটিকে কল করে। ফাংশন এই মত সংজ্ঞায়িত করা আবশ্যক:

    static void initializerName(accumType *accum) { … }

    accum এই ফাংশন আরম্ভ করার জন্য একটি সঞ্চয়কারী ডেটা আইটেমের একটি পয়েন্টার।

    আপনি যদি একটি ইনিশিয়ালাইজার ফাংশন প্রদান না করেন, তাহলে রেন্ডারস্ক্রিপ্ট প্রতিটি সঞ্চয়কারী ডেটা আইটেমকে শূন্যে আরম্ভ করে (যেমন 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 থেকে in N হল এক বা একাধিক আর্গুমেন্ট যা কার্নেল লঞ্চে পাস করা ইনপুটের উপর ভিত্তি করে স্বয়ংক্রিয়ভাবে পূর্ণ হয়, প্রতি ইনপুট একটি আর্গুমেন্ট। সঞ্চয়কারী ফাংশন ঐচ্ছিকভাবে যে কোনো বিশেষ আর্গুমেন্ট নিতে পারে।

    একাধিক ইনপুট সহ একটি উদাহরণ কার্নেল হল dotProduct

  • combiner( combinerName )

    (ঐচ্ছিক): এই রিডাকশন কার্নেলের জন্য কম্বাইনার ফাংশনের নাম উল্লেখ করে। RenderScript ইনপুট(গুলি) মধ্যে প্রতিটি স্থানাঙ্কের জন্য একবার সঞ্চয়কারী ফাংশনকে কল করার পরে, এটি সমস্ত সঞ্চয়কারী ডেটা আইটেমকে একটি একক সঞ্চয়কারী ডেটা আইটেমে একত্রিত করার জন্য যতবার প্রয়োজন ততবার এই ফাংশনটিকে কল করে। ফাংশন এই মত সংজ্ঞায়িত করা আবশ্যক:

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

    accum হল এই ফাংশনটি পরিবর্তন করার জন্য একটি "গন্তব্য" সঞ্চয়কারী ডেটা আইটেমের একটি পয়েন্টার। other একটি "উৎস" সঞ্চয়কারী ডেটা আইটেমের একটি নির্দেশক এই ফাংশনের জন্য *accum এ "একত্রিত" করতে।

    দ্রষ্টব্য: এটা সম্ভব যে *accum , *other , বা উভয়ই আরম্ভ করা হয়েছে কিন্তু কখনোই accumulator ফাংশনে পাস করা হয়নি; অর্থাৎ, একটি বা উভয়ই কোনো ইনপুট ডেটা অনুযায়ী আপডেট করা হয়নি। উদাহরণস্বরূপ, findMinAndMax কার্নেলে, কম্বাইনার ফাংশন fMMCombiner স্পষ্টভাবে idx < 0 পরীক্ষা করে কারণ এটি এমন একটি সঞ্চয়কারী ডেটা আইটেম নির্দেশ করে, যার মান হল INITVAL

    আপনি যদি একটি কম্বাইনার ফাংশন প্রদান না করেন, তাহলে রেন্ডারস্ক্রিপ্ট তার জায়গায় অ্যাকিউমুলেটর ফাংশন ব্যবহার করে, এমন আচরণ করে যেন একটি কম্বাইনার ফাংশন আছে যা দেখতে এইরকম:

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

    যদি কার্নেলের একাধিক ইনপুট থাকে, যদি ইনপুট ডেটা টাইপ অ্যাকিউমুলেটর ডেটা টাইপের মতো না হয়, অথবা যদি সঞ্চয়কারী ফাংশন এক বা একাধিক বিশেষ আর্গুমেন্ট নেয় তাহলে একটি কম্বিনার ফাংশন বাধ্যতামূলক।

  • outconverter( outconverterName ) (ঐচ্ছিক): এই রিডাকশন কার্নেলের জন্য outconverter ফাংশনের নাম উল্লেখ করে। রেন্ডারস্ক্রিপ্ট সমস্ত সঞ্চয়কারী ডেটা আইটেম একত্রিত করার পরে, এটি জাভাতে ফিরে যাওয়ার হ্রাসের ফলাফল নির্ধারণ করতে এই ফাংশনটিকে কল করে। ফাংশন এই মত সংজ্ঞায়িত করা আবশ্যক:

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

    result হল একটি ফলাফল ডেটা আইটেমের একটি নির্দেশক (বরাদ্দ করা হয়েছে কিন্তু রেন্ডারস্ক্রিপ্ট রানটাইম দ্বারা আরম্ভ করা হয়নি) এই ফাংশনটি হ্রাসের ফলাফলের সাথে শুরু করার জন্য। resultType হল সেই ডেটা আইটেমের ধরন, যা accumType- এর মতো হতে হবে না। accum হল কম্বাইনার ফাংশন দ্বারা গণনা করা চূড়ান্ত সঞ্চয়কারী ডেটা আইটেমের একটি পয়েন্টার।

    আপনি যদি একটি আউটকনভার্টার ফাংশন প্রদান না করেন, তাহলে রেন্ডারস্ক্রিপ্ট চূড়ান্ত সঞ্চয়কারী ডেটা আইটেমটিকে ফলাফলের ডেটা আইটেমে অনুলিপি করে, এমন আচরণ করে যেন একটি আউটকনভার্টার ফাংশন ছিল যা দেখতে এইরকম:

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

    আপনি যদি সঞ্চয়কারী ডেটা টাইপের চেয়ে ভিন্ন ফলাফলের ধরন চান, তাহলে আউটকনভার্টার ফাংশন বাধ্যতামূলক।

মনে রাখবেন যে একটি কার্নেলের ইনপুট প্রকার, একটি সঞ্চয়কারী ডেটা আইটেম টাইপ এবং একটি ফলাফলের ধরন রয়েছে, যার কোনোটিরই একই রকম হওয়ার দরকার নেই। উদাহরণস্বরূপ, findMinAndMax কার্নেলে, ইনপুট টাইপ long , অ্যাকিউমুলেটর ডেটা আইটেম টাইপ MinAndMax এবং ফলাফলের ধরন int2 সবই আলাদা।

আপনি কি অনুমান করতে পারেন না?

একটি প্রদত্ত কার্নেল লঞ্চের জন্য রেন্ডারস্ক্রিপ্ট দ্বারা তৈরি সঞ্চয়কারী ডেটা আইটেমগুলির সংখ্যার উপর আপনাকে নির্ভর করতে হবে না। কোন গ্যারান্টি নেই যে একই ইনপুট(গুলি) সহ একই কার্নেলের দুটি লঞ্চ একই সংখ্যক সঞ্চয়কারী ডেটা আইটেম তৈরি করবে।

রেন্ডারস্ক্রিপ্ট ইনিশিয়ালাইজার, অ্যাকুমুলেটর এবং কম্বাইনার ফাংশনগুলিকে যে ক্রম অনুসারে কল করে তার উপর আপনাকে নির্ভর করতে হবে না; এটি সমান্তরালভাবে তাদের কিছু কল করতে পারে। কোন গ্যারান্টি নেই যে একই ইনপুট সহ একই কার্নেলের দুটি লঞ্চ একই আদেশ অনুসরণ করবে। একমাত্র গ্যারান্টি হল যে শুধুমাত্র ইনিশিয়ালাইজার ফাংশনটি কখনও একটি অপ্রচলিত সঞ্চয়কারী ডেটা আইটেম দেখতে পাবে। যেমন:

  • কোনো গ্যারান্টি নেই যে সমস্ত সঞ্চয়কারী ডেটা আইটেমগুলি সঞ্চয়কারী ফাংশনটি কল করার আগে শুরু হবে, যদিও এটি শুধুমাত্র একটি প্রারম্ভিক সঞ্চয়কারী ডেটা আইটেমে কল করা হবে।
  • ইনপুট উপাদানগুলি সংগ্রহকারী ফাংশনে পাস করা হয় এমন অর্ডারের কোনও গ্যারান্টি নেই।
  • কম্বাইনার ফাংশনটি কল করার আগে সমস্ত ইনপুট উপাদানগুলির জন্য আহ্বানকারী ফাংশনটি কল করা হয়েছে এমন কোনও গ্যারান্টি নেই।

এর একটি পরিণতি হ'ল ফাইন্ডমিন্যান্ডম্যাক্স কার্নেলটি ডিটারমিনিস্টিক নয়: যদি ইনপুটটিতে একই ন্যূনতম বা সর্বোচ্চ মানের একাধিক ঘটনা থাকে তবে কার্নেলটি কোন ঘটনাটি খুঁজে পাবে তা জানার কোনও উপায় আপনার নেই।

আপনি কি গ্যারান্টি দিতে হবে?

যেহেতু রেন্ডারস্ক্রিপ্ট সিস্টেমটি বিভিন্ন উপায়ে একটি কার্নেল সম্পাদন করতে বেছে নিতে পারে, তাই আপনার কার্নেলটি আপনার পছন্দ মতো আচরণ করে তা নিশ্চিত করার জন্য আপনাকে অবশ্যই কিছু নিয়ম অনুসরণ করতে হবে। আপনি যদি এই নিয়মগুলি অনুসরণ না করেন তবে আপনি ভুল ফলাফল, ননডিটারমিনিস্টিক আচরণ বা রানটাইম ত্রুটি পেতে পারেন।

নীচের নিয়মগুলি প্রায়শই বলে যে দুটি সংগ্রহকারী ডেটা আইটেমের অবশ্যই " একই মান" থাকতে হবে। এর মানে কি? এটি আপনি কার্নেলটি কী করতে চান তার উপর নির্ভর করে। অ্যাডিন্টের মতো গাণিতিক হ্রাসের জন্য, এটি সাধারণত "একই" এর জন্য গাণিতিক সাম্যতার অর্থ বোঝায়। "কোনও চয়ন করুন" অনুসন্ধানের জন্য যেমন ফাইন্ডমিন্যান্ডম্যাক্স ("ন্যূনতম এবং সর্বাধিক ইনপুট মানগুলির অবস্থান সন্ধান করুন") যেখানে অভিন্ন ইনপুট মানগুলির একাধিক ঘটনা থাকতে পারে, একটি প্রদত্ত ইনপুট মানের সমস্ত অবস্থান অবশ্যই "একই" হিসাবে বিবেচিত হতে হবে "একই" হিসাবে বিবেচনা করা উচিত . আপনি " বামতম সর্বনিম্ন এবং সর্বাধিক ইনপুট মানগুলির অবস্থান সন্ধান করতে" অনুরূপ কার্নেল লিখতে পারেন যেখানে (বলুন) অবস্থান 100 এ ন্যূনতম মানকে 200 এ অভিন্ন ন্যূনতম মানের চেয়ে অগ্রাধিকার দেওয়া হয়; এই কার্নেলের জন্য, "একই" এর অর্থ হ'ল অভিন্ন অবস্থান , কেবল অভিন্ন মান নয়, এবং সংগ্রহকারী এবং কম্বাইনার ফাংশনগুলি ফাইন্ডমিন্যান্ডম্যাক্সের চেয়ে আলাদা হতে হবে।

ইনিশিয়ালাইজার ফাংশন অবশ্যই একটি পরিচয় মান তৈরি করতে হবে। এটি হ'ল I এবং A যদি ইনিশিয়ালাইজার ফাংশন দ্বারা শুরু করা ডেটা আইটেমগুলি হয় এবং I কখনই সঞ্চালক ফাংশনে পাস করি নি (তবে A হতে পারে), তারপরে
  • combinerName (& A , & I ) অবশ্যই A একই ছেড়ে যেতে হবে
  • combinerName (& I , & A ) অবশ্যই I A হিসাবে ছেড়ে যেতে হবে

উদাহরণ: অ্যাডিন্ট কার্নেলে, একটি সংগ্রহকারী ডেটা আইটেমটি শূন্যে শুরু করা হয়। এই কার্নেলের জন্য কম্বাইনার ফাংশনটি সংযোজন করে; জিরো সংযোজনের জন্য পরিচয় মান।

উদাহরণ: ফাইন্ডমিন্যান্ডম্যাক্স কার্নেলে, একটি সঞ্চালক ডেটা আইটেমটি 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

উদাহরণ: অ্যাডিন্ট কার্নেলে, কম্বাইনার ফাংশন দুটি সংগ্রহকারী ডেটা আইটেমের মান যুক্ত করে; সংযোজনটি কমিউটিভেটিভ।

উদাহরণ: ফাইন্ডমিনানডম্যাক্স কার্নেলে, 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);
    

উদাহরণ: অ্যাডিন্ট কার্নেলে, কম্বাইনার ফাংশন দুটি সংগ্রহকারী ডেটা আইটেমের মান যুক্ত করে:

  • 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
    

সংযোজন সহযোগী, এবং তাই কম্বাইনার ফাংশনটিও।

উদাহরণ: ফাইন্ডমিন্যান্ডম্যাক্স কার্নেল,

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 আরম্ভ করা হয়নি, এবং আরগস ইনপুট আর্গুমেন্ট এবং বিশেষের তালিকা একিউমুলেটর ফাংশনে একটি নির্দিষ্ট কলের জন্য যুক্তি, তারপরে নিম্নলিখিত দুটি কোড সিকোয়েন্সগুলি অবশ্যই A একই মান সেট করতে হবে:

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

উদাহরণ: অ্যাডিন্ট কার্নেলে, একটি ইনপুট মান v এর জন্য:

  • বিবৃতি 1 A += V এর সমান
  • বিবৃতি 2 B = 0 এর সমান
  • বিবৃতি 3 B += V এর সমান, যা B = V সমান
  • বিবৃতি 4 A += B এর সমান, যা A += V এর সমান

1 এবং 4 বিবৃতি একই মান A সেট করে এবং তাই এই কার্নেলটি মৌলিক ভাঁজ নিয়মটি মেনে চলে।

উদাহরণ: ফাইন্ডমিন্যান্ডম্যাক্স কার্নেলে, স্থানাঙ্ক এক্স এ ইনপুট মান V এর জন্য:

  • বিবৃতি 1 A = minmax(A, IndexedVal( V , X )) এর সমান
  • বিবৃতি 2 B = INITVAL হিসাবে একই
  • বিবৃতি 3 একই রকম
    B = minmax(B, IndexedVal(V, X))
    
    যা, কারণ বি প্রাথমিক মান, একই রকম
    B = IndexedVal(V, X)
    
  • বিবৃতি 4 হিসাবে একই
    A = minmax(A, B)
    
    যা একই রকম
    A = minmax(A, IndexedVal(V, X))
    

1 এবং 4 বিবৃতি একই মান হিসাবে সেট A এবং তাই এই কার্নেলটি মৌলিক ভাঁজ নিয়মটি মেনে চলে।

জাভা কোড থেকে হ্রাস কার্নেল কল করা

ScriptC_ filename নাম filename .rs

কোটলিন

// 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

জাভা

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

অ্যাডিন্ট কার্নেলকে কল করার কয়েকটি উদাহরণ এখানে রয়েছে:

কোটলিন

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

জাভা

ScriptC_example script = new ScriptC_example(renderScript);

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

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

পদ্ধতি 1 এর কার্নেলের সংগ্রহকারী ফাংশনে প্রতিটি ইনপুট আর্গুমেন্টের জন্য একটি ইনপুট Allocation আর্গুমেন্ট রয়েছে। রেন্ডারস্ক্রিপ্ট রানটাইম চেক করে তা নিশ্চিত করার জন্য যে সমস্ত ইনপুট বরাদ্দের একই মাত্রা রয়েছে এবং প্রতিটি ইনপুট বরাদ্দের Element প্রকারটি সংগ্রহকারী ফাংশনের প্রোটোটাইপের সংশ্লিষ্ট ইনপুট আর্গুমেন্টের সাথে মেলে। যদি এই চেকগুলির মধ্যে কোনও ব্যর্থ হয় তবে রেন্ডারস্ক্রিপ্ট একটি ব্যতিক্রম ছুঁড়ে দেয়। কার্নেল সেই মাত্রায় প্রতিটি সমন্বয়কে কার্যকর করে।

পদ্ধতি 2 পদ্ধতি 1 এর মতো একই পদ্ধতি 2 ব্যতীত একটি অতিরিক্ত আর্গুমেন্ট sc নেয় যা কার্নেল এক্সিকিউশনকে স্থানাঙ্কের একটি উপসেটে সীমাবদ্ধ করতে ব্যবহার করা যেতে পারে।

পদ্ধতি 3 পদ্ধতি 1 এর মতো একই ব্যতীত বরাদ্দ ইনপুটগুলি গ্রহণের পরিবর্তে এটি জাভা অ্যারে ইনপুট লাগে। এটি এমন একটি সুবিধা যা আপনাকে জাভা অ্যারে থেকে স্পষ্টভাবে বরাদ্দ তৈরি করতে এবং এতে ডেটা অনুলিপি করতে কোড লিখতে বাঁচায়। তবে পদ্ধতি 1 এর পরিবর্তে পদ্ধতি 3 ব্যবহার করা কোডের কার্যকারিতা বাড়ায় না । প্রতিটি ইনপুট অ্যারের জন্য, পদ্ধতি 3 উপযুক্ত Element প্রকার এবং setAutoPadding(boolean) সক্ষম করে একটি অস্থায়ী 1-মাত্রিক বরাদ্দ তৈরি করে এবং Allocation জন্য যথাযথ copyFrom() পদ্ধতি দ্বারা অ্যারেটি অনুলিপি করে। এটি তখন সেই অস্থায়ী বরাদ্দগুলি পাস করে পদ্ধতি 1 কে কল করে।

দ্রষ্টব্য: যদি আপনার অ্যাপ্লিকেশনটি একই অ্যারের সাথে একাধিক কার্নেল কল তৈরি করে, বা একই মাত্রা এবং উপাদান ধরণের বিভিন্ন অ্যারে সহ, আপনি পদ্ধতি 3 ব্যবহার না করে স্পষ্টভাবে তৈরি, পপুলেশন এবং বরাদ্দগুলি পুনরায় ব্যবহার করে কার্যকারিতা উন্নত করতে পারেন।

জাভাফুটুরাইপ , প্রতিফলিত হ্রাস পদ্ধতির রিটার্ন প্রকার, এটি ScriptC_ filename শ্রেণীর মধ্যে একটি প্রতিফলিত স্থির নেস্টেড শ্রেণি। এটি হ্রাস কার্নেল রানের ভবিষ্যতের ফলাফলের প্রতিনিধিত্ব করে। রানের আসল ফলাফল পেতে, সেই শ্রেণীর get() পদ্ধতিতে কল করুন, যা জাভারসাল্টটাইপ টাইপের মান দেয়। get() সিঙ্ক্রোনাস হয়।

কোটলিন

class ScriptC_filename(rs: RenderScript) : ScriptC(…) {
    object javaFutureType {
        fun get(): javaResultType { … }
    }
}

জাভা

public class ScriptC_filename extends ScriptC {
  public static class javaFutureType {
    public javaResultType get() { … }
  }
}

জাভারসাল্টটাইপ আউটকনভার্টার ফাংশনের ফলাফল টাইপ থেকে নির্ধারিত হয়। ফলাফল টাইপ যদি কোনও স্বাক্ষরবিহীন টাইপ (স্কেলার, ভেক্টর বা অ্যারে) না হয় তবে জাভেরেসাল্টটাইপ হ'ল সরাসরি জাভা টাইপ। যদি ফলাফল টাইপ একটি স্বাক্ষরবিহীন টাইপ হয় এবং এখানে একটি বৃহত্তর জাভা স্বাক্ষরিত প্রকার থাকে তবে জাভারেসাল্টটাইপটি হ'ল বৃহত্তর জাভা স্বাক্ষরিত প্রকার; অন্যথায়, এটি সরাসরি সংশ্লিষ্ট জাভা প্রকার। যেমন:

  • যদি ফলাফল টাইপ int , int2 , বা int[15] হয় তবে জাভারসাল্টটাইপ int , Int2 বা int[]ফলাফল টাইপের সমস্ত মান জাভেরেসাল্টপাই দ্বারা প্রতিনিধিত্ব করা যেতে পারে।
  • যদি ফলাফল টাইপটি uint , uint2 , বা uint[15] হয়, তবে জাভেরেসাল্টটাইপ long , Long2 বা long[]ফলাফল টাইপের সমস্ত মান জাভেরেসাল্টপাই দ্বারা প্রতিনিধিত্ব করা যেতে পারে।
  • যদি ফলাফল টাইপটি ulong , ulong2 বা ulong[15] হয় তবে জাভেরেসাল্টটাইপ long , Long2 বা long[]ফলাফল টাইপের কয়েকটি নির্দিষ্ট মান রয়েছে যা জাভেরেসাল্টপাই দ্বারা প্রতিনিধিত্ব করা যায় না।

জাভাফুটুরটাইপ হ'ল ভবিষ্যতের ফলাফলের ধরণ যা আউটকনভার্টার ফাংশনের ফলাফল টাইপের সাথে সম্পর্কিত।

  • যদি ফলাফল টাইপ কোনও অ্যারের ধরণ না হয় তবে জাভাফুটুরটাইপ result_ resultType
  • যদি ফলাফল টাইপ হ'ল টাইপ সদস্য টাইপের সদস্যদের সাথে দৈর্ঘ্যের গণনার একটি অ্যারে, তবে জাভাফুটুরটাইপ হ'ল resultArray Count _ memberType

যেমন:

কোটলিন

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> = …
    }
}

জাভা

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() { … }
  }
}

যদি জাভারসাল্টটাইপ কোনও অবজেক্ট টাইপ (একটি অ্যারে টাইপ সহ) হয় তবে একই উদাহরণে javaFutureType .get() প্রতিটি কল।

যদি জাভারসাল্টটাইপ টাইপ রেজাল্ট টাইপের সমস্ত মান উপস্থাপন করতে না পারে এবং একটি হ্রাস কার্নেল একটি অনির্বচনীয় মান উত্পাদন করে, তবে javaFutureType .get()

পদ্ধতি 3 এবং divecsiinxtype

ডিভেকসিআইএনএক্সটিপ হ'ল জাভা টাইপ যা অ্যাকিউমুলেটর ফাংশনের সাথে সম্পর্কিত যুক্তির ইনক্সটাইপের সাথে সম্পর্কিত। ইনক্সটাইপ যদি কোনও স্বাক্ষরবিহীন টাইপ বা ভেক্টর টাইপ না হয় তবে ডিভেকসিআইএনএক্সটিপটি সরাসরি জাভা প্রকারের সাথে সম্পর্কিত। যদি ইনক্সটাইপটি একটি স্বাক্ষরবিহীন স্কেলারের ধরণ হয়, তবে ডিভাইসিসিনেক্সটাইপ হ'ল জাভা টাইপ যা সরাসরি একই আকারের স্বাক্ষরিত স্কেলার ধরণের সাথে সম্পর্কিত। যদি ইনক্সটাইপ একটি স্বাক্ষরিত ভেক্টর প্রকার হয়, তবে ডিভেকসিআইএনএক্সটিপ হ'ল জাভা টাইপ যা সরাসরি ভেক্টর উপাদান ধরণের সাথে সম্পর্কিত। যদি ইনক্সটাইপ একটি স্বাক্ষরবিহীন ভেক্টর প্রকার হয়, তবে ডিভেকসিআইএনএক্সটিপ হ'ল জাভা টাইপ যা সরাসরি ভেক্টর উপাদানগুলির ধরণের হিসাবে একই আকারের স্বাক্ষরিত স্কেলার ধরণের সাথে সম্পর্কিত। যেমন:

  • যদি ইনক্সটাইপ int হয়, তবে ডিভেকসিনেক্সটাইপ int
  • যদি ইনক্সটাইপ int2 হয়, তবে ডিভেকসিআইএনএক্সটিপ int । অ্যারে একটি সমতল প্রতিনিধিত্ব: বরাদ্দের 2-উপাদান ভেক্টর উপাদান রয়েছে বলে এটিতে দ্বিগুণ স্কেলার উপাদান রয়েছে। Allocation কাজের copyFrom() পদ্ধতিগুলি একইভাবে।
  • যদি ইনক্সটাইপটি uint হয় তবে ডিভাইসিসআইএনএক্সটিপ int । জাভা অ্যারেতে একটি স্বাক্ষরিত মান বরাদ্দে একই বিটপ্যাটার্নের একটি স্বাক্ষরযুক্ত মান হিসাবে ব্যাখ্যা করা হয়। Allocation কাজের copyFrom() পদ্ধতিগুলি একইভাবে।
  • যদি ইনক্সটাইপ uint2 হয়, তবে ডিভাইসিসআইএনএক্সটিপ int । এটি int2 এবং uint যেভাবে পরিচালনা করা হয়েছে তার সংমিশ্রণ: অ্যারে একটি সমতল প্রতিনিধিত্ব, এবং জাভা অ্যারে স্বাক্ষরিত মানগুলি রেন্ডারস্ক্রিপ্ট স্বাক্ষরযুক্ত উপাদান মান হিসাবে ব্যাখ্যা করা হয়।

নোট করুন যে পদ্ধতি 3 এর জন্য, ইনপুট প্রকারগুলি ফলাফলের ধরণের চেয়ে আলাদাভাবে পরিচালনা করা হয়:

  • একটি স্ক্রিপ্টের ভেক্টর ইনপুট জাভা পাশে সমতল করা হয়, যেখানে কোনও স্ক্রিপ্টের ভেক্টর ফলাফল হয় না।
  • একটি স্ক্রিপ্টের স্বাক্ষরযুক্ত ইনপুটটি জাভা পাশের একই আকারের স্বাক্ষরিত ইনপুট হিসাবে প্রতিনিধিত্ব করা হয়, যেখানে একটি স্ক্রিপ্টের স্বাক্ষরযুক্ত ফলাফল জাভা পাশের ( 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];
}

অতিরিক্ত কোড নমুনা

বেসিক রেন্ডারস্ক্রিপ্ট , রেন্ডারস্ক্রিপ্টইনট্রিনিক এবং হ্যালো কম্পিউটারের নমুনাগুলি এই পৃষ্ঠায় আচ্ছাদিত এপিআইগুলির ব্যবহার আরও প্রদর্শন করে।

,

রেন্ডারস্ক্রিপ্ট হল Android-এ উচ্চ কার্যসম্পাদনে গণনামূলকভাবে নিবিড় কাজ চালানোর জন্য একটি কাঠামো। রেন্ডারস্ক্রিপ্ট প্রাথমিকভাবে ডেটা-সমান্তরাল গণনার সাথে ব্যবহারের জন্য ওরিয়েন্টেড, যদিও সিরিয়াল কাজের চাপগুলিও উপকৃত হতে পারে। রেন্ডারস্ক্রিপ্ট রানটাইম একটি ডিভাইসে উপলব্ধ প্রসেসরের কাজকে সমান্তরাল করে, যেমন মাল্টি-কোর সিপিইউ এবং জিপিইউ। এটি আপনাকে কাজের সময় নির্ধারণের পরিবর্তে অ্যালগরিদম প্রকাশের উপর ফোকাস করতে দেয়। রেন্ডারস্ক্রিপ্ট বিশেষ করে ইমেজ প্রসেসিং, কম্পিউটেশনাল ফটোগ্রাফি বা কম্পিউটার ভিশন সম্পাদনকারী অ্যাপ্লিকেশনের জন্য উপযোগী।

রেন্ডারস্ক্রিপ্ট দিয়ে শুরু করার জন্য, দুটি প্রধান ধারণা রয়েছে যা আপনার বোঝা উচিত:

  • ভাষা নিজেই উচ্চ-পারফরম্যান্স গণনা কোড লেখার জন্য একটি সি 99-উত্পন্ন ভাষা। একটি রেন্ডারস্ক্রিপ্ট লেখার কার্নেল বর্ণনা করে কীভাবে এটি গণনা কার্নেলগুলি লিখতে ব্যবহার করতে হয়।
  • নিয়ন্ত্রণ এপিআই রেন্ডারস্ক্রিপ্ট রিসোর্সগুলির আজীবন পরিচালনা এবং কার্নেল এক্সিকিউশন নিয়ন্ত্রণের জন্য ব্যবহৃত হয়। এটি তিনটি ভিন্ন ভাষায় উপলভ্য: জাভা, অ্যান্ড্রয়েড এনডিকে-তে সি ++ এবং নিজেই সি 99-উত্পন্ন কার্নেল ভাষা। জাভা কোড এবং একক-উত্স রেন্ডারস্ক্রিপ্ট থেকে রেন্ডারস্ক্রিপ্ট ব্যবহার করে যথাক্রমে প্রথম এবং তৃতীয় বিকল্পগুলি বর্ণনা করে।

একটি রেন্ডারস্ক্রিপ্ট কার্নেল লিখছেন

একটি রেন্ডারস্ক্রিপ্ট কার্নেল সাধারণত <project_root>/src/rs ডিরেক্টরিতে একটি .rs ফাইলে থাকে; প্রতিটি .rs ফাইলকে স্ক্রিপ্ট বলা হয়। প্রতিটি স্ক্রিপ্টে কার্নেল, ফাংশন এবং ভেরিয়েবলগুলির নিজস্ব সেট থাকে। একটি স্ক্রিপ্ট থাকতে পারে:

  • একটি প্রাগমা ঘোষণা ( #pragma version(1) ) যা এই স্ক্রিপ্টে ব্যবহৃত রেন্ডারস্ক্রিপ্ট কার্নেল ভাষার সংস্করণ ঘোষণা করে। বর্তমানে, 1 হ'ল একমাত্র বৈধ মান।
  • একটি প্রাগমা ঘোষণা ( #pragma rs java_package_name(com.example.app) ) যা এই স্ক্রিপ্ট থেকে প্রতিফলিত জাভা ক্লাসগুলির প্যাকেজের নাম ঘোষণা করে। নোট করুন যে আপনার .rs ফাইলটি অবশ্যই আপনার অ্যাপ্লিকেশন প্যাকেজের অংশ হতে হবে, এবং কোনও গ্রন্থাগার প্রকল্পে নয়।
  • শূন্য বা আরও বেশি অজানা ফাংশন । একটি ইনভোকেবল ফাংশন হ'ল একটি একক-থ্রেডেড রেন্ডারস্ক্রিপ্ট ফাংশন যা আপনি স্বেচ্ছাসেবী যুক্তি সহ আপনার জাভা কোড থেকে কল করতে পারেন। এগুলি প্রায়শই বৃহত্তর প্রসেসিং পাইপলাইনের মধ্যে প্রাথমিক সেটআপ বা সিরিয়াল গণনার জন্য দরকারী।
  • শূন্য বা আরও স্ক্রিপ্ট গ্লোবাল । একটি স্ক্রিপ্ট গ্লোবাল সি এর গ্লোবাল ভেরিয়েবলের অনুরূপ আপনি জাভা কোড থেকে স্ক্রিপ্ট গ্লোবালগুলি অ্যাক্সেস করতে পারেন এবং এগুলি প্রায়শই রেন্ডারস্ক্রিপ্ট কার্নেলগুলিতে পাস করা প্যারামিটারের জন্য ব্যবহৃত হয়। স্ক্রিপ্ট গ্লোবালগুলি এখানে আরও বিশদে ব্যাখ্যা করা হয়েছে।

  • শূন্য বা আরও বেশি গণনা কার্নেল । একটি গণনা কার্নেল হ'ল একটি ফাংশন বা সংগ্রহ যা আপনি রেন্ডারস্ক্রিপ্ট রানটাইমকে ডেটা সংগ্রহের সমান্তরালে সম্পাদন করতে নির্দেশ করতে পারেন। দুটি ধরণের গণনা কার্নেল রয়েছে: ম্যাপিং কার্নেলগুলি ( ফোরচ কার্নেলও বলা হয়) এবং হ্রাস কার্নেলগুলি।

    একটি ম্যাপিং কার্নেল একটি সমান্তরাল ফাংশন যা একই মাত্রার 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;
      }

      বেশিরভাগ ক্ষেত্রে, এটি একটি স্ট্যান্ডার্ড সি ফাংশনের সাথে সমান। ফাংশন প্রোটোটাইপে প্রয়োগ করা RS_KERNEL সম্পত্তিটি নির্দিষ্ট করে যে ফাংশনটি একটি ইনভোকেবল ফাংশনের পরিবর্তে একটি রেন্ডারস্ক্রিপ্ট ম্যাপিং কার্নেল। কার্নেল লঞ্চে পাস করা ইনপুট Allocation উপর ভিত্তি করে স্বয়ংক্রিয়ভাবে in পূরণ করা হয়। x এবং y যুক্তিগুলি নীচে আলোচনা করা হয়েছে। কার্নেল থেকে ফিরে আসা মানটি স্বয়ংক্রিয়ভাবে আউটপুট Allocation উপযুক্ত স্থানে লেখা হয়। ডিফল্টরূপে, এই কার্নেলটি তার পুরো ইনপুট Allocation জুড়ে চালিত হয়, Allocation Element প্রতি কার্নেল ফাংশনটির একটি সম্পাদন করে।

      একটি ম্যাপিং কার্নেলের এক বা একাধিক ইনপুট Allocations , একটি একক আউটপুট Allocation বা উভয়ই থাকতে পারে। সমস্ত ইনপুট এবং আউটপুট বরাদ্দের একই মাত্রা রয়েছে এবং ইনপুট এবং আউটপুট বরাদ্দের Element প্রকারগুলি কার্নেলের প্রোটোটাইপের সাথে মেলে; যদি এই চেকগুলির কোনওটি ব্যর্থ হয় তবে রেন্ডারস্ক্রিপ্টটি একটি ব্যতিক্রম ছুঁড়ে দেয়।

      দ্রষ্টব্য: অ্যান্ড্রয়েড 6.0 (এপিআই স্তর 23) এর আগে, একটি ম্যাপিং কার্নেলের একাধিক ইনপুট Allocation নাও থাকতে পারে।

      আপনার যদি কার্নেলের চেয়ে আরও বেশি ইনপুট বা আউটপুট Allocations প্রয়োজন হয় তবে সেই অবজেক্টগুলি rs_allocation স্ক্রিপ্ট গ্লোবালগুলির সাথে আবদ্ধ হওয়া উচিত এবং rsGetElementAt_ type () বা rsSetElementAt_ type () এর মাধ্যমে কার্নেল বা ইনভোকেবল ফাংশন থেকে অ্যাক্সেস করা উচিত।

      দ্রষ্টব্য: RS_KERNEL হ'ল আপনার সুবিধার জন্য রেন্ডারস্ক্রিপ্ট দ্বারা স্বয়ংক্রিয়ভাবে সংজ্ঞায়িত একটি ম্যাক্রো:

      #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 ভিত্তিতে স্বয়ংক্রিয়ভাবে পূরণ করা হয়। অ্যাকিউমুলেটর ডেটা আইটেমটি রেন্ডারস্ক্রিপ্ট রানটাইম দ্বারা তৈরি করা হয়; ডিফল্টরূপে, এটি শূন্যে শুরু করা হয়। ডিফল্টরূপে, এই কার্নেলটি তার পুরো ইনপুট Allocation জুড়ে চালিত হয়, Allocation Element প্রতি একুমুলেটর ফাংশনের একটি সম্পাদন করে। ডিফল্টরূপে, সঞ্চয়ের ডেটা আইটেমের চূড়ান্ত মান হ্রাসের ফলাফল হিসাবে বিবেচনা করা হয় এবং জাভাতে ফিরে আসে। রেন্ডারস্ক্রিপ্ট রানটাইম চেক করে তা নিশ্চিত করতে যে ইনপুট বরাদ্দের Element সঞ্চয়ের ফাংশনের প্রোটোটাইপের সাথে মেলে; যদি এটি মেলে না, রেন্ডারস্ক্রিপ্ট একটি ব্যতিক্রম ছুঁড়ে দেয়।

      একটি হ্রাস কার্নেলের এক বা একাধিক ইনপুট Allocations রয়েছে তবে কোনও আউটপুট Allocations নেই।

      হ্রাস কার্নেলগুলি এখানে আরও বিস্তারিতভাবে ব্যাখ্যা করা হয়েছে।

      হ্রাস কার্নেলগুলি অ্যান্ড্রয়েড 7.0 (এপিআই স্তর 24) এবং পরে সমর্থিত।

    একটি ম্যাপিং কার্নেল ফাংশন বা একটি হ্রাস কার্নেল অ্যাকিউমুলেটর ফাংশন x , y এবং z বিশেষ আর্গুমেন্টগুলি ব্যবহার করে বর্তমান সম্পাদনের স্থানাঙ্কগুলিতে অ্যাক্সেস করতে পারে, যা অবশ্যই টাইপ int বা uint32_t হতে হবে। এই আর্গুমেন্ট ঐচ্ছিক.

    একটি ম্যাপিং কার্নেল ফাংশন বা একটি হ্রাস কার্নেল অ্যাকিউমুলেটর ফাংশন আরএস_কার্নেল_কন্টেক্সট টাইপের al চ্ছিক বিশেষ যুক্তি context নিতে পারে। এটি রানটাইম এপিআইয়ের একটি পরিবার দ্বারা প্রয়োজন যা বর্তমান সম্পাদনের নির্দিষ্ট বৈশিষ্ট্যগুলি জিজ্ঞাসা করতে ব্যবহৃত হয় - উদাহরণস্বরূপ, আরএসজিইটিডিমেক্স । ( context যুক্তি অ্যান্ড্রয়েড 6.0 (এপিআই স্তর 23) এবং পরে পাওয়া যায়))

  • একটি al চ্ছিক init() ফাংশন। init() ফাংশনটি একটি বিশেষ ধরণের ইনভোকেবল ফাংশন যা স্ক্রিপ্টটি প্রথম তাত্ক্ষণিক হয়ে গেলে রেন্ডারস্ক্রিপ্ট চালায়। এটি স্ক্রিপ্ট তৈরিতে কিছু গণনা স্বয়ংক্রিয়ভাবে ঘটতে দেয়।
  • শূন্য বা আরও স্ট্যাটিক স্ক্রিপ্ট গ্লোবাল এবং ফাংশন । একটি স্ট্যাটিক স্ক্রিপ্ট গ্লোবাল জাভা কোড থেকে অ্যাক্সেস করা যায় না তা ব্যতীত একটি স্ক্রিপ্ট গ্লোবালের সমতুল্য। একটি স্ট্যাটিক ফাংশন একটি স্ট্যান্ডার্ড সি ফাংশন যা স্ক্রিপ্টে যে কোনও কার্নেল বা ইনভোকেবল ফাংশন থেকে কল করা যেতে পারে তবে জাভা এপিআইয়ের সংস্পর্শে আসে না। যদি কোনও স্ক্রিপ্ট গ্লোবাল বা ফাংশন জাভা কোড থেকে অ্যাক্সেস করার প্রয়োজন না হয় তবে এটি static ঘোষণা করার জন্য অত্যন্ত সুপারিশ করা হয়।

ভাসমান বিন্দু নির্ভুলতা সেট করা

আপনি কোনও স্ক্রিপ্টে ভাসমান পয়েন্টের নির্ভুলতার প্রয়োজনীয় স্তরটি নিয়ন্ত্রণ করতে পারেন। এটি কার্যকর যদি সম্পূর্ণ আইইইই 754-2008 স্ট্যান্ডার্ড (ডিফল্টরূপে ব্যবহৃত হয়) প্রয়োজন হয় না। নিম্নলিখিত প্রাগমাসগুলি ভাসমান পয়েন্টের যথার্থতার একটি পৃথক স্তর সেট করতে পারে:

  • #pragma rs_fp_full (যদি কিছু নির্দিষ্ট না করা থাকে তবে ডিফল্ট): আইইইই 754-2008 স্ট্যান্ডার্ড দ্বারা বর্ণিত হিসাবে ভাসমান পয়েন্টের নির্ভুলতা প্রয়োজন এমন অ্যাপ্লিকেশনগুলির জন্য।
  • #pragma rs_fp_relaxed : যে অ্যাপ্লিকেশনগুলির জন্য কঠোর আইইইই 754-2008 সম্মতি প্রয়োজন হয় না এবং কম নির্ভুলতা সহ্য করতে পারে এমন অ্যাপ্লিকেশনগুলির জন্য। এই মোডটি ডেনর্মস এবং রাউন্ড-টাওয়ার্ডস-জিরোর জন্য ফ্লাশ-টু-শূন্য সক্ষম করে।
  • #pragma rs_fp_imprecise : এমন অ্যাপ্লিকেশনগুলির জন্য যার কঠোর নির্ভুলতার প্রয়োজনীয়তা নেই। এই মোডটি নিম্নলিখিতগুলির সাথে rs_fp_relaxed এ সমস্ত কিছু সক্ষম করে:
    • -0.0 এর ফলে অপারেশনগুলি পরিবর্তে +0.0 ফিরে আসতে পারে।
    • আইএনএফ এবং এনএএন -তে অপারেশনগুলি অপরিজ্ঞাত।

বেশিরভাগ অ্যাপ্লিকেশনগুলি কোনও পার্শ্ব প্রতিক্রিয়া ছাড়াই rs_fp_relaxed ব্যবহার করতে পারে। কেবলমাত্র শিথিল নির্ভুলতার সাথে (যেমন সিমডি সিপিইউ নির্দেশাবলী) উপলভ্য অতিরিক্ত অপ্টিমাইজেশনের কারণে এটি কিছু আর্কিটেকচারে খুব উপকারী হতে পারে।

জাভা থেকে রেন্ডারস্ক্রিপ্ট এপিআই অ্যাক্সেস করা

রেন্ডারস্ক্রিপ্ট ব্যবহার করে এমন একটি অ্যান্ড্রয়েড অ্যাপ্লিকেশন বিকাশ করার সময়, আপনি জাভা থেকে এর এপিআই দুটি উপায়ে একটিতে অ্যাক্সেস করতে পারেন:

  • android.renderscript - এই ক্লাস প্যাকেজের এপিআইগুলি অ্যান্ড্রয়েড 3.0 (এপিআই স্তর 11) এবং উচ্চতর ডিভাইসগুলিতে উপলব্ধ।
  • android.support.v8.renderscript - এই প্যাকেজের এপিআইগুলি একটি সমর্থন লাইব্রেরির মাধ্যমে উপলব্ধ, যা আপনাকে অ্যান্ড্রয়েড 2.3 (এপিআই স্তর 9) এবং আরও উচ্চতর ডিভাইসে ব্যবহার করতে দেয়।

এখানে ট্রেড অফস:

  • আপনি যদি সমর্থন লাইব্রেরি এপিআই ব্যবহার করেন তবে আপনার অ্যাপ্লিকেশনটির রেন্ডারস্ক্রিপ্ট অংশটি অ্যান্ড্রয়েড ২.৩ (এপিআই স্তর 9) এবং উচ্চতর ডিভাইসগুলির সাথে সামঞ্জস্যপূর্ণ হবে, আপনি যে রেন্ডারস্ক্রিপ্ট বৈশিষ্ট্যগুলি ব্যবহার করেন তা নির্বিশেষে। আপনি যদি নেটিভ ( android.renderscript ) এপিআই ব্যবহার করেন তবে এটি আপনার অ্যাপ্লিকেশনটিকে আরও ডিভাইসে কাজ করার অনুমতি দেয়।
  • সাপোর্ট লাইব্রেরি এপিআইয়ের মাধ্যমে নির্দিষ্ট রেন্ডারস্ক্রিপ্ট বৈশিষ্ট্যগুলি পাওয়া যায় না।
  • আপনি যদি সাপোর্ট লাইব্রেরি এপিআই ব্যবহার করেন তবে আপনি যদি নেটিভ ( android.renderscript ) এপিআই ব্যবহার করেন তবে আপনি (সম্ভবত উল্লেখযোগ্যভাবে) বৃহত্তর এপিকে পাবেন।

রেন্ডারস্ক্রিপ্ট সমর্থন লাইব্রেরি এপিআই ব্যবহার করে

সাপোর্ট লাইব্রেরি রেন্ডারস্ক্রিপ্ট এপিআইগুলি ব্যবহার করার জন্য, আপনাকে অবশ্যই তাদের অ্যাক্সেস করতে সক্ষম হতে আপনার বিকাশের পরিবেশটি কনফিগার করতে হবে। এই এপিআইগুলি ব্যবহারের জন্য নিম্নলিখিত অ্যান্ড্রয়েড এসডিকে সরঞ্জামগুলি প্রয়োজনীয়:

  • অ্যান্ড্রয়েড এসডিকে সরঞ্জামগুলি পুনর্বিবেচনা 22.2 বা তার বেশি
  • অ্যান্ড্রয়েড এসডিকে বিল্ড-টুলস রিভিশন 18.1.0 বা তার বেশি

নোট করুন যে অ্যান্ড্রয়েড এসডিকে বিল্ড-সরঞ্জামগুলি 24.0.0 থেকে শুরু করে অ্যান্ড্রয়েড 2.2 (এপিআই স্তর 8) আর সমর্থিত নয়।

আপনি অ্যান্ড্রয়েড এসডিকে ম্যানেজারে এই সরঞ্জামগুলির ইনস্টল করা সংস্করণটি চেক এবং আপডেট করতে পারেন।

সাপোর্ট লাইব্রেরি রেন্ডারস্ক্রিপ্ট এপিআই ব্যবহার করতে:

  1. আপনার প্রয়োজনীয় অ্যান্ড্রয়েড এসডিকে সংস্করণ ইনস্টল করা আছে তা নিশ্চিত করুন।
  2. রেন্ডারস্ক্রিপ্ট সেটিংস অন্তর্ভুক্ত করতে অ্যান্ড্রয়েড বিল্ড প্রক্রিয়াটির জন্য সেটিংস আপডেট করুন:
    • আপনার অ্যাপ্লিকেশন মডিউলটির অ্যাপ্লিকেশন ফোল্ডারে build.gradle ফাইলটি খুলুন।
    • ফাইলটিতে নিম্নলিখিত রেন্ডারস্ক্রিপ্ট সেটিংস যুক্ত করুন:

      গ্রোভি

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

      কোটলিন

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

      উপরের তালিকাভুক্ত সেটিংস অ্যান্ড্রয়েড বিল্ড প্রক্রিয়াতে নির্দিষ্ট আচরণ নিয়ন্ত্রণ করুন:

      • renderscriptTargetApi - উত্পন্ন করার জন্য বাইটকোড সংস্করণ নির্দিষ্ট করে। আমরা আপনাকে এই মানটি সর্বনিম্ন এপিআই স্তরে সেট করার পরামর্শ দিচ্ছি যা আপনি ব্যবহার করছেন এমন সমস্ত কার্যকারিতা সরবরাহ করতে সক্ষম এবং renderscriptSupportModeEnabled true হিসাবে সেট করুন। এই সেটিংয়ের বৈধ মানগুলি 11 থেকে সর্বাধিক প্রকাশিত এপিআই স্তর পর্যন্ত কোনও পূর্ণসংখ্যার মান। যদি আপনার অ্যাপ্লিকেশন ম্যানিফেস্টে নির্দিষ্ট করা আপনার ন্যূনতম এসডিকে সংস্করণটি অন্য কোনও মানতে সেট করা থাকে তবে সেই মানটিকে উপেক্ষা করা হয় এবং বিল্ড ফাইলের লক্ষ্য মানটি ন্যূনতম এসডিকে সংস্করণ সেট করতে ব্যবহৃত হয়।
      • renderscriptSupportModeEnabled - নির্দিষ্ট করে যে উত্পন্ন বাইটকোডটি যদি এটি চলছে এমন ডিভাইসটি লক্ষ্য সংস্করণটিকে সমর্থন না করে তবে একটি সামঞ্জস্যপূর্ণ সংস্করণে ফিরে যেতে হবে।
  3. আপনার অ্যাপ্লিকেশন ক্লাসে যা রেন্ডারস্ক্রিপ্ট ব্যবহার করে, সমর্থন লাইব্রেরি ক্লাসগুলির জন্য একটি আমদানি যুক্ত করুন:

    কোটলিন

    import android.support.v8.renderscript.*
    

    জাভা

    import android.support.v8.renderscript.*;
    

জাভা বা কোটলিন কোড থেকে রেন্ডারস্ক্রিপ্ট ব্যবহার করে

জাভা বা কোটলিন কোড থেকে রেন্ডারস্ক্রিপ্ট ব্যবহার করে android.renderscript বা android.support.v8.renderscript রেন্ডারস্ক্রিপ্ট প্যাকেজে অবস্থিত এপিআই ক্লাসগুলির উপর নির্ভর করে। বেশিরভাগ অ্যাপ্লিকেশন একই প্রাথমিক ব্যবহারের ধরণ অনুসরণ করে:

  1. একটি রেন্ডারস্ক্রিপ্ট প্রসঙ্গ শুরু করুন। create(Context) দিয়ে তৈরি RenderScript প্রসঙ্গটি নিশ্চিত করে যে রেন্ডারস্ক্রিপ্ট ব্যবহার করা যেতে পারে এবং পরবর্তী সমস্ত রেন্ডারস্ক্রিপ্ট অবজেক্টগুলির জীবনকাল নিয়ন্ত্রণ করতে একটি বস্তু সরবরাহ করে। আপনার প্রসঙ্গ সৃষ্টিকে একটি সম্ভাব্য দীর্ঘ-চলমান অপারেশন হিসাবে বিবেচনা করা উচিত, যেহেতু এটি হার্ডওয়্যার বিভিন্ন টুকরোতে সংস্থান তৈরি করতে পারে; যদি সম্ভব হয় তবে এটি কোনও অ্যাপ্লিকেশনটির সমালোচনামূলক পথে হওয়া উচিত নয়। সাধারণত, একটি অ্যাপ্লিকেশনটিতে একবারে কেবল একটি একক রেন্ডারস্ক্রিপ্ট প্রসঙ্গ থাকবে।
  2. কোনও স্ক্রিপ্টে পাস করার জন্য কমপক্ষে একটি Allocation তৈরি করুন। Allocation হ'ল একটি রেন্ডারস্ক্রিপ্ট অবজেক্ট যা নির্দিষ্ট পরিমাণের ডেটার জন্য স্টোরেজ সরবরাহ করে। স্ক্রিপ্টগুলিতে কার্নেলগুলি তাদের ইনপুট এবং আউটপুট হিসাবে Allocation অবজেক্টগুলি গ্রহণ করে এবং স্ক্রিপ্ট গ্লোবাল হিসাবে আবদ্ধ হলে rsGetElementAt_ type () এবং rsSetElementAt_ type () ব্যবহার করে কার্নেলগুলিতে Allocation অবজেক্টগুলি অ্যাক্সেস করা যায়। Allocation অবজেক্টগুলি জাভা কোড থেকে রেন্ডারস্ক্রিপ্ট কোড এবং তদ্বিপরীতগুলিতে অ্যারেগুলি পাস করার অনুমতি দেয়। Allocation অবজেক্টগুলি সাধারণত createTyped() বা createFromBitmap() ব্যবহার করে তৈরি করা হয়।
  3. স্ক্রিপ্টগুলি যা প্রয়োজন তা তৈরি করুন। রেন্ডারস্ক্রিপ্ট ব্যবহার করার সময় আপনার কাছে দুটি ধরণের স্ক্রিপ্ট উপলব্ধ রয়েছে:
    • স্ক্রিপ্টসি : উপরে একটি রেন্ডারস্ক্রিপ্ট কার্নেল লেখায় বর্ণিত হিসাবে এগুলি ব্যবহারকারী-সংজ্ঞায়িত স্ক্রিপ্টগুলি। প্রতিটি স্ক্রিপ্টে জাভা কোড থেকে স্ক্রিপ্টটি অ্যাক্সেস করা সহজ করার জন্য রেন্ডারস্ক্রিপ্ট সংকলক দ্বারা প্রতিফলিত একটি জাভা ক্লাস রয়েছে; এই শ্রেণীর নাম ScriptC_ filename নাম রয়েছে। উদাহরণস্বরূপ, যদি উপরের ম্যাপিং কার্নেলটি invert.rs অবস্থিত থাকে এবং একটি রেন্ডারস্ক্রিপ্ট প্রসঙ্গটি ইতিমধ্যে mRenderScript অবস্থিত ছিল, স্ক্রিপ্টটি ইনস্ট্যান্ট করার জন্য জাভা বা কোটলিন কোডটি হবে:

      কোটলিন

      val invert = ScriptC_invert(renderScript)
      

      জাভা

      ScriptC_invert invert = new ScriptC_invert(renderScript);
      
    • স্ক্রিপটিন্ট্রিনসিক : এগুলি সাধারণ ক্রিয়াকলাপগুলির জন্য অন্তর্নির্মিত রেন্ডারস্ক্রিপ্ট কার্নেলগুলি যেমন গাউসিয়ান ব্লার, কনভোলশন এবং চিত্রের মিশ্রণ। আরও তথ্যের জন্য, ScriptIntrinsic সাবক্লাসগুলি দেখুন।
  4. ডেটা সহ বরাদ্দ পপুলেট করুন। createFromBitmap() দিয়ে তৈরি বরাদ্দ ব্যতীত, এটি প্রথম তৈরি হওয়ার পরে খালি ডেটা দিয়ে একটি বরাদ্দ পপুলেশন করা হয়। একটি বরাদ্দ পপুলেশন করতে, Allocation একটি "অনুলিপি" পদ্ধতি ব্যবহার করুন। "অনুলিপি" পদ্ধতিগুলি সিঙ্ক্রোনাস
  5. কোনও প্রয়োজনীয় স্ক্রিপ্ট গ্লোবাল সেট করুন। আপনি একই ScriptC_ filename শ্রেণিতে set_ globalname নামক পদ্ধতি ব্যবহার করে গ্লোবাল সেট করতে পারেন। উদাহরণস্বরূপ, threshold নামে একটি int ভেরিয়েবল সেট করার জন্য, জাভা পদ্ধতি set_threshold(int) ব্যবহার করুন; এবং lookup নামক একটি rs_allocation ভেরিয়েবল সেট করার জন্য, জাভা পদ্ধতি set_lookup(Allocation) ব্যবহার করুন। set পদ্ধতিগুলি অ্যাসিনক্রোনাস
  6. উপযুক্ত কার্নেল এবং ইনভোকেবল ফাংশন চালু করুন।

    প্রদত্ত কার্নেল চালু করার পদ্ধতিগুলি একই ScriptC_ filename শ্রেণিতে প্রতিফলিত হয় forEach_ mappingKernelName () বা reduce_ reductionKernelName () নামক পদ্ধতি সহ। এই লঞ্চগুলি অ্যাসিনক্রোনাস । কার্নেলের যুক্তিগুলির উপর নির্ভর করে, পদ্ধতিটি এক বা একাধিক বরাদ্দ নেয়, যার সবগুলির মধ্যে অবশ্যই একই মাত্রা থাকতে হবে। ডিফল্টরূপে, একটি কার্নেল সেই মাত্রায় প্রতিটি সমন্বয়কে কার্যকর করে; এই স্থানাঙ্কগুলির একটি উপসেটের উপরে একটি কার্নেল forEach করতে, reduce উপযুক্ত Script.LaunchOptions পাস করুন la

    একই ScriptC_ filename শ্রেণিতে প্রতিফলিত invoke_ functionName পদ্ধতিগুলি ব্যবহার করে ইনভোকেবল ফাংশনগুলি চালু করুন। এই লঞ্চগুলি অ্যাসিনক্রোনাস

  7. Allocation অবজেক্ট এবং জাভাফুটুরেটিপ অবজেক্টগুলি থেকে ডেটা পুনরুদ্ধার করুন। জাভা কোড থেকে Allocation থেকে ডেটা অ্যাক্সেস করার জন্য, আপনাকে Allocation "অনুলিপি" পদ্ধতিগুলির মধ্যে একটি ব্যবহার করে সেই ডেটা জাভাতে ফিরে অনুলিপি করতে হবে। হ্রাস কার্নেলের ফলাফল পেতে, আপনাকে অবশ্যই javaFutureType .get() পদ্ধতিটি ব্যবহার করতে হবে। "অনুলিপি" এবং get() পদ্ধতিগুলি সিঙ্ক্রোনাস
  8. রেন্ডারস্ক্রিপ্ট প্রসঙ্গটি ছিঁড়ে ফেলুন। আপনি destroy() দিয়ে রেন্ডারস্ক্রিপ্ট প্রসঙ্গটি ধ্বংস করতে পারেন বা রেন্ডারস্ক্রিপ্ট প্রসঙ্গে অবজেক্টটি আবর্জনা সংগ্রহ করার অনুমতি দিয়ে। এটি ব্যতিক্রম ছুঁড়ে ফেলার জন্য সেই প্রসঙ্গে থাকা কোনও অবজেক্টের আরও কোনও ব্যবহারের কারণ করে।

অ্যাসিঙ্ক্রোনাস এক্সিকিউশন মডেল

প্রতিফলিত forEach , invoke , reduce এবং set পদ্ধতিগুলি অ্যাসিঙ্ক্রোনাস - প্রত্যেকে অনুরোধ করা ক্রিয়াটি শেষ করার আগে জাভাতে ফিরে আসতে পারে। যাইহোক, পৃথক ক্রিয়াগুলি যে ক্রমে সেগুলি চালু করা হয়েছে তাতে সিরিয়ালাইজ করা হয়।

Allocation শ্রেণিটি বরাদ্দগুলিতে এবং থেকে ডেটা অনুলিপি করার জন্য "অনুলিপি" পদ্ধতি সরবরাহ করে। একটি "অনুলিপি" পদ্ধতিটি সিঙ্ক্রোনাস এবং একই বরাদ্দকে স্পর্শ করে এমন কোনও অ্যাসিঙ্ক্রোনাস ক্রিয়াকলাপের ক্ষেত্রে সিরিয়ালাইজ করা হয়।

প্রতিফলিত জাভাফুটুরাইপ ক্লাসগুলি হ্রাসের ফলাফল পেতে একটি get() পদ্ধতি সরবরাহ করে। get() সিঙ্ক্রোনাস, এবং হ্রাস (যা অ্যাসিঙ্ক্রোনাস) এর সাথে শ্রদ্ধার সাথে সিরিয়ালাইজ করা হয়।

একক উত্স রেন্ডারস্ক্রিপ্ট

অ্যান্ড্রয়েড .0.০ (এপিআই স্তর 24) সিঙ্গেল-সোর্স রেন্ডারস্ক্রিপ্ট নামে একটি নতুন প্রোগ্রামিং বৈশিষ্ট্য প্রবর্তন করেছে, যেখানে জাভা থেকে পরিবর্তে স্ক্রিপ্ট যেখানে তারা সংজ্ঞায়িত করা হয়েছে সেখানে কার্নেলগুলি চালু করা হয়েছে। এই পদ্ধতিটি বর্তমানে ম্যাপিং কার্নেলগুলির মধ্যে সীমাবদ্ধ, যা সংক্ষিপ্ততার জন্য এই বিভাগে কেবল "কার্নেল" হিসাবে উল্লেখ করা হয়। এই নতুন বৈশিষ্ট্যটি স্ক্রিপ্টের ভিতরে থেকে টাইপ rs_allocation বরাদ্দ তৈরি করতেও সমর্থন করে। একাধিক কার্নেল লঞ্চের প্রয়োজন হলেও কেবল একটি স্ক্রিপ্টের মধ্যে পুরো অ্যালগরিদম প্রয়োগ করা এখন সম্ভব। সুবিধাটি দ্বিগুণ: আরও পঠনযোগ্য কোড, কারণ এটি একটি ভাষায় একটি অ্যালগরিদমের বাস্তবায়ন রাখে; এবং একাধিক কার্নেল লঞ্চগুলিতে জাভা এবং রেন্ডারস্ক্রিপ্টের মধ্যে কম সংক্রমণের কারণে এবং সম্ভাব্য দ্রুত কোড।

একক-উত্স রেন্ডারস্ক্রিপ্টে, আপনি একটি রেন্ডারস্ক্রিপ্ট কার্নেল লেখায় বর্ণিত কার্নেলগুলি লিখেছেন। তারপরে আপনি এমন একটি আগমনযোগ্য ফাংশন লিখুন যা সেগুলি চালু করতে rsForEach() কল করে। সেই এপিআই প্রথম প্যারামিটার হিসাবে একটি কার্নেল ফাংশন নেয়, তারপরে ইনপুট এবং আউটপুট বরাদ্দের পরে। অনুরূপ একটি এপিআই rsForEachWithOptions() rs_script_call_t টাইপের একটি অতিরিক্ত আর্গুমেন্ট গ্রহণ করে, যা কার্নেল ফাংশনটি প্রক্রিয়া করার জন্য ইনপুট এবং আউটপুট বরাদ্দ থেকে উপাদানগুলির একটি উপসেট নির্দিষ্ট করে।

রেন্ডারস্ক্রিপ্ট গণনা শুরু করতে, আপনি জাভা থেকে আগত ফাংশনটি কল করুন। জাভা কোড থেকে রেন্ডারস্ক্রিপ্ট ব্যবহারের পদক্ষেপগুলি অনুসরণ করুন। ধাপে উপযুক্ত কার্নেলগুলি চালু করার ক্ষেত্রে, invoke_ function_name () ব্যবহার করে ইনভোকেবল ফাংশনটি কল করুন, যা কার্নেলগুলি চালু সহ পুরো গণনা শুরু করবে।

একটি কার্নেল লঞ্চ থেকে অন্যটিতে মধ্যবর্তী ফলাফলগুলি সংরক্ষণ এবং পাস করার জন্য প্রায়শই বরাদ্দের প্রয়োজন হয়। আপনি আরএসক্রিটালালোকেশন () ব্যবহার করে এগুলি তৈরি করতে পারেন। সেই এপিআইয়ের একটি সহজে ব্যবহারযোগ্য ফর্ম হ'ল rsCreateAllocation_<T><W>(…) , যেখানে টি একটি উপাদানগুলির জন্য ডেটা টাইপ, এবং ডাব্লু উপাদানটির জন্য ভেক্টর প্রস্থ। এপিআই আর্গুমেন্ট হিসাবে x, y এবং z মাত্রায় আকারগুলি নেয়। 1 ডি বা 2 ডি বরাদ্দের জন্য, মাত্রা y বা z এর আকার বাদ দেওয়া যেতে পারে। উদাহরণস্বরূপ, rsCreateAllocation_uchar4(16384) 16384 উপাদানগুলির 1D বরাদ্দ তৈরি করে, যার প্রতিটি টাইপ uchar4 এর।

বরাদ্দগুলি স্বয়ংক্রিয়ভাবে সিস্টেম দ্বারা পরিচালিত হয়। আপনাকে স্পষ্টভাবে তাদের প্রকাশ বা মুক্ত করতে হবে না। তবে, আপনি আর অন্তর্নিহিত বরাদ্দের জন্য হ্যান্ডেল alloc প্রয়োজন হবে না তা নির্দেশ করতে আপনি rsClearObject(rs_allocation* 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);
}

আপনি জাভা বা কোটলিন থেকে process() ফাংশনটিকে নিম্নরূপে কল করতে পারেন:

কোটলিন

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)

জাভা

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

এই উদাহরণটি দেখায় যে দুটি কার্নেল লঞ্চ জড়িত একটি অ্যালগরিদম কীভাবে রেন্ডারস্ক্রিপ্ট ভাষায় সম্পূর্ণরূপে প্রয়োগ করা যেতে পারে। একক উত্স রেন্ডারস্ক্রিপ্ট ছাড়াই, আপনাকে জাভা কোড থেকে উভয় কার্নেল চালু করতে হবে, কার্নেল সংজ্ঞা থেকে কার্নেল লঞ্চগুলি পৃথক করে এবং পুরো অ্যালগরিদমটি বুঝতে আরও শক্ত করে তুলতে হবে। কেবল একক উত্স রেন্ডারস্ক্রিপ্ট কোডটি পড়া সহজ নয়, এটি কার্নেল লঞ্চগুলি জুড়ে জাভা এবং স্ক্রিপ্টের মধ্যে রূপান্তরকেও সরিয়ে দেয়। কিছু পুনরাবৃত্ত অ্যালগরিদম কয়েকবার কার্নেল চালু করতে পারে, এই জাতীয় রূপান্তরকে যথেষ্ট পরিমাণে বিবেচনা করে।

স্ক্রিপ্ট গ্লোবালস

একটি স্ক্রিপ্ট গ্লোবাল একটি স্ক্রিপ্ট ( .rs ) ফাইলের একটি সাধারণ অ static গ্লোবাল ভেরিয়েবল। get_ var নাম filename .rs ScriptC_ filename গ্লোবাল const না হলে, একটি পদ্ধতি set_ var থাকবে।

প্রদত্ত স্ক্রিপ্ট গ্লোবালের দুটি পৃথক মান রয়েছে - একটি জাভা মান এবং একটি স্ক্রিপ্ট মান। এই মানগুলি নিম্নলিখিত হিসাবে আচরণ করে:

  • যদি ভিএআর স্ক্রিপ্টে স্ট্যাটিক ইনিশিয়ালাইজার থাকে তবে এটি জাভা এবং স্ক্রিপ্ট উভয় ক্ষেত্রেই ভিএআর এর প্রাথমিক মান নির্দিষ্ট করে। Otherwise, that initial value is zero.
  • Accesses to var within the script read and write its script value.
  • The get_ var method reads the Java value.
  • The set_ var method (if it exists) writes the Java value immediately, and writes the script value asynchronously .

NOTE: This means that except for any static initializer in the script, values written to a global from within a script are not visible to Java.

Reduction Kernels in Depth

Reduction is the process of combining a collection of data into a single value. This is a useful primitive in parallel programming, with applications such as the following:

  • computing the sum or product over all the data
  • computing logical operations ( and , or , xor ) over all the data
  • finding the minimum or maximum value within the data
  • searching for a specific value or for the coordinate of a specific value within the data

In Android 7.0 (API level 24) and later, RenderScript supports reduction kernels to allow efficient user-written reduction algorithms. You may launch reduction kernels on inputs with 1, 2, or 3 dimensions.

An example above shows a simple addint reduction kernel. Here is a more complicated findMinAndMax reduction kernel that finds the locations of the minimum and maximum long values in a 1-dimensional Allocation :

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

NOTE: There are more example reduction kernels here .

In order to run a reduction kernel, the RenderScript runtime creates one or more variables called accumulator data items to hold the state of the reduction process. The RenderScript runtime picks the number of accumulator data items in such a way as to maximize performance. The type of the accumulator data items ( accumType ) is determined by the kernel's accumulator function -- the first argument to that function is a pointer to an accumulator data item. By default, every accumulator data item is initialized to zero (as if by memset ); however, you may write an initializer function to do something different.

Example: In the addint kernel, the accumulator data items (of type int ) are used to add up input values. There is no initializer function, so each accumulator data item is initialized to zero.

Example: In the findMinAndMax kernel, the accumulator data items (of type MinAndMax ) are used to keep track of the minimum and maximum values found so far. There is an initializer function to set these to LONG_MAX and LONG_MIN , respectively; and to set the locations of these values to -1, indicating that the values are not actually present in the (empty) portion of the input that has been processed.

RenderScript calls your accumulator function once for every coordinate in the input(s). Typically, your function should update the accumulator data item in some way according to the input.

Example: In the addint kernel, the accumulator function adds the value of an input Element to the accumulator data item.

Example: In the findMinAndMax kernel, the accumulator function checks to see whether the value of an input Element is less than or equal to the minimum value recorded in the accumulator data item and/or greater than or equal to the maximum value recorded in the accumulator data item, and updates the accumulator data item accordingly.

After the accumulator function has been called once for every coordinate in the input(s), RenderScript must combine the accumulator data items together into a single accumulator data item. You may write a combiner function to do this. If the accumulator function has a single input and no special arguments , then you do not need to write a combiner function; RenderScript will use the accumulator function to combine the accumulator data items. (You may still write a combiner function if this default behavior is not what you want.)

Example: In the addint kernel, there is no combiner function, so the accumulator function will be used. This is the correct behavior, because if we split a collection of values into two pieces, and we add up the values in those two pieces separately, adding up those two sums is the same as adding up the entire collection.

Example: In the findMinAndMax kernel, the combiner function checks to see whether the minimum value recorded in the "source" accumulator data item *val is less than the minimum value recorded in the "destination" accumulator data item *accum , and updates *accum সেই অনুযায়ী It does similar work for the maximum value. This updates *accum to the state it would have had if all of the input values had been accumulated into *accum rather than some into *accum and some into *val .

After all of the accumulator data items have been combined, RenderScript determines the result of the reduction to return to Java. You may write an outconverter function to do this. You do not need to write an outconverter function if you want the final value of the combined accumulator data items to be the result of the reduction.

Example: In the addint kernel, there is no outconverter function. The final value of the combined data items is the sum of all Elements of the input, which is the value we want to return.

Example: In the findMinAndMax kernel, the outconverter function initializes an int2 result value to hold the locations of the minimum and maximum values resulting from the combination of all of the accumulator data items.

Writing a reduction kernel

#pragma rs reduce defines a reduction kernel by specifying its name and the names and roles of the functions that make up the kernel. All such functions must be static . A reduction kernel always requires an accumulator function; you can omit some or all of the other functions, depending on what you want the kernel to do.

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

The meaning of the items in the #pragma is as follows:

  • reduce( kernelName ) (mandatory): Specifies that a reduction kernel is being defined. A reflected Java method reduce_ kernelName will launch the kernel.
  • initializer( initializerName ) (optional): Specifies the name of the initializer function for this reduction kernel. When you launch the kernel, RenderScript calls this function once for each accumulator data item . The function must be defined like this:

    static void initializerName(accumType *accum) { … }

    accum is a pointer to an accumulator data item for this function to initialize.

    If you do not provide an initializer function, RenderScript initializes every accumulator data item to zero (as if by memset ), behaving as if there were an initializer function that looks like this:

    static void initializerName(accumType *accum) {
      memset(accum, 0, sizeof(*accum));
    }
  • accumulator( accumulatorName ) (mandatory): Specifies the name of the accumulator function for this reduction kernel. When you launch the kernel, RenderScript calls this function once for every coordinate in the input(s), to update an accumulator data item in some way according to the input(s). The function must be defined like this:

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

    accum is a pointer to an accumulator data item for this function to modify. in1 through in N are one or more arguments that are automatically filled in based on the inputs passed to the kernel launch, one argument per input. The accumulator function may optionally take any of the special arguments .

    An example kernel with multiple inputs is dotProduct .

  • combiner( combinerName )

    (optional): Specifies the name of the combiner function for this reduction kernel. After RenderScript calls the accumulator function once for every coordinate in the input(s), it calls this function as many times as necessary to combine all accumulator data items into a single accumulator data item. The function must be defined like this:

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

    accum is a pointer to a "destination" accumulator data item for this function to modify. other is a pointer to a "source" accumulator data item for this function to "combine" into *accum .

    NOTE: It is possible that *accum , *other , or both have been initialized but have never been passed to the accumulator function; that is, one or both have never been updated according to any input data. For example, in the findMinAndMax kernel, the combiner function fMMCombiner explicitly checks for idx < 0 because that indicates such an accumulator data item, whose value is INITVAL .

    If you do not provide a combiner function, RenderScript uses the accumulator function in its place, behaving as if there were a combiner function that looks like this:

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

    A combiner function is mandatory if the kernel has more than one input, if the input data type is not the same as the accumulator data type, or if the accumulator function takes one or more special arguments .

  • outconverter( outconverterName ) (optional): Specifies the name of the outconverter function for this reduction kernel. After RenderScript combines all of the accumulator data items, it calls this function to determine the result of the reduction to return to Java. The function must be defined like this:

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

    result is a pointer to a result data item (allocated but not initialized by the RenderScript runtime) for this function to initialize with the result of the reduction. resultType is the type of that data item, which need not be the same as accumType . accum is a pointer to the final accumulator data item computed by the combiner function .

    If you do not provide an outconverter function, RenderScript copies the final accumulator data item to the result data item, behaving as if there were an outconverter function that looks like this:

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

    If you want a different result type than the accumulator data type, then the outconverter function is mandatory.

Note that a kernel has input types, an accumulator data item type, and a result type, none of which need to be the same. For example, in the findMinAndMax kernel, the input type long , accumulator data item type MinAndMax , and result type int2 are all different.

What can't you assume?

You must not rely on the number of accumulator data items created by RenderScript for a given kernel launch. There is no guarantee that two launches of the same kernel with the same input(s) will create the same number of accumulator data items.

You must not rely on the order in which RenderScript calls the initializer, accumulator, and combiner functions; it may even call some of them in parallel. There is no guarantee that two launches of the same kernel with the same input will follow the same order. The only guarantee is that only the initializer function will ever see an uninitialized accumulator data item. যেমন:

  • There is no guarantee that all accumulator data items will be initialized before the accumulator function is called, although it will only be called on an initialized accumulator data item.
  • There is no guarantee on the order in which input Elements are passed to the accumulator function.
  • There is no guarantee that the accumulator function has been called for all input Elements before the combiner function is called.

One consequence of this is that the findMinAndMax kernel is not deterministic: If the input contains more than one occurrence of the same minimum or maximum value, you have no way of knowing which occurrence the kernel will find.

What must you guarantee?

Because the RenderScript system can choose to execute a kernel in many different ways , you must follow certain rules to ensure that your kernel behaves the way you want. If you do not follow these rules, you may get incorrect results, nondeterministic behavior, or runtime errors.

The rules below often say that two accumulator data items must have " the same value" . এর মানে কি? That depends on what you want the kernel to do. For a mathematical reduction such as addint , it usually makes sense for "the same" to mean mathematical equality. For a "pick any" search such as findMinAndMax ("find the location of minimum and maximum input values") where there might be more than one occurrence of identical input values, all locations of a given input value must be considered "the same" . You could write a similar kernel to "find the location of leftmost minimum and maximum input values" where (say) a minimum value at location 100 is preferred over an identical minimum value at location 200; for this kernel, "the same" would mean identical location , not merely identical value , and the accumulator and combiner functions would have to be different than those for findMinAndMax .

The initializer function must create an identity value . That is, if I and A are accumulator data items initialized by the initializer function, and I has never been passed to the accumulator function (but A may have been), then
  • combinerName (& A , & I ) must leave A the same
  • combinerName (& I , & A ) must leave I the same as A

Example: In the addint kernel, an accumulator data item is initialized to zero. The combiner function for this kernel performs addition; zero is the identity value for addition.

Example: In the findMinAndMax kernel, an accumulator data item is initialized to INITVAL .

  • fMMCombiner(& A , & I ) leaves A the same, because I is INITVAL .
  • fMMCombiner(& I , & A ) sets I to A , because I is INITVAL .

Therefore, INITVAL is indeed an identity value.

The combiner function must be commutative . That is, if A and B are accumulator data items initialized by the initializer function, and that may have been passed to the accumulator function zero or more times, then combinerName (& A , & B ) must set A to the same value that combinerName (& B , & A ) sets B .

Example: In the addint kernel, the combiner function adds the two accumulator data item values; addition is commutative.

Example: In the findMinAndMax kernel, fMMCombiner(& A , & B ) is the same as A = minmax( A , B ) , and minmax is commutative, so fMMCombiner is also.

The combiner function must be associative . That is, if A , B , and C are accumulator data items initialized by the initializer function, and that may have been passed to the accumulator function zero or more times, then the following two code sequences must set A to the same value :

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

Example: In the addint kernel, the combiner function adds the two accumulator data item values:

  • 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
    

Addition is associative, and so the combiner function is also.

Example: In the findMinAndMax kernel,

fMMCombiner(&A, &B)
হিসাবে একই
A = minmax(A, B)
So the two sequences are
  • 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 is associative, and so fMMCombiner is also.

The accumulator function and combiner function together must obey the basic folding rule . That is, if A and B are accumulator data items, A has been initialized by the initializer function and may have been passed to the accumulator function zero or more times, B has not been initialized, and args is the list of input arguments and special arguments for a particular call to the accumulator function, then the following two code sequences must set A to the same value :

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

Example: In the addint kernel, for an input value V :

  • Statement 1 is the same as A += V
  • Statement 2 is the same as B = 0
  • Statement 3 is the same as B += V , which is the same as B = V
  • Statement 4 is the same as A += B , which is the same as A += V

Statements 1 and 4 set A to the same value, and so this kernel obeys the basic folding rule.

Example: In the findMinAndMax kernel, for an input value V at coordinate X :

  • Statement 1 is the same as A = minmax(A, IndexedVal( V , X ))
  • Statement 2 is the same as B = INITVAL
  • Statement 3 is the same as
    B = minmax(B, IndexedVal(V, X))
    
    which, because B is the initial value, is the same as
    B = IndexedVal(V, X)
    
  • Statement 4 is the same as
    A = minmax(A, B)
    
    যা একই রকম
    A = minmax(A, IndexedVal(V, X))
    

Statements 1 and 4 set A to the same value, and so this kernel obeys the basic folding rule.

Calling a reduction kernel from Java code

For a reduction kernel named kernelName defined in the file filename .rs , there are three methods reflected in the class ScriptC_ filename :

কোটলিন

// 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

জাভা

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

Here are some examples of calling the addint kernel:

কোটলিন

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

জাভা

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

Method 1 has one input Allocation argument for every input argument in the kernel's accumulator function . The RenderScript runtime checks to ensure that all of the input Allocations have the same dimensions and that the Element type of each of the input Allocations matches that of the corresponding input argument of the accumulator function's prototype. If any of these checks fail, RenderScript throws an exception. The kernel executes over every coordinate in those dimensions.

Method 2 is the same as Method 1 except that Method 2 takes an additional argument sc that can be used to limit the kernel execution to a subset of the coordinates.

Method 3 is the same as Method 1 except that instead of taking Allocation inputs it takes Java array inputs. This is a convenience that saves you from having to write code to explicitly create an Allocation and copy data to it from a Java array. However, using Method 3 instead of Method 1 does not increase the performance of the code . For each input array, Method 3 creates a temporary 1-dimensional Allocation with the appropriate Element type and setAutoPadding(boolean) enabled, and copies the array to the Allocation as if by the appropriate copyFrom() method of Allocation . It then calls Method 1, passing those temporary Allocations.

NOTE: If your application will make multiple kernel calls with the same array, or with different arrays of the same dimensions and Element type, you may improve performance by explicitly creating, populating, and reusing Allocations yourself, instead of by using Method 3.

javaFutureType , the return type of the reflected reduction methods, is a reflected static nested class within the ScriptC_ filename class. It represents the future result of a reduction kernel run. To obtain the actual result of the run, call the get() method of that class, which returns a value of type javaResultType . get() is synchronous .

কোটলিন

class ScriptC_filename(rs: RenderScript) : ScriptC(…) {
    object javaFutureType {
        fun get(): javaResultType { … }
    }
}

জাভা

public class ScriptC_filename extends ScriptC {
  public static class javaFutureType {
    public javaResultType get() { … }
  }
}

javaResultType is determined from the resultType of the outconverter function . Unless resultType is an unsigned type (scalar, vector, or array), javaResultType is the directly corresponding Java type. If resultType is an unsigned type and there is a larger Java signed type, then javaResultType is that larger Java signed type; otherwise, it is the directly corresponding Java type. যেমন:

  • If resultType is int , int2 , or int[15] , then javaResultType is int , Int2 , or int[] . All values of resultType can be represented by javaResultType .
  • If resultType is uint , uint2 , or uint[15] , then javaResultType is long , Long2 , or long[] . All values of resultType can be represented by javaResultType .
  • If resultType is ulong , ulong2 , or ulong[15] , then javaResultType is long , Long2 , or long[] . There are certain values of resultType that cannot be represented by javaResultType .

javaFutureType is the future result type corresponding to the resultType of the outconverter function .

  • If resultType is not an array type, then javaFutureType is result_ resultType .
  • If resultType is an array of length Count with members of type memberType , then javaFutureType is resultArray Count _ memberType .

যেমন:

কোটলিন

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> = …
    }
}

জাভা

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() { … }
  }
}

If javaResultType is an object type (including an array type), each call to javaFutureType .get() on the same instance will return the same object.

If javaResultType cannot represent all values of type resultType , and a reduction kernel produces an unrepresentible value, then javaFutureType .get() throws an exception.

Method 3 and devecSiInXType

devecSiInXType is the Java type corresponding to the inXType of the corresponding argument of the accumulator function . Unless inXType is an unsigned type or a vector type, devecSiInXType is the directly corresponding Java type. If inXType is an unsigned scalar type, then devecSiInXType is the Java type directly corresponding to the signed scalar type of the same size. If inXType is a signed vector type, then devecSiInXType is the Java type directly corresponding to the vector component type. If inXType is an unsigned vector type, then devecSiInXType is the Java type directly corresponding to the signed scalar type of the same size as the vector component type. যেমন:

  • If inXType is int , then devecSiInXType is int .
  • If inXType is int2 , then devecSiInXType is int . The array is a flattened representation: It has twice as many scalar Elements as the Allocation has 2-component vector Elements. This is the same way that the copyFrom() methods of Allocation work.
  • If inXType is uint , then deviceSiInXType is int . A signed value in the Java array is interpreted as an unsigned value of the same bitpattern in the Allocation. This is the same way that the copyFrom() methods of Allocation work.
  • If inXType is uint2 , then deviceSiInXType is int . This is a combination of the way int2 and uint are handled: The array is a flattened representation, and Java array signed values are interpreted as RenderScript unsigned Element values.

Note that for Method 3 , input types are handled differently than result types:

  • A script's vector input is flattened on the Java side, whereas a script's vector result is not.
  • A script's unsigned input is represented as a signed input of the same size on the Java side, whereas a script's unsigned result is represented as a widened signed type on the Java side (except in the case of ulong ).

More example reduction kernels

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

অতিরিক্ত কোড নমুনা

The BasicRenderScript , RenderScriptIntrinsic , and Hello Compute samples further demonstrate the use of the APIs covered on this page.

,

রেন্ডারস্ক্রিপ্ট হল Android-এ উচ্চ কার্যসম্পাদনে গণনামূলকভাবে নিবিড় কাজ চালানোর জন্য একটি কাঠামো। RenderScript is primarily oriented for use with data-parallel computation, although serial workloads can benefit as well. রেন্ডারস্ক্রিপ্ট রানটাইম একটি ডিভাইসে উপলব্ধ প্রসেসরের কাজকে সমান্তরাল করে, যেমন মাল্টি-কোর সিপিইউ এবং জিপিইউ। এটি আপনাকে কাজের সময় নির্ধারণের পরিবর্তে অ্যালগরিদম প্রকাশের উপর ফোকাস করতে দেয়। রেন্ডারস্ক্রিপ্ট বিশেষ করে ইমেজ প্রসেসিং, কম্পিউটেশনাল ফটোগ্রাফি বা কম্পিউটার ভিশন সম্পাদনকারী অ্যাপ্লিকেশনের জন্য উপযোগী।

To begin with RenderScript, there are two main concepts you should understand:

  • The language itself is a C99-derived language for writing high-performance compute code. Writing a RenderScript Kernel describes how to use it to write compute kernels.
  • The control API is used for managing the lifetime of RenderScript resources and controlling kernel execution. It is available in three different languages: Java, C++ in Android NDK, and the C99-derived kernel language itself. Using RenderScript from Java Code and Single-Source RenderScript describe the first and the third options, respectively.

Writing a RenderScript Kernel

A RenderScript kernel typically resides in a .rs file in the <project_root>/src/rs directory; each .rs file is called a script . Every script contains its own set of kernels, functions, and variables. A script can contain:

  • A pragma declaration ( #pragma version(1) ) that declares the version of the RenderScript kernel language used in this script. Currently, 1 is the only valid value.
  • A pragma declaration ( #pragma rs java_package_name(com.example.app) ) that declares the package name of the Java classes reflected from this script. Note that your .rs file must be part of your application package, and not in a library project.
  • Zero or more invokable functions . An invokable function is a single-threaded RenderScript function that you can call from your Java code with arbitrary arguments. These are often useful for initial setup or serial computations within a larger processing pipeline.
  • Zero or more script globals . A script global is similar to a global variable in C. You can access script globals from Java code, and these are often used for parameter passing to RenderScript kernels. Script globals are explained in more detail here .

  • Zero or more compute kernels . A compute kernel is a function or collection of functions that you can direct the RenderScript runtime to execute in parallel across a collection of data. There are two kinds of compute kernels: mapping kernels (also called foreach kernels) and reduction kernels.

    A mapping kernel is a parallel function that operates on a collection of Allocations of the same dimensions. By default, it executes once for every coordinate in those dimensions. It is typically (but not exclusively) used to transform a collection of input Allocations to an output Allocation one Element at a time.

    • Here is an example of a simple mapping kernel :

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

      In most respects, this is identical to a standard C function. The RS_KERNEL property applied to the function prototype specifies that the function is a RenderScript mapping kernel instead of an invokable function. The in argument is automatically filled in based on the input Allocation passed to the kernel launch. The arguments x and y are discussed below . The value returned from the kernel is automatically written to the appropriate location in the output Allocation . By default, this kernel is run across its entire input Allocation , with one execution of the kernel function per Element in the Allocation .

      A mapping kernel may have one or more input Allocations , a single output Allocation , or both. The RenderScript runtime checks to ensure that all input and output Allocations have the same dimensions, and that the Element types of the input and output Allocations match the kernel's prototype; if either of these checks fails, RenderScript throws an exception.

      NOTE: Before Android 6.0 (API level 23), a mapping kernel may not have more than one input Allocation .

      If you need more input or output Allocations than the kernel has, those objects should be bound to rs_allocation script globals and accessed from a kernel or invokable function via rsGetElementAt_ type () or rsSetElementAt_ type () .

      NOTE: RS_KERNEL is a macro defined automatically by RenderScript for your convenience:

      #define RS_KERNEL __attribute__((kernel))
      

    A reduction kernel is a family of functions that operates on a collection of input Allocations of the same dimensions. By default, its accumulator function executes once for every coordinate in those dimensions. It is typically (but not exclusively) used to "reduce" a collection of input Allocations to a single value.

    • Here is an example of a simple reduction kernel that adds up the Elements of its input:

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

      A reduction kernel consists of one or more user-written functions. #pragma rs reduce is used to define the kernel by specifying its name ( addint , in this example) and the names and roles of the functions that make up the kernel (an accumulator function addintAccum , in this example). All such functions must be static . A reduction kernel always requires an accumulator function; it may also have other functions, depending on what you want the kernel to do.

      A reduction kernel accumulator function must return void and must have at least two arguments. The first argument ( accum , in this example) is a pointer to an accumulator data item and the second ( val , in this example) is automatically filled in based on the input Allocation passed to the kernel launch. The accumulator data item is created by the RenderScript runtime; by default, it is initialized to zero. By default, this kernel is run across its entire input Allocation , with one execution of the accumulator function per Element in the Allocation . By default, the final value of the accumulator data item is treated as the result of the reduction, and is returned to Java. The RenderScript runtime checks to ensure that the Element type of the input Allocation matches the accumulator function's prototype; if it does not match, RenderScript throws an exception.

      A reduction kernel has one or more input Allocations but no output Allocations .

      Reduction kernels are explained in more detail here .

      Reduction kernels are supported in Android 7.0 (API level 24) and later.

    A mapping kernel function or a reduction kernel accumulator function may access the coordinates of the current execution using the special arguments x , y , and z , which must be of type int or uint32_t . এই আর্গুমেন্ট ঐচ্ছিক.

    A mapping kernel function or a reduction kernel accumulator function may also take the optional special argument context of type rs_kernel_context . It is needed by a family of runtime APIs that are used to query certain properties of the current execution -- for example, rsGetDimX . (The context argument is available in Android 6.0 (API level 23) and later.)

  • An optional init() function. The init() function is a special type of invokable function that RenderScript runs when the script is first instantiated. This allows for some computation to occur automatically at script creation.
  • Zero or more static script globals and functions . A static script global is equivalent to a script global except that it cannot be accessed from Java code. A static function is a standard C function that can be called from any kernel or invokable function in the script but is not exposed to the Java API. If a script global or function does not need to be accessed from Java code, it is highly recommended that it be declared static .

Setting floating point precision

You can control the required level of floating point precision in a script. This is useful if full IEEE 754-2008 standard (used by default) is not required. The following pragmas can set a different level of floating point precision:

  • #pragma rs_fp_full (default if nothing is specified): For apps that require floating point precision as outlined by the IEEE 754-2008 standard.
  • #pragma rs_fp_relaxed : For apps that don't require strict IEEE 754-2008 compliance and can tolerate less precision. This mode enables flush-to-zero for denorms and round-towards-zero.
  • #pragma rs_fp_imprecise : For apps that don't have stringent precision requirements. This mode enables everything in rs_fp_relaxed along with the following:
    • Operations resulting in -0.0 can return +0.0 instead.
    • Operations on INF and NAN are undefined.

Most applications can use rs_fp_relaxed without any side effects. This may be very beneficial on some architectures due to additional optimizations only available with relaxed precision (such as SIMD CPU instructions).

Accessing RenderScript APIs from Java

When developing an Android application that uses RenderScript, you can access its API from Java in one of two ways:

Here are the tradeoffs:

  • If you use the Support Library APIs, the RenderScript portion of your application will be compatible with devices running Android 2.3 (API level 9) and higher, regardless of which RenderScript features you use. This allows your application to work on more devices than if you use the native ( android.renderscript ) APIs.
  • Certain RenderScript features are not available through the Support Library APIs.
  • If you use the Support Library APIs, you will get (possibly significantly) larger APKs than if you use the native ( android.renderscript ) APIs.

Using the RenderScript Support Library APIs

In order to use the Support Library RenderScript APIs, you must configure your development environment to be able to access them. The following Android SDK tools are required for using these APIs:

  • Android SDK Tools revision 22.2 or higher
  • Android SDK Build-tools revision 18.1.0 or higher

Note that starting from Android SDK Build-tools 24.0.0, Android 2.2 (API level 8) is no longer supported.

You can check and update the installed version of these tools in the Android SDK Manager .

To use the Support Library RenderScript APIs:

  1. Make sure you have the required Android SDK version installed.
  2. Update the settings for the Android build process to include the RenderScript settings:
    • Open the build.gradle file in the app folder of your application module.
    • Add the following RenderScript settings to the file:

      গ্রোভি

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

      কোটলিন

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

      The settings listed above control specific behavior in the Android build process:

      • renderscriptTargetApi - Specifies the bytecode version to be generated. We recommend you set this value to the lowest API level able to provide all the functionality you are using and set renderscriptSupportModeEnabled to true . Valid values for this setting are any integer value from 11 to the most recently released API level. If your minimum SDK version specified in your application manifest is set to a different value, that value is ignored and the target value in the build file is used to set the minimum SDK version.
      • renderscriptSupportModeEnabled - Specifies that the generated bytecode should fall back to a compatible version if the device it is running on does not support the target version.
  3. In your application classes that use RenderScript, add an import for the Support Library classes:

    কোটলিন

    import android.support.v8.renderscript.*
    

    জাভা

    import android.support.v8.renderscript.*;
    

Using RenderScript from Java or Kotlin Code

Using RenderScript from Java or Kotlin code relies on the API classes located in the android.renderscript or the android.support.v8.renderscript package. Most applications follow the same basic usage pattern:

  1. Initialize a RenderScript context. The RenderScript context, created with create(Context) , ensures that RenderScript can be used and provides an object to control the lifetime of all subsequent RenderScript objects. You should consider context creation to be a potentially long-running operation, since it may create resources on different pieces of hardware; it should not be in an application's critical path if at all possible. Typically, an application will have only a single RenderScript context at a time.
  2. Create at least one Allocation to be passed to a script. An Allocation is a RenderScript object that provides storage for a fixed amount of data. Kernels in scripts take Allocation objects as their input and output, and Allocation objects can be accessed in kernels using rsGetElementAt_ type () and rsSetElementAt_ type () when bound as script globals. Allocation objects allow arrays to be passed from Java code to RenderScript code and vice-versa. Allocation objects are typically created using createTyped() or createFromBitmap() .
  3. Create whatever scripts are necessary. There are two types of scripts available to you when using RenderScript:
    • ScriptC : These are the user-defined scripts as described in Writing a RenderScript Kernel above. Every script has a Java class reflected by the RenderScript compiler in order to make it easy to access the script from Java code; this class has the name ScriptC_ filename . For example, if the mapping kernel above were located in invert.rs and a RenderScript context were already located in mRenderScript , the Java or Kotlin code to instantiate the script would be:

      কোটলিন

      val invert = ScriptC_invert(renderScript)
      

      জাভা

      ScriptC_invert invert = new ScriptC_invert(renderScript);
      
    • ScriptIntrinsic : These are built-in RenderScript kernels for common operations, such as Gaussian blur, convolution, and image blending. For more information, see the subclasses of ScriptIntrinsic .
  4. Populate Allocations with data. Except for Allocations created with createFromBitmap() , an Allocation is populated with empty data when it is first created. To populate an Allocation, use one of the "copy" methods in Allocation . The "copy" methods are synchronous .
  5. Set any necessary script globals . You may set globals using methods in the same ScriptC_ filename class named set_ globalname . For example, in order to set an int variable named threshold , use the Java method set_threshold(int) ; and in order to set an rs_allocation variable named lookup , use the Java method set_lookup(Allocation) . The set methods are asynchronous .
  6. Launch the appropriate kernels and invokable functions.

    Methods to launch a given kernel are reflected in the same ScriptC_ filename class with methods named forEach_ mappingKernelName () or reduce_ reductionKernelName () . These launches are asynchronous . Depending on the arguments to the kernel, the method takes one or more Allocations, all of which must have the same dimensions. By default, a kernel executes over every coordinate in those dimensions; to execute a kernel over a subset of those coordinates, pass an appropriate Script.LaunchOptions as the last argument to the forEach or reduce method.

    Launch invokable functions using the invoke_ functionName methods reflected in the same ScriptC_ filename class. These launches are asynchronous .

  7. Retrieve data from Allocation objects and javaFutureType objects. In order to access data from an Allocation from Java code, you must copy that data back to Java using one of the "copy" methods in Allocation . In order to obtain the result of a reduction kernel, you must use the javaFutureType .get() method. The "copy" and get() methods are synchronous .
  8. Tear down the RenderScript context. You can destroy the RenderScript context with destroy() or by allowing the RenderScript context object to be garbage collected. This causes any further use of any object belonging to that context to throw an exception.

Asynchronous execution model

The reflected forEach , invoke , reduce , and set methods are asynchronous -- each may return to Java before completing the requested action. However, the individual actions are serialized in the order in which they are launched.

The Allocation class provides "copy" methods to copy data to and from Allocations. A "copy" method is synchronous, and is serialized with respect to any of the asynchronous actions above that touch the same Allocation.

The reflected javaFutureType classes provide a get() method to obtain the result of a reduction. get() is synchronous, and is serialized with respect to the reduction (which is asynchronous).

Single-Source RenderScript

Android 7.0 (API level 24) introduces a new programming feature called Single-Source RenderScript , in which kernels are launched from the script where they are defined, rather than from Java. This approach is currently limited to mapping kernels, which are simply referred to as "kernels" in this section for conciseness. This new feature also supports creating allocations of type rs_allocation from inside the script. It is now possible to implement a whole algorithm solely within a script, even if multiple kernel launches are required. The benefit is twofold: more readable code, because it keeps the implementation of an algorithm in one language; and potentially faster code, because of fewer transitions between Java and RenderScript across multiple kernel launches.

In Single-Source RenderScript, you write kernels as described in Writing a RenderScript Kernel . You then write an invokable function that calls rsForEach() to launch them. That API takes a kernel function as the first parameter, followed by input and output allocations. A similar API rsForEachWithOptions() takes an extra argument of type rs_script_call_t , which specifies a subset of the elements from the input and output allocations for the kernel function to process.

To start RenderScript computation, you call the invokable function from Java. Follow the steps in Using RenderScript from Java Code . In the step launch the appropriate kernels , call the invokable function using invoke_ function_name () , which will start the whole computation, including launching kernels.

Allocations are often needed to save and pass intermediate results from one kernel launch to another. You can create them using rsCreateAllocation() . One easy-to-use form of that API is rsCreateAllocation_<T><W>(…) , where T is the data type for an element, and W is the vector width for the element. The API takes the sizes in dimensions X, Y, and Z as arguments. For 1D or 2D allocations, the size for dimension Y or Z can be omitted. For example, rsCreateAllocation_uchar4(16384) creates a 1D allocation of 16384 elements, each of which is of type uchar4 .

Allocations are managed by the system automatically. You do not have to explicitly release or free them. However, you can call rsClearObject(rs_allocation* alloc) to indicate you no longer need the handle alloc to the underlying allocation, so that the system can free up resources as early as possible.

The Writing a RenderScript Kernel section contains an example kernel that inverts an image. The example below expands that to apply more than one effect to an image, using Single-Source RenderScript. It includes another kernel, greyscale , which turns a color image into black-and-white. An invokable function process() then applies those two kernels consecutively to an input image, and produces an output image. Allocations for both the input and the output are passed in as arguments of type 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);
}

You can call the process() function from Java or Kotlin as follows:

কোটলিন

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)

জাভা

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

This example shows how an algorithm that involves two kernel launches can be implemented completely in the RenderScript language itself. Without Single-Source RenderScript, you would have to launch both kernels from the Java code, separating kernel launches from kernel definitions and making it harder to understand the whole algorithm. Not only is the Single-Source RenderScript code easier to read, it also eliminates the transitioning between Java and the script across kernel launches. Some iterative algorithms may launch kernels hundreds of times, making the overhead of such transitioning considerable.

Script Globals

A script global is an ordinary non- static global variable in a script ( .rs ) file. For a script global named var defined in the file filename .rs , there will be a method get_ var reflected in the class ScriptC_ filename . Unless the global is const , there will also be a method set_ var .

A given script global has two separate values -- a Java value and a script value. These values behave as follows:

  • If var has a static initializer in the script, it specifies the initial value of var in both Java and the script. Otherwise, that initial value is zero.
  • Accesses to var within the script read and write its script value.
  • The get_ var method reads the Java value.
  • The set_ var method (if it exists) writes the Java value immediately, and writes the script value asynchronously .

NOTE: This means that except for any static initializer in the script, values written to a global from within a script are not visible to Java.

Reduction Kernels in Depth

Reduction is the process of combining a collection of data into a single value. This is a useful primitive in parallel programming, with applications such as the following:

  • computing the sum or product over all the data
  • computing logical operations ( and , or , xor ) over all the data
  • finding the minimum or maximum value within the data
  • searching for a specific value or for the coordinate of a specific value within the data

In Android 7.0 (API level 24) and later, RenderScript supports reduction kernels to allow efficient user-written reduction algorithms. You may launch reduction kernels on inputs with 1, 2, or 3 dimensions.

An example above shows a simple addint reduction kernel. Here is a more complicated findMinAndMax reduction kernel that finds the locations of the minimum and maximum long values in a 1-dimensional Allocation :

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

NOTE: There are more example reduction kernels here .

In order to run a reduction kernel, the RenderScript runtime creates one or more variables called accumulator data items to hold the state of the reduction process. The RenderScript runtime picks the number of accumulator data items in such a way as to maximize performance. The type of the accumulator data items ( accumType ) is determined by the kernel's accumulator function -- the first argument to that function is a pointer to an accumulator data item. By default, every accumulator data item is initialized to zero (as if by memset ); however, you may write an initializer function to do something different.

Example: In the addint kernel, the accumulator data items (of type int ) are used to add up input values. There is no initializer function, so each accumulator data item is initialized to zero.

Example: In the findMinAndMax kernel, the accumulator data items (of type MinAndMax ) are used to keep track of the minimum and maximum values found so far. There is an initializer function to set these to LONG_MAX and LONG_MIN , respectively; and to set the locations of these values to -1, indicating that the values are not actually present in the (empty) portion of the input that has been processed.

RenderScript calls your accumulator function once for every coordinate in the input(s). Typically, your function should update the accumulator data item in some way according to the input.

Example: In the addint kernel, the accumulator function adds the value of an input Element to the accumulator data item.

Example: In the findMinAndMax kernel, the accumulator function checks to see whether the value of an input Element is less than or equal to the minimum value recorded in the accumulator data item and/or greater than or equal to the maximum value recorded in the accumulator data item, and updates the accumulator data item accordingly.

After the accumulator function has been called once for every coordinate in the input(s), RenderScript must combine the accumulator data items together into a single accumulator data item. You may write a combiner function to do this. If the accumulator function has a single input and no special arguments , then you do not need to write a combiner function; RenderScript will use the accumulator function to combine the accumulator data items. (You may still write a combiner function if this default behavior is not what you want.)

Example: In the addint kernel, there is no combiner function, so the accumulator function will be used. This is the correct behavior, because if we split a collection of values into two pieces, and we add up the values in those two pieces separately, adding up those two sums is the same as adding up the entire collection.

Example: In the findMinAndMax kernel, the combiner function checks to see whether the minimum value recorded in the "source" accumulator data item *val is less than the minimum value recorded in the "destination" accumulator data item *accum , and updates *accum সেই অনুযায়ী It does similar work for the maximum value. This updates *accum to the state it would have had if all of the input values had been accumulated into *accum rather than some into *accum and some into *val .

After all of the accumulator data items have been combined, RenderScript determines the result of the reduction to return to Java. You may write an outconverter function to do this. You do not need to write an outconverter function if you want the final value of the combined accumulator data items to be the result of the reduction.

Example: In the addint kernel, there is no outconverter function. The final value of the combined data items is the sum of all Elements of the input, which is the value we want to return.

Example: In the findMinAndMax kernel, the outconverter function initializes an int2 result value to hold the locations of the minimum and maximum values resulting from the combination of all of the accumulator data items.

Writing a reduction kernel

#pragma rs reduce defines a reduction kernel by specifying its name and the names and roles of the functions that make up the kernel. All such functions must be static . A reduction kernel always requires an accumulator function; you can omit some or all of the other functions, depending on what you want the kernel to do.

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

The meaning of the items in the #pragma is as follows:

  • reduce( kernelName ) (mandatory): Specifies that a reduction kernel is being defined. A reflected Java method reduce_ kernelName will launch the kernel.
  • initializer( initializerName ) (optional): Specifies the name of the initializer function for this reduction kernel. When you launch the kernel, RenderScript calls this function once for each accumulator data item . The function must be defined like this:

    static void initializerName(accumType *accum) { … }

    accum is a pointer to an accumulator data item for this function to initialize.

    If you do not provide an initializer function, RenderScript initializes every accumulator data item to zero (as if by memset ), behaving as if there were an initializer function that looks like this:

    static void initializerName(accumType *accum) {
      memset(accum, 0, sizeof(*accum));
    }
  • accumulator( accumulatorName ) (mandatory): Specifies the name of the accumulator function for this reduction kernel. When you launch the kernel, RenderScript calls this function once for every coordinate in the input(s), to update an accumulator data item in some way according to the input(s). The function must be defined like this:

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

    accum is a pointer to an accumulator data item for this function to modify. in1 through in N are one or more arguments that are automatically filled in based on the inputs passed to the kernel launch, one argument per input. The accumulator function may optionally take any of the special arguments .

    An example kernel with multiple inputs is dotProduct .

  • combiner( combinerName )

    (optional): Specifies the name of the combiner function for this reduction kernel. After RenderScript calls the accumulator function once for every coordinate in the input(s), it calls this function as many times as necessary to combine all accumulator data items into a single accumulator data item. The function must be defined like this:

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

    accum is a pointer to a "destination" accumulator data item for this function to modify. other is a pointer to a "source" accumulator data item for this function to "combine" into *accum .

    NOTE: It is possible that *accum , *other , or both have been initialized but have never been passed to the accumulator function; that is, one or both have never been updated according to any input data. For example, in the findMinAndMax kernel, the combiner function fMMCombiner explicitly checks for idx < 0 because that indicates such an accumulator data item, whose value is INITVAL .

    If you do not provide a combiner function, RenderScript uses the accumulator function in its place, behaving as if there were a combiner function that looks like this:

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

    A combiner function is mandatory if the kernel has more than one input, if the input data type is not the same as the accumulator data type, or if the accumulator function takes one or more special arguments .

  • outconverter( outconverterName ) (optional): Specifies the name of the outconverter function for this reduction kernel. After RenderScript combines all of the accumulator data items, it calls this function to determine the result of the reduction to return to Java. The function must be defined like this:

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

    result is a pointer to a result data item (allocated but not initialized by the RenderScript runtime) for this function to initialize with the result of the reduction. resultType is the type of that data item, which need not be the same as accumType . accum is a pointer to the final accumulator data item computed by the combiner function .

    If you do not provide an outconverter function, RenderScript copies the final accumulator data item to the result data item, behaving as if there were an outconverter function that looks like this:

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

    If you want a different result type than the accumulator data type, then the outconverter function is mandatory.

Note that a kernel has input types, an accumulator data item type, and a result type, none of which need to be the same. For example, in the findMinAndMax kernel, the input type long , accumulator data item type MinAndMax , and result type int2 are all different.

What can't you assume?

You must not rely on the number of accumulator data items created by RenderScript for a given kernel launch. There is no guarantee that two launches of the same kernel with the same input(s) will create the same number of accumulator data items.

You must not rely on the order in which RenderScript calls the initializer, accumulator, and combiner functions; it may even call some of them in parallel. There is no guarantee that two launches of the same kernel with the same input will follow the same order. The only guarantee is that only the initializer function will ever see an uninitialized accumulator data item. যেমন:

  • There is no guarantee that all accumulator data items will be initialized before the accumulator function is called, although it will only be called on an initialized accumulator data item.
  • There is no guarantee on the order in which input Elements are passed to the accumulator function.
  • There is no guarantee that the accumulator function has been called for all input Elements before the combiner function is called.

One consequence of this is that the findMinAndMax kernel is not deterministic: If the input contains more than one occurrence of the same minimum or maximum value, you have no way of knowing which occurrence the kernel will find.

What must you guarantee?

Because the RenderScript system can choose to execute a kernel in many different ways , you must follow certain rules to ensure that your kernel behaves the way you want. If you do not follow these rules, you may get incorrect results, nondeterministic behavior, or runtime errors.

The rules below often say that two accumulator data items must have " the same value" . এর মানে কি? That depends on what you want the kernel to do. For a mathematical reduction such as addint , it usually makes sense for "the same" to mean mathematical equality. For a "pick any" search such as findMinAndMax ("find the location of minimum and maximum input values") where there might be more than one occurrence of identical input values, all locations of a given input value must be considered "the same" . You could write a similar kernel to "find the location of leftmost minimum and maximum input values" where (say) a minimum value at location 100 is preferred over an identical minimum value at location 200; for this kernel, "the same" would mean identical location , not merely identical value , and the accumulator and combiner functions would have to be different than those for findMinAndMax .

The initializer function must create an identity value . That is, if I and A are accumulator data items initialized by the initializer function, and I has never been passed to the accumulator function (but A may have been), then
  • combinerName (& A , & I ) must leave A the same
  • combinerName (& I , & A ) must leave I the same as A

Example: In the addint kernel, an accumulator data item is initialized to zero. The combiner function for this kernel performs addition; zero is the identity value for addition.

Example: In the findMinAndMax kernel, an accumulator data item is initialized to INITVAL .

  • fMMCombiner(& A , & I ) leaves A the same, because I is INITVAL .
  • fMMCombiner(& I , & A ) sets I to A , because I is INITVAL .

Therefore, INITVAL is indeed an identity value.

The combiner function must be commutative . That is, if A and B are accumulator data items initialized by the initializer function, and that may have been passed to the accumulator function zero or more times, then combinerName (& A , & B ) must set A to the same value that combinerName (& B , & A ) sets B .

Example: In the addint kernel, the combiner function adds the two accumulator data item values; addition is commutative.

Example: In the findMinAndMax kernel, fMMCombiner(& A , & B ) is the same as A = minmax( A , B ) , and minmax is commutative, so fMMCombiner is also.

The combiner function must be associative . That is, if A , B , and C are accumulator data items initialized by the initializer function, and that may have been passed to the accumulator function zero or more times, then the following two code sequences must set A to the same value :

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

Example: In the addint kernel, the combiner function adds the two accumulator data item values:

  • 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
    

Addition is associative, and so the combiner function is also.

Example: In the findMinAndMax kernel,

fMMCombiner(&A, &B)
হিসাবে একই
A = minmax(A, B)
So the two sequences are
  • 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 is associative, and so fMMCombiner is also.

The accumulator function and combiner function together must obey the basic folding rule . That is, if A and B are accumulator data items, A has been initialized by the initializer function and may have been passed to the accumulator function zero or more times, B has not been initialized, and args is the list of input arguments and special arguments for a particular call to the accumulator function, then the following two code sequences must set A to the same value :

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

Example: In the addint kernel, for an input value V :

  • Statement 1 is the same as A += V
  • Statement 2 is the same as B = 0
  • Statement 3 is the same as B += V , which is the same as B = V
  • Statement 4 is the same as A += B , which is the same as A += V

Statements 1 and 4 set A to the same value, and so this kernel obeys the basic folding rule.

Example: In the findMinAndMax kernel, for an input value V at coordinate X :

  • Statement 1 is the same as A = minmax(A, IndexedVal( V , X ))
  • Statement 2 is the same as B = INITVAL
  • Statement 3 is the same as
    B = minmax(B, IndexedVal(V, X))
    
    which, because B is the initial value, is the same as
    B = IndexedVal(V, X)
    
  • Statement 4 is the same as
    A = minmax(A, B)
    
    যা একই রকম
    A = minmax(A, IndexedVal(V, X))
    

Statements 1 and 4 set A to the same value, and so this kernel obeys the basic folding rule.

Calling a reduction kernel from Java code

For a reduction kernel named kernelName defined in the file filename .rs , there are three methods reflected in the class ScriptC_ filename :

কোটলিন

// 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

জাভা

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

Here are some examples of calling the addint kernel:

কোটলিন

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

জাভা

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

Method 1 has one input Allocation argument for every input argument in the kernel's accumulator function . The RenderScript runtime checks to ensure that all of the input Allocations have the same dimensions and that the Element type of each of the input Allocations matches that of the corresponding input argument of the accumulator function's prototype. If any of these checks fail, RenderScript throws an exception. The kernel executes over every coordinate in those dimensions.

Method 2 is the same as Method 1 except that Method 2 takes an additional argument sc that can be used to limit the kernel execution to a subset of the coordinates.

Method 3 is the same as Method 1 except that instead of taking Allocation inputs it takes Java array inputs. This is a convenience that saves you from having to write code to explicitly create an Allocation and copy data to it from a Java array. However, using Method 3 instead of Method 1 does not increase the performance of the code . For each input array, Method 3 creates a temporary 1-dimensional Allocation with the appropriate Element type and setAutoPadding(boolean) enabled, and copies the array to the Allocation as if by the appropriate copyFrom() method of Allocation . It then calls Method 1, passing those temporary Allocations.

NOTE: If your application will make multiple kernel calls with the same array, or with different arrays of the same dimensions and Element type, you may improve performance by explicitly creating, populating, and reusing Allocations yourself, instead of by using Method 3.

javaFutureType , the return type of the reflected reduction methods, is a reflected static nested class within the ScriptC_ filename class. It represents the future result of a reduction kernel run. To obtain the actual result of the run, call the get() method of that class, which returns a value of type javaResultType . get() is synchronous .

কোটলিন

class ScriptC_filename(rs: RenderScript) : ScriptC(…) {
    object javaFutureType {
        fun get(): javaResultType { … }
    }
}

জাভা

public class ScriptC_filename extends ScriptC {
  public static class javaFutureType {
    public javaResultType get() { … }
  }
}

javaResultType is determined from the resultType of the outconverter function . Unless resultType is an unsigned type (scalar, vector, or array), javaResultType is the directly corresponding Java type. If resultType is an unsigned type and there is a larger Java signed type, then javaResultType is that larger Java signed type; otherwise, it is the directly corresponding Java type. যেমন:

  • If resultType is int , int2 , or int[15] , then javaResultType is int , Int2 , or int[] . All values of resultType can be represented by javaResultType .
  • If resultType is uint , uint2 , or uint[15] , then javaResultType is long , Long2 , or long[] . All values of resultType can be represented by javaResultType .
  • If resultType is ulong , ulong2 , or ulong[15] , then javaResultType is long , Long2 , or long[] . There are certain values of resultType that cannot be represented by javaResultType .

javaFutureType is the future result type corresponding to the resultType of the outconverter function .

  • If resultType is not an array type, then javaFutureType is result_ resultType .
  • If resultType is an array of length Count with members of type memberType , then javaFutureType is resultArray Count _ memberType .

যেমন:

কোটলিন

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> = …
    }
}

জাভা

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() { … }
  }
}

If javaResultType is an object type (including an array type), each call to javaFutureType .get() on the same instance will return the same object.

If javaResultType cannot represent all values of type resultType , and a reduction kernel produces an unrepresentible value, then javaFutureType .get() throws an exception.

Method 3 and devecSiInXType

devecSiInXType is the Java type corresponding to the inXType of the corresponding argument of the accumulator function . Unless inXType is an unsigned type or a vector type, devecSiInXType is the directly corresponding Java type. If inXType is an unsigned scalar type, then devecSiInXType is the Java type directly corresponding to the signed scalar type of the same size. If inXType is a signed vector type, then devecSiInXType is the Java type directly corresponding to the vector component type. If inXType is an unsigned vector type, then devecSiInXType is the Java type directly corresponding to the signed scalar type of the same size as the vector component type. যেমন:

  • If inXType is int , then devecSiInXType is int .
  • If inXType is int2 , then devecSiInXType is int . The array is a flattened representation: It has twice as many scalar Elements as the Allocation has 2-component vector Elements. This is the same way that the copyFrom() methods of Allocation work.
  • If inXType is uint , then deviceSiInXType is int . A signed value in the Java array is interpreted as an unsigned value of the same bitpattern in the Allocation. This is the same way that the copyFrom() methods of Allocation work.
  • If inXType is uint2 , then deviceSiInXType is int . This is a combination of the way int2 and uint are handled: The array is a flattened representation, and Java array signed values are interpreted as RenderScript unsigned Element values.

Note that for Method 3 , input types are handled differently than result types:

  • A script's vector input is flattened on the Java side, whereas a script's vector result is not.
  • A script's unsigned input is represented as a signed input of the same size on the Java side, whereas a script's unsigned result is represented as a widened signed type on the Java side (except in the case of ulong ).

More example reduction kernels

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

অতিরিক্ত কোড নমুনা

The BasicRenderScript , RenderScriptIntrinsic , and Hello Compute samples further demonstrate the use of the APIs covered on this page.

,

রেন্ডারস্ক্রিপ্ট হল Android-এ উচ্চ কার্যসম্পাদনে গণনামূলকভাবে নিবিড় কাজ চালানোর জন্য একটি কাঠামো। RenderScript is primarily oriented for use with data-parallel computation, although serial workloads can benefit as well. রেন্ডারস্ক্রিপ্ট রানটাইম একটি ডিভাইসে উপলব্ধ প্রসেসরের কাজকে সমান্তরাল করে, যেমন মাল্টি-কোর সিপিইউ এবং জিপিইউ। এটি আপনাকে কাজের সময় নির্ধারণের পরিবর্তে অ্যালগরিদম প্রকাশের উপর ফোকাস করতে দেয়। রেন্ডারস্ক্রিপ্ট বিশেষ করে ইমেজ প্রসেসিং, কম্পিউটেশনাল ফটোগ্রাফি বা কম্পিউটার ভিশন সম্পাদনকারী অ্যাপ্লিকেশনের জন্য উপযোগী।

To begin with RenderScript, there are two main concepts you should understand:

  • The language itself is a C99-derived language for writing high-performance compute code. Writing a RenderScript Kernel describes how to use it to write compute kernels.
  • The control API is used for managing the lifetime of RenderScript resources and controlling kernel execution. It is available in three different languages: Java, C++ in Android NDK, and the C99-derived kernel language itself. Using RenderScript from Java Code and Single-Source RenderScript describe the first and the third options, respectively.

Writing a RenderScript Kernel

A RenderScript kernel typically resides in a .rs file in the <project_root>/src/rs directory; each .rs file is called a script . Every script contains its own set of kernels, functions, and variables. A script can contain:

  • A pragma declaration ( #pragma version(1) ) that declares the version of the RenderScript kernel language used in this script. Currently, 1 is the only valid value.
  • A pragma declaration ( #pragma rs java_package_name(com.example.app) ) that declares the package name of the Java classes reflected from this script. Note that your .rs file must be part of your application package, and not in a library project.
  • Zero or more invokable functions . An invokable function is a single-threaded RenderScript function that you can call from your Java code with arbitrary arguments. These are often useful for initial setup or serial computations within a larger processing pipeline.
  • Zero or more script globals . A script global is similar to a global variable in C. You can access script globals from Java code, and these are often used for parameter passing to RenderScript kernels. Script globals are explained in more detail here .

  • Zero or more compute kernels . A compute kernel is a function or collection of functions that you can direct the RenderScript runtime to execute in parallel across a collection of data. There are two kinds of compute kernels: mapping kernels (also called foreach kernels) and reduction kernels.

    A mapping kernel is a parallel function that operates on a collection of Allocations of the same dimensions. By default, it executes once for every coordinate in those dimensions. It is typically (but not exclusively) used to transform a collection of input Allocations to an output Allocation one Element at a time.

    • Here is an example of a simple mapping kernel :

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

      In most respects, this is identical to a standard C function. The RS_KERNEL property applied to the function prototype specifies that the function is a RenderScript mapping kernel instead of an invokable function. The in argument is automatically filled in based on the input Allocation passed to the kernel launch. The arguments x and y are discussed below . The value returned from the kernel is automatically written to the appropriate location in the output Allocation . By default, this kernel is run across its entire input Allocation , with one execution of the kernel function per Element in the Allocation .

      A mapping kernel may have one or more input Allocations , a single output Allocation , or both. The RenderScript runtime checks to ensure that all input and output Allocations have the same dimensions, and that the Element types of the input and output Allocations match the kernel's prototype; if either of these checks fails, RenderScript throws an exception.

      NOTE: Before Android 6.0 (API level 23), a mapping kernel may not have more than one input Allocation .

      If you need more input or output Allocations than the kernel has, those objects should be bound to rs_allocation script globals and accessed from a kernel or invokable function via rsGetElementAt_ type () or rsSetElementAt_ type () .

      NOTE: RS_KERNEL is a macro defined automatically by RenderScript for your convenience:

      #define RS_KERNEL __attribute__((kernel))
      

    A reduction kernel is a family of functions that operates on a collection of input Allocations of the same dimensions. By default, its accumulator function executes once for every coordinate in those dimensions. It is typically (but not exclusively) used to "reduce" a collection of input Allocations to a single value.

    • Here is an example of a simple reduction kernel that adds up the Elements of its input:

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

      A reduction kernel consists of one or more user-written functions. #pragma rs reduce is used to define the kernel by specifying its name ( addint , in this example) and the names and roles of the functions that make up the kernel (an accumulator function addintAccum , in this example). All such functions must be static . A reduction kernel always requires an accumulator function; it may also have other functions, depending on what you want the kernel to do.

      A reduction kernel accumulator function must return void and must have at least two arguments. The first argument ( accum , in this example) is a pointer to an accumulator data item and the second ( val , in this example) is automatically filled in based on the input Allocation passed to the kernel launch. The accumulator data item is created by the RenderScript runtime; by default, it is initialized to zero. By default, this kernel is run across its entire input Allocation , with one execution of the accumulator function per Element in the Allocation . By default, the final value of the accumulator data item is treated as the result of the reduction, and is returned to Java. The RenderScript runtime checks to ensure that the Element type of the input Allocation matches the accumulator function's prototype; if it does not match, RenderScript throws an exception.

      A reduction kernel has one or more input Allocations but no output Allocations .

      Reduction kernels are explained in more detail here .

      Reduction kernels are supported in Android 7.0 (API level 24) and later.

    A mapping kernel function or a reduction kernel accumulator function may access the coordinates of the current execution using the special arguments x , y , and z , which must be of type int or uint32_t . এই আর্গুমেন্ট ঐচ্ছিক.

    A mapping kernel function or a reduction kernel accumulator function may also take the optional special argument context of type rs_kernel_context . It is needed by a family of runtime APIs that are used to query certain properties of the current execution -- for example, rsGetDimX . (The context argument is available in Android 6.0 (API level 23) and later.)

  • An optional init() function. The init() function is a special type of invokable function that RenderScript runs when the script is first instantiated. This allows for some computation to occur automatically at script creation.
  • Zero or more static script globals and functions . A static script global is equivalent to a script global except that it cannot be accessed from Java code. A static function is a standard C function that can be called from any kernel or invokable function in the script but is not exposed to the Java API. If a script global or function does not need to be accessed from Java code, it is highly recommended that it be declared static .

Setting floating point precision

You can control the required level of floating point precision in a script. This is useful if full IEEE 754-2008 standard (used by default) is not required. The following pragmas can set a different level of floating point precision:

  • #pragma rs_fp_full (default if nothing is specified): For apps that require floating point precision as outlined by the IEEE 754-2008 standard.
  • #pragma rs_fp_relaxed : For apps that don't require strict IEEE 754-2008 compliance and can tolerate less precision. This mode enables flush-to-zero for denorms and round-towards-zero.
  • #pragma rs_fp_imprecise : For apps that don't have stringent precision requirements. This mode enables everything in rs_fp_relaxed along with the following:
    • Operations resulting in -0.0 can return +0.0 instead.
    • Operations on INF and NAN are undefined.

Most applications can use rs_fp_relaxed without any side effects. This may be very beneficial on some architectures due to additional optimizations only available with relaxed precision (such as SIMD CPU instructions).

Accessing RenderScript APIs from Java

When developing an Android application that uses RenderScript, you can access its API from Java in one of two ways:

Here are the tradeoffs:

  • If you use the Support Library APIs, the RenderScript portion of your application will be compatible with devices running Android 2.3 (API level 9) and higher, regardless of which RenderScript features you use. This allows your application to work on more devices than if you use the native ( android.renderscript ) APIs.
  • Certain RenderScript features are not available through the Support Library APIs.
  • If you use the Support Library APIs, you will get (possibly significantly) larger APKs than if you use the native ( android.renderscript ) APIs.

Using the RenderScript Support Library APIs

In order to use the Support Library RenderScript APIs, you must configure your development environment to be able to access them. The following Android SDK tools are required for using these APIs:

  • Android SDK Tools revision 22.2 or higher
  • Android SDK Build-tools revision 18.1.0 or higher

Note that starting from Android SDK Build-tools 24.0.0, Android 2.2 (API level 8) is no longer supported.

You can check and update the installed version of these tools in the Android SDK Manager .

To use the Support Library RenderScript APIs:

  1. Make sure you have the required Android SDK version installed.
  2. Update the settings for the Android build process to include the RenderScript settings:
    • Open the build.gradle file in the app folder of your application module.
    • Add the following RenderScript settings to the file:

      গ্রোভি

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

      কোটলিন

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

      The settings listed above control specific behavior in the Android build process:

      • renderscriptTargetApi - Specifies the bytecode version to be generated. We recommend you set this value to the lowest API level able to provide all the functionality you are using and set renderscriptSupportModeEnabled to true . Valid values for this setting are any integer value from 11 to the most recently released API level. If your minimum SDK version specified in your application manifest is set to a different value, that value is ignored and the target value in the build file is used to set the minimum SDK version.
      • renderscriptSupportModeEnabled - Specifies that the generated bytecode should fall back to a compatible version if the device it is running on does not support the target version.
  3. In your application classes that use RenderScript, add an import for the Support Library classes:

    কোটলিন

    import android.support.v8.renderscript.*
    

    জাভা

    import android.support.v8.renderscript.*;
    

Using RenderScript from Java or Kotlin Code

Using RenderScript from Java or Kotlin code relies on the API classes located in the android.renderscript or the android.support.v8.renderscript package. Most applications follow the same basic usage pattern:

  1. Initialize a RenderScript context. The RenderScript context, created with create(Context) , ensures that RenderScript can be used and provides an object to control the lifetime of all subsequent RenderScript objects. You should consider context creation to be a potentially long-running operation, since it may create resources on different pieces of hardware; it should not be in an application's critical path if at all possible. Typically, an application will have only a single RenderScript context at a time.
  2. Create at least one Allocation to be passed to a script. An Allocation is a RenderScript object that provides storage for a fixed amount of data. Kernels in scripts take Allocation objects as their input and output, and Allocation objects can be accessed in kernels using rsGetElementAt_ type () and rsSetElementAt_ type () when bound as script globals. Allocation objects allow arrays to be passed from Java code to RenderScript code and vice-versa. Allocation objects are typically created using createTyped() or createFromBitmap() .
  3. Create whatever scripts are necessary. There are two types of scripts available to you when using RenderScript:
    • ScriptC : These are the user-defined scripts as described in Writing a RenderScript Kernel above. Every script has a Java class reflected by the RenderScript compiler in order to make it easy to access the script from Java code; this class has the name ScriptC_ filename . For example, if the mapping kernel above were located in invert.rs and a RenderScript context were already located in mRenderScript , the Java or Kotlin code to instantiate the script would be:

      কোটলিন

      val invert = ScriptC_invert(renderScript)
      

      জাভা

      ScriptC_invert invert = new ScriptC_invert(renderScript);
      
    • ScriptIntrinsic : These are built-in RenderScript kernels for common operations, such as Gaussian blur, convolution, and image blending. For more information, see the subclasses of ScriptIntrinsic .
  4. Populate Allocations with data. Except for Allocations created with createFromBitmap() , an Allocation is populated with empty data when it is first created. To populate an Allocation, use one of the "copy" methods in Allocation . The "copy" methods are synchronous .
  5. Set any necessary script globals . You may set globals using methods in the same ScriptC_ filename class named set_ globalname . For example, in order to set an int variable named threshold , use the Java method set_threshold(int) ; and in order to set an rs_allocation variable named lookup , use the Java method set_lookup(Allocation) . The set methods are asynchronous .
  6. Launch the appropriate kernels and invokable functions.

    Methods to launch a given kernel are reflected in the same ScriptC_ filename class with methods named forEach_ mappingKernelName () or reduce_ reductionKernelName () . These launches are asynchronous . Depending on the arguments to the kernel, the method takes one or more Allocations, all of which must have the same dimensions. By default, a kernel executes over every coordinate in those dimensions; to execute a kernel over a subset of those coordinates, pass an appropriate Script.LaunchOptions as the last argument to the forEach or reduce method.

    Launch invokable functions using the invoke_ functionName methods reflected in the same ScriptC_ filename class. These launches are asynchronous .

  7. Retrieve data from Allocation objects and javaFutureType objects. In order to access data from an Allocation from Java code, you must copy that data back to Java using one of the "copy" methods in Allocation . In order to obtain the result of a reduction kernel, you must use the javaFutureType .get() method. The "copy" and get() methods are synchronous .
  8. Tear down the RenderScript context. You can destroy the RenderScript context with destroy() or by allowing the RenderScript context object to be garbage collected. This causes any further use of any object belonging to that context to throw an exception.

Asynchronous execution model

The reflected forEach , invoke , reduce , and set methods are asynchronous -- each may return to Java before completing the requested action. However, the individual actions are serialized in the order in which they are launched.

The Allocation class provides "copy" methods to copy data to and from Allocations. A "copy" method is synchronous, and is serialized with respect to any of the asynchronous actions above that touch the same Allocation.

The reflected javaFutureType classes provide a get() method to obtain the result of a reduction. get() is synchronous, and is serialized with respect to the reduction (which is asynchronous).

Single-Source RenderScript

Android 7.0 (API level 24) introduces a new programming feature called Single-Source RenderScript , in which kernels are launched from the script where they are defined, rather than from Java. This approach is currently limited to mapping kernels, which are simply referred to as "kernels" in this section for conciseness. This new feature also supports creating allocations of type rs_allocation from inside the script. It is now possible to implement a whole algorithm solely within a script, even if multiple kernel launches are required. The benefit is twofold: more readable code, because it keeps the implementation of an algorithm in one language; and potentially faster code, because of fewer transitions between Java and RenderScript across multiple kernel launches.

In Single-Source RenderScript, you write kernels as described in Writing a RenderScript Kernel . You then write an invokable function that calls rsForEach() to launch them. That API takes a kernel function as the first parameter, followed by input and output allocations. A similar API rsForEachWithOptions() takes an extra argument of type rs_script_call_t , which specifies a subset of the elements from the input and output allocations for the kernel function to process.

To start RenderScript computation, you call the invokable function from Java. Follow the steps in Using RenderScript from Java Code . In the step launch the appropriate kernels , call the invokable function using invoke_ function_name () , which will start the whole computation, including launching kernels.

Allocations are often needed to save and pass intermediate results from one kernel launch to another. You can create them using rsCreateAllocation() . One easy-to-use form of that API is rsCreateAllocation_<T><W>(…) , where T is the data type for an element, and W is the vector width for the element. The API takes the sizes in dimensions X, Y, and Z as arguments. For 1D or 2D allocations, the size for dimension Y or Z can be omitted. For example, rsCreateAllocation_uchar4(16384) creates a 1D allocation of 16384 elements, each of which is of type uchar4 .

Allocations are managed by the system automatically. You do not have to explicitly release or free them. However, you can call rsClearObject(rs_allocation* alloc) to indicate you no longer need the handle alloc to the underlying allocation, so that the system can free up resources as early as possible.

The Writing a RenderScript Kernel section contains an example kernel that inverts an image. The example below expands that to apply more than one effect to an image, using Single-Source RenderScript. It includes another kernel, greyscale , which turns a color image into black-and-white. An invokable function process() then applies those two kernels consecutively to an input image, and produces an output image. Allocations for both the input and the output are passed in as arguments of type 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);
}

You can call the process() function from Java or Kotlin as follows:

কোটলিন

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)

জাভা

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

This example shows how an algorithm that involves two kernel launches can be implemented completely in the RenderScript language itself. Without Single-Source RenderScript, you would have to launch both kernels from the Java code, separating kernel launches from kernel definitions and making it harder to understand the whole algorithm. Not only is the Single-Source RenderScript code easier to read, it also eliminates the transitioning between Java and the script across kernel launches. Some iterative algorithms may launch kernels hundreds of times, making the overhead of such transitioning considerable.

Script Globals

A script global is an ordinary non- static global variable in a script ( .rs ) file. For a script global named var defined in the file filename .rs , there will be a method get_ var reflected in the class ScriptC_ filename . Unless the global is const , there will also be a method set_ var .

A given script global has two separate values -- a Java value and a script value. These values behave as follows:

  • If var has a static initializer in the script, it specifies the initial value of var in both Java and the script. Otherwise, that initial value is zero.
  • Accesses to var within the script read and write its script value.
  • The get_ var method reads the Java value.
  • The set_ var method (if it exists) writes the Java value immediately, and writes the script value asynchronously .

NOTE: This means that except for any static initializer in the script, values written to a global from within a script are not visible to Java.

Reduction Kernels in Depth

Reduction is the process of combining a collection of data into a single value. This is a useful primitive in parallel programming, with applications such as the following:

  • computing the sum or product over all the data
  • computing logical operations ( and , or , xor ) over all the data
  • finding the minimum or maximum value within the data
  • searching for a specific value or for the coordinate of a specific value within the data

In Android 7.0 (API level 24) and later, RenderScript supports reduction kernels to allow efficient user-written reduction algorithms. You may launch reduction kernels on inputs with 1, 2, or 3 dimensions.

An example above shows a simple addint reduction kernel. Here is a more complicated findMinAndMax reduction kernel that finds the locations of the minimum and maximum long values in a 1-dimensional Allocation :

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

NOTE: There are more example reduction kernels here .

In order to run a reduction kernel, the RenderScript runtime creates one or more variables called accumulator data items to hold the state of the reduction process. The RenderScript runtime picks the number of accumulator data items in such a way as to maximize performance. The type of the accumulator data items ( accumType ) is determined by the kernel's accumulator function -- the first argument to that function is a pointer to an accumulator data item. By default, every accumulator data item is initialized to zero (as if by memset ); however, you may write an initializer function to do something different.

Example: In the addint kernel, the accumulator data items (of type int ) are used to add up input values. There is no initializer function, so each accumulator data item is initialized to zero.

Example: In the findMinAndMax kernel, the accumulator data items (of type MinAndMax ) are used to keep track of the minimum and maximum values found so far. There is an initializer function to set these to LONG_MAX and LONG_MIN , respectively; and to set the locations of these values to -1, indicating that the values are not actually present in the (empty) portion of the input that has been processed.

RenderScript calls your accumulator function once for every coordinate in the input(s). Typically, your function should update the accumulator data item in some way according to the input.

Example: In the addint kernel, the accumulator function adds the value of an input Element to the accumulator data item.

Example: In the findMinAndMax kernel, the accumulator function checks to see whether the value of an input Element is less than or equal to the minimum value recorded in the accumulator data item and/or greater than or equal to the maximum value recorded in the accumulator data item, and updates the accumulator data item accordingly.

After the accumulator function has been called once for every coordinate in the input(s), RenderScript must combine the accumulator data items together into a single accumulator data item. You may write a combiner function to do this. If the accumulator function has a single input and no special arguments , then you do not need to write a combiner function; RenderScript will use the accumulator function to combine the accumulator data items. (You may still write a combiner function if this default behavior is not what you want.)

Example: In the addint kernel, there is no combiner function, so the accumulator function will be used. This is the correct behavior, because if we split a collection of values into two pieces, and we add up the values in those two pieces separately, adding up those two sums is the same as adding up the entire collection.

Example: In the findMinAndMax kernel, the combiner function checks to see whether the minimum value recorded in the "source" accumulator data item *val is less than the minimum value recorded in the "destination" accumulator data item *accum , and updates *accum সেই অনুযায়ী It does similar work for the maximum value. This updates *accum to the state it would have had if all of the input values had been accumulated into *accum rather than some into *accum and some into *val .

After all of the accumulator data items have been combined, RenderScript determines the result of the reduction to return to Java. You may write an outconverter function to do this. You do not need to write an outconverter function if you want the final value of the combined accumulator data items to be the result of the reduction.

Example: In the addint kernel, there is no outconverter function. The final value of the combined data items is the sum of all Elements of the input, which is the value we want to return.

Example: In the findMinAndMax kernel, the outconverter function initializes an int2 result value to hold the locations of the minimum and maximum values resulting from the combination of all of the accumulator data items.

Writing a reduction kernel

#pragma rs reduce defines a reduction kernel by specifying its name and the names and roles of the functions that make up the kernel. All such functions must be static . A reduction kernel always requires an accumulator function; you can omit some or all of the other functions, depending on what you want the kernel to do.

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

The meaning of the items in the #pragma is as follows:

  • reduce( kernelName ) (mandatory): Specifies that a reduction kernel is being defined. A reflected Java method reduce_ kernelName will launch the kernel.
  • initializer( initializerName ) (optional): Specifies the name of the initializer function for this reduction kernel. When you launch the kernel, RenderScript calls this function once for each accumulator data item . The function must be defined like this:

    static void initializerName(accumType *accum) { … }

    accum is a pointer to an accumulator data item for this function to initialize.

    If you do not provide an initializer function, RenderScript initializes every accumulator data item to zero (as if by memset ), behaving as if there were an initializer function that looks like this:

    static void initializerName(accumType *accum) {
      memset(accum, 0, sizeof(*accum));
    }
  • accumulator( accumulatorName ) (mandatory): Specifies the name of the accumulator function for this reduction kernel. When you launch the kernel, RenderScript calls this function once for every coordinate in the input(s), to update an accumulator data item in some way according to the input(s). The function must be defined like this:

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

    accum is a pointer to an accumulator data item for this function to modify. in1 through in N are one or more arguments that are automatically filled in based on the inputs passed to the kernel launch, one argument per input. The accumulator function may optionally take any of the special arguments .

    An example kernel with multiple inputs is dotProduct .

  • combiner( combinerName )

    (optional): Specifies the name of the combiner function for this reduction kernel. After RenderScript calls the accumulator function once for every coordinate in the input(s), it calls this function as many times as necessary to combine all accumulator data items into a single accumulator data item. The function must be defined like this:

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

    accum is a pointer to a "destination" accumulator data item for this function to modify. other is a pointer to a "source" accumulator data item for this function to "combine" into *accum .

    NOTE: It is possible that *accum , *other , or both have been initialized but have never been passed to the accumulator function; that is, one or both have never been updated according to any input data. For example, in the findMinAndMax kernel, the combiner function fMMCombiner explicitly checks for idx < 0 because that indicates such an accumulator data item, whose value is INITVAL .

    If you do not provide a combiner function, RenderScript uses the accumulator function in its place, behaving as if there were a combiner function that looks like this:

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

    A combiner function is mandatory if the kernel has more than one input, if the input data type is not the same as the accumulator data type, or if the accumulator function takes one or more special arguments .

  • outconverter( outconverterName ) (optional): Specifies the name of the outconverter function for this reduction kernel. After RenderScript combines all of the accumulator data items, it calls this function to determine the result of the reduction to return to Java. The function must be defined like this:

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

    result is a pointer to a result data item (allocated but not initialized by the RenderScript runtime) for this function to initialize with the result of the reduction. resultType is the type of that data item, which need not be the same as accumType . accum is a pointer to the final accumulator data item computed by the combiner function .

    If you do not provide an outconverter function, RenderScript copies the final accumulator data item to the result data item, behaving as if there were an outconverter function that looks like this:

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

    If you want a different result type than the accumulator data type, then the outconverter function is mandatory.

Note that a kernel has input types, an accumulator data item type, and a result type, none of which need to be the same. For example, in the findMinAndMax kernel, the input type long , accumulator data item type MinAndMax , and result type int2 are all different.

What can't you assume?

You must not rely on the number of accumulator data items created by RenderScript for a given kernel launch. There is no guarantee that two launches of the same kernel with the same input(s) will create the same number of accumulator data items.

You must not rely on the order in which RenderScript calls the initializer, accumulator, and combiner functions; it may even call some of them in parallel. There is no guarantee that two launches of the same kernel with the same input will follow the same order. The only guarantee is that only the initializer function will ever see an uninitialized accumulator data item. যেমন:

  • There is no guarantee that al