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

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

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

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

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

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

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

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

    ম্যাপিং কার্নেল হল একটি সমান্তরাল ফাংশন যা একই মাত্রার Allocations সংগ্রহের উপর কাজ করে। ডিফল্টরূপে, এটি সেই মাত্রার প্রতিটি স্থানাঙ্কের জন্য একবার কার্যকর হয়। এটি সাধারণত (কিন্তু একচেটিয়াভাবে নয়) ইনপুট Allocations সংগ্রহকে একবারে একটি Element আউটপুট Allocation রূপান্তর করতে ব্যবহৃত হয়।

    • এখানে একটি সাধারণ ম্যাপিং কার্নেলের উদাহরণ দেওয়া হল:

      uchar4 RS_KERNEL invert(uchar4 in, uint32_t x, uint32_t y) {
        uchar4 out = in;
        out.r = 255 - in.r;
        out.g = 255 - in.g;
        out.b = 255 - in.b;
        return out;
      }

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

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

      দ্রষ্টব্য: অ্যান্ড্রয়েড ৬.০ (এপিআই লেভেল ২৩) এর আগে, একটি ম্যাপিং কার্নেলে একাধিক ইনপুট 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 ) এবং কার্নেল তৈরি করে এমন ফাংশনগুলির নাম এবং ভূমিকা (এই উদাহরণে addintAccum ) উল্লেখ করে কার্নেলকে সংজ্ঞায়িত করতে ব্যবহৃত হয়। এই ধরণের সমস্ত ফাংশন অবশ্যই static হতে হবে। একটি রিডাকশন কার্নেলের জন্য সর্বদা একটি accumulator ফাংশন প্রয়োজন; আপনি কার্নেলটি কী করতে চান তার উপর নির্ভর করে এর অন্যান্য ফাংশনও থাকতে পারে accumulator

      একটি রিডাকশন কার্নেল অ্যাকিউমুলেটর ফাংশন অবশ্যই void রিটার্ন করবে এবং কমপক্ষে দুটি আর্গুমেন্ট থাকতে হবে। প্রথম আর্গুমেন্ট ( এই উদাহরণে accum ) হল একটি অ্যাকিউমুলেটর ডেটা আইটেমের পয়েন্টার এবং দ্বিতীয়টি ( এই উদাহরণে val ) কার্নেল লঞ্চে প্রেরিত ইনপুট Allocation উপর ভিত্তি করে স্বয়ংক্রিয়ভাবে পূরণ করা হয়। অ্যাকিউমুলেটর ডেটা আইটেমটি রেন্ডারস্ক্রিপ্ট রানটাইম দ্বারা তৈরি করা হয়; ডিফল্টরূপে, এটি শূন্যে শুরু হয়। ডিফল্টরূপে, এই কার্নেলটি তার সম্পূর্ণ ইনপুট Allocation জুড়ে চালানো হয়, Allocation প্রতি Element অ্যাকিউমুলেটর ফাংশনের একটি এক্সিকিউশন সহ। ডিফল্টরূপে, অ্যাকিউমুলেটর ডেটা আইটেমের চূড়ান্ত মান হ্রাসের ফলাফল হিসাবে বিবেচিত হয় এবং জাভাতে ফেরত পাঠানো হয়। রেন্ডারস্ক্রিপ্ট রানটাইম পরীক্ষা করে নিশ্চিত করে যে ইনপুট অ্যালোকেশনের Element টাইপ অ্যাকিউমুলেটর ফাংশনের প্রোটোটাইপের সাথে মেলে; যদি এটি মেলে না, তাহলে রেন্ডারস্ক্রিপ্ট একটি ব্যতিক্রম ছুঁড়ে দেয়।

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

      রিডাকশন কার্নেলগুলি এখানে আরও বিশদে ব্যাখ্যা করা হয়েছে।

      রিডাকশন কার্নেলগুলি অ্যান্ড্রয়েড ৭.০ (এপিআই লেভেল ২৪) এবং পরবর্তী সংস্করণগুলিতে সমর্থিত।

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

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

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

ভাসমান বিন্দুর নির্ভুলতা নির্ধারণ করা হচ্ছে

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

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

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

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

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

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

এখানে বিনিময়গুলি দেওয়া হল:

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

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

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

  • অ্যান্ড্রয়েড এসডিকে টুলস রিভিশন ২২.২ বা তার বেশি
  • অ্যান্ড্রয়েড এসডিকে বিল্ড-টুল সংস্করণ ১৮.১.০ বা তার বেশি

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

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

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

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

      খাঁজকাটা

              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.*;

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

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

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

      কোটলিন

      val invert = ScriptC_invert(renderScript)

      জাভা

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

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

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

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

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

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

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

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

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

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

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

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

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

বরাদ্দগুলি স্বয়ংক্রিয়ভাবে সিস্টেম দ্বারা পরিচালিত হয়। আপনাকে স্পষ্টভাবে এগুলি ছেড়ে দিতে বা মুক্ত করতে হবে না। তবে, আপনি rsClearObject(rs_allocation* alloc) কল করে বোঝাতে পারেন যে আপনার আর অন্তর্নিহিত বরাদ্দের জন্য হ্যান্ডেল alloc প্রয়োজন নেই, যাতে সিস্টেম যত তাড়াতাড়ি সম্ভব সম্পদ খালি করতে পারে।

"Writing a RenderScript Kernel" বিভাগে একটি উদাহরণ কার্নেল রয়েছে যা একটি চিত্রকে উল্টে দেয়। নীচের উদাহরণটি Single-Source RenderScript ব্যবহার করে একটি চিত্রে একাধিক প্রভাব প্রয়োগ করার জন্য এটিকে প্রসারিত করে। এতে আরেকটি কার্নেল, greyscale , রয়েছে, যা একটি রঙিন চিত্রকে কালো-সাদা রঙে রূপান্তরিত করে। একটি invokable ফাংশন 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 গ্লোবাল ভেরিয়েবল। filename .rs অনুসারে var নামের একটি স্ক্রিপ্ট গ্লোবালের জন্য, ScriptC_ filename অনুসারে একটি পদ্ধতি get_ var প্রতিফলিত হবে। যদি না গ্লোবাল const হয়, তাহলে একটি পদ্ধতি set_ var ও থাকবে।

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

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

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

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

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

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

অ্যান্ড্রয়েড ৭.০ (এপিআই লেভেল ২৪) এবং পরবর্তী সংস্করণগুলিতে, রেন্ডারস্ক্রিপ্ট ব্যবহারকারী-লিখিত দক্ষ রিডাকশন অ্যালগরিদমগুলিকে সক্ষম করার জন্য রিডাকশন কার্নেলগুলিকে সমর্থন করে। আপনি ১, ২, অথবা ৩ মাত্রা সহ ইনপুটগুলিতে রিডাকশন কার্নেল চালু করতে পারেন।

উপরের একটি উদাহরণে একটি সহজ অ্যাডিন্ট রিডাকশন কার্নেল দেখানো হয়েছে। এখানে আরও জটিল 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 ধরণের) এখন পর্যন্ত পাওয়া সর্বনিম্ন এবং সর্বোচ্চ মানগুলির ট্র্যাক রাখতে ব্যবহৃত হয়। একটি initializer ফাংশন রয়েছে যা এগুলিকে যথাক্রমে LONG_MAX এবং LONG_MIN এ সেট করে; এবং এই মানগুলির অবস্থান -1 এ সেট করে, যা নির্দেশ করে যে মানগুলি আসলে ইনপুটের (খালি) অংশে উপস্থিত নেই যা প্রক্রিয়া করা হয়েছে।

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

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

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

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

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

উদাহরণ: findMinAndMax কার্নেলে, কম্বিনার ফাংশন পরীক্ষা করে যে "source" অ্যাকিউমুলেটর ডেটা আইটেম *val এ রেকর্ড করা সর্বনিম্ন মান "destination" অ্যাকিউমুলেটর ডেটা আইটেম *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 ) (বাধ্যতামূলক): একটি reduction কার্নেল সংজ্ঞায়িত করা হচ্ছে তা নির্দিষ্ট করে। একটি প্রতিফলিত জাভা পদ্ধতি reduce_ kernelName কার্নেলটি চালু করবে।
  • initializer( initializerName ) (ঐচ্ছিক): এই রিডাকশন কার্নেলের জন্য initializer ফাংশনের নাম নির্দিষ্ট করে। যখন আপনি কার্নেলটি চালু করেন, তখন RenderScript প্রতিটি অ্যাকিউমুলেটর ডেটা আইটেমের জন্য এই ফাংশনটিকে একবার কল করে। ফাংশনটি এইভাবে সংজ্ঞায়িত করতে হবে:

    static void initializerName(accumType *accum) {  }

    accum হল একটি অ্যাকিউমুলেটর ডেটা আইটেমের একটি পয়েন্টার যার মাধ্যমে এই ফাংশনটি আরম্ভ করা হয়।

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

    static void initializerName(accumType *accum) {
      memset(accum, 0, sizeof(*accum));
    }
  • accumulator( accumulatorName ) (বাধ্যতামূলক): এই রিডাকশন কার্নেলের জন্য accumulator ফাংশনের নাম নির্দিষ্ট করে। যখন আপনি কার্নেলটি চালু করেন, তখন RenderScript ইনপুট(গুলি) এর প্রতিটি স্থানাঙ্কের জন্য এই ফাংশনটি একবার কল করে, যাতে ইনপুট(গুলি) অনুসারে কোনওভাবে accumulator ডেটা আইটেম আপডেট করা যায়। ফাংশনটি এইভাবে সংজ্ঞায়িত করতে হবে:

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

    accum হল একটি অ্যাকিউমুলেটর ডেটা আইটেমের একটি পয়েন্টার যা এই ফাংশনটি পরিবর্তন করতে পারে। in1 থেকে in N পর্যন্ত এক বা একাধিক আর্গুমেন্ট যা কার্নেল লঞ্চে প্রেরিত ইনপুটগুলির উপর ভিত্তি করে স্বয়ংক্রিয়ভাবে পূরণ করা হয়, প্রতি ইনপুটটিতে একটি আর্গুমেন্ট। অ্যাকিউমুলেটর ফাংশন ঐচ্ছিকভাবে যেকোনো বিশেষ আর্গুমেন্ট নিতে পারে।

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

  • combiner( combinerName )

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

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

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

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

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

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

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

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

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

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

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

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

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

মনে রাখবেন যে একটি কার্নেলের ইনপুট টাইপ, একটি অ্যাকিউমুলেটর ডেটা আইটেম টাইপ এবং একটি রেজাল্ট টাইপ থাকে, যার কোনটিই একই রকম হতে হবে না। উদাহরণস্বরূপ, findMinAndMax কার্নেলে, ইনপুট টাইপ long , অ্যাকিউমুলেটর ডেটা আইটেম টাইপ MinAndMax এবং রেজাল্ট টাইপ int2 সবই আলাদা।

তুমি কী ধরে নিতে পারো না?

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. For example:

  • 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" . What does this mean? 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)
is the same as
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)
    which is the same as
    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 :

Kotlin

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

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

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

জাভা

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

Kotlin

val script = ScriptC_example(renderScript)

// 1D array
//   and obtain answer immediately
val input1 = intArrayOf()
val sum1: Int = script.reduce_addint(input1).get()  // Method 3

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

জাভা

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 .

Kotlin

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. For example:

  • 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 .

উদাহরণস্বরূপ:

Kotlin

class ScriptC_filename(rs: RenderScript) : ScriptC(…) {

    // for kernels with int result
    object result_int {
        fun get(): Int =     }

    // for kernels with int[10] result
    object resultArray10_int {
        fun get(): IntArray =     }

    // for kernels with int2 result
    //   note that the Kotlin type name "Int2" is not the same as the script type name "int2"
    object result_int2 {
        fun get(): Int2 =     }

    // for kernels with int2[10] result
    //   note that the Kotlin type name "Int2" is not the same as the script type name "int2"
    object resultArray10_int2 {
        fun get(): Array<Int2> =     }

    // for kernels with uint result
    //   note that the Kotlin type "long" is a wider signed type than the unsigned script type "uint"
    object result_uint {
        fun get(): Long =     }

    // for kernels with uint[10] result
    //   note that the Kotlin type "long" is a wider signed type than the unsigned script type "uint"
    object resultArray10_uint {
        fun get(): LongArray =     }

    // for kernels with uint2 result
    //   note that the Kotlin type "Long2" is a wider signed type than the unsigned script type "uint2"
    object result_uint2 {
        fun get(): Long2 =     }

    // for kernels with uint2[10] result
    //   note that the Kotlin type "Long2" is a wider signed type than the unsigned script type "uint2"
    object resultArray10_uint2 {
        fun get(): Array<Long2> =     }
}

জাভা

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. For example:

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

Additional code samples

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