RenderScript ওভারভিউ

RenderScript হল Android-এ হাই পারফর্ম্যান্সে কম্পিউটেশনাল ইনটেনসিভ টাস্ক রান করার ফ্রেমওয়ার্ক। RenderScript মূলত ডেটা-প্যারালাল কম্পিউটেশনের সাথে ব্যবহারের জন্য তৈরি করা হয়েছে, যদিও সিরিয়াল ওয়ার্কলোডও এর থেকে উপকৃত হতে পারে। RenderScript রানটাইম, ডিভাইসে উপলভ্য প্রসেসর জুড়ে কাজকে সমান্তরাল করে, যেমন মাল্টি-কোর সিপিইউ (CPU) এবং জিপিইউ (GPU)। এটি আপনাকে কাজ শিডিউল করার পরিবর্তে অ্যালগরিদম প্রকাশ করার উপর ফোকাস করতে দেয়। RenderScript বিশেষ করে ইমেজ প্রসেসিং, কম্পিউটেশনাল ফটোগ্রাফি বা কম্পিউটার ভিশন পারফর্ম করা অ্যাপ্লিকেশনের জন্য উপযোগী।

RenderScript-এর সাথে শুরু করতে, দুটি প্রধান ধারণা আপনাকে বুঝতে হবে:

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

RenderScript কার্নেল লেখা

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

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

  • শূন্য বা তার বেশি কম্পিউট কার্নেল। কম্পিউট কার্নেল হল একটি ফাংশন বা ফাংশনের সংগ্রহ যা আপনি ডেটার সংগ্রহ জুড়ে সমান্তরালভাবে কার্যকর করার জন্য RenderScript রানটাইমকে নির্দেশ করতে পারেন। দুই ধরনের কম্পিউট কার্নেল আছে: ম্যাপিং কার্নেল (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 রানটাইম চেক করে দেখে যে সব ইনপুট ও আউটপুট অ্যালোকেশনের একই ডাইমেনশন আছে কিনা এবং ইনপুট ও আউটপুট অ্যালোকেশনের Element ধরন কার্নেলের প্রোটোটাইপের সাথে ম্যাচ করে কিনা; এর মধ্যে কোনও একটি চেক ব্যর্থ হলে, RenderScript এক্সেপশন থ্রো করে।

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

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

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

      Android 7.0 (API লেভেল ২৪) এবং তার পরবর্তী ভার্সনে রিডাকশন কার্নেল কাজ করে।

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

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

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

ফ্লোটিং পয়েন্ট প্রিসিশন সেট করা হচ্ছে

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

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

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

Java থেকে RenderScript API অ্যাক্সেস করা

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

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

এখানে ট্রেড-অফগুলি দেওয়া হল:

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

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

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

  • Android SDK টুল রিভিশন 22.2 বা তার পরবর্তী যেকোনও ভার্সন
  • Android SDK বিল্ড-টুল রিভিশন 18.1.0 বা তার পরবর্তী যেকোনও ভার্সন

মনে রাখবেন, Android SDK Build-tools 24.0.0 থেকে শুরু করে, Android 2.2 (API লেভেল ৮) আর কাজ করে না।

আপনি Android SDK Manager-এ গিয়ে এইসব টুলের ইনস্টল করা ভার্সন চেক ও আপডেট করতে পারবেন।

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

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

      গ্ৰুভি

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

      Kotlin

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

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

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

    Kotlin

    import android.support.v8.renderscript.*

    জাভা

    import android.support.v8.renderscript.*;

Java বা Kotlin কোড থেকে RenderScript ব্যবহার করা

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

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

      Kotlin

      val invert = ScriptC_invert(renderScript)

      জাভা

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

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

    একই ScriptC_filename ক্লাসে প্রতিফলিত invoke_functionName পদ্ধতি ব্যবহার করে ইনভোক করা যায় এমন ফাংশন লঞ্চ করুন। এইসব লঞ্চ অ্যাসিঙ্ক্রোনাস।

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

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

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

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

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

সিঙ্গেল-সোর্স RenderScript

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

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

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

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

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

RenderScript কার্নেল লেখা বিভাগে একটি উদাহরণ কার্নেল আছে যা একটি ছবিকে উল্টে দেয়। নিচের উদাহরণে, Single-Source RenderScript ব্যবহার করে, একটি ছবিতে একাধিক এফেক্ট প্রয়োগ করার জন্য এটি আরও বড় করা হয়েছে। এতে আরেকটি কার্নেল, greyscale, অন্তর্ভুক্ত আছে, যা একটি রঙিন ছবিকে সাদা-কালোতে পরিণত করে। একটি ইনভোক করা যায় এমন ফাংশন process() তারপর সেই দুটি কার্নেল একটি ইনপুট ইমেজে পরপর প্রয়োগ করে এবং একটি আউটপুট ইমেজ তৈরি করে। ইনপুট ও আউটপুট, দুটির জন্যই বরাদ্দ করা মেমরি rs_allocation টাইপের আর্গুমেন্ট হিসেবে পাস করা হয়।

// File: singlesource.rs

#pragma version(1)
#pragma rs java_package_name(com.android.rssample)

static const float4 weight = {0.299f, 0.587f, 0.114f, 0.0f};

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

uchar4 RS_KERNEL greyscale(uchar4 in) {
  const float4 inF = rsUnpackColor8888(in);
  const float4 outF = (float4){ dot(inF, weight) };
  return rsPackColorTo8888(outF);
}

void process(rs_allocation inputImage, rs_allocation outputImage) {
  const uint32_t imageWidth = rsAllocationGetDimX(inputImage);
  const uint32_t imageHeight = rsAllocationGetDimY(inputImage);
  rs_allocation tmp = rsCreateAllocation_uchar4(imageWidth, imageHeight);
  rsForEach(invert, inputImage, tmp);
  rsForEach(greyscale, tmp, outputImage);
}

আপনি Java বা Kotlin থেকে process() ফাংশনটি এইভাবে কল করতে পারেন:

Kotlin

val RS: RenderScript = RenderScript.create(context)
val script = ScriptC_singlesource(RS)
val inputAllocation: Allocation = Allocation.createFromBitmapResource(
        RS,
        resources,
        R.drawable.image
)
val outputAllocation: Allocation = Allocation.createTyped(
        RS,
        inputAllocation.type,
        Allocation.USAGE_SCRIPT or Allocation.USAGE_IO_OUTPUT
)
script.invoke_process(inputAllocation, outputAllocation)

জাভা

// File SingleSource.java

RenderScript RS = RenderScript.create(context);
ScriptC_singlesource script = new ScriptC_singlesource(RS);
Allocation inputAllocation = Allocation.createFromBitmapResource(
    RS, getResources(), R.drawable.image);
Allocation outputAllocation = Allocation.createTyped(
    RS, inputAllocation.getType(),
    Allocation.USAGE_SCRIPT | Allocation.USAGE_IO_OUTPUT);
script.invoke_process(inputAllocation, outputAllocation);

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

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

স্ক্রিপ্ট গ্লোবাল হল স্ক্রিপ্ট (.rs) ফাইলে থাকা সাধারণ নন-static গ্লোবাল ভেরিয়েবেল। filename.rs ফাইলে সংজ্ঞায়িত var নামের গ্লোবাল স্ক্রিপ্টের জন্য, ScriptC_filename ক্লাসে get_var পদ্ধতি প্রতিফলিত হবে। গ্লোবাল const না হলে, set_var মেথডও থাকবে।

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

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

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

Reduction Kernels in Depth

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

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

Android 7.0 (API লেভেল ২৪) এবং তার পরবর্তী ভার্সনে, RenderScript রিডাকশন কার্নেল সাপোর্ট করে যাতে ব্যবহারকারীর লেখা রিডাকশন অ্যালগরিদম দক্ষতার সাথে কাজ করতে পারে। আপনি ১, ২ বা ৩টি ডাইমেনশন সহ ইনপুটে রিডাকশন কার্নেল লঞ্চ করতে পারেন।

উপরের উদাহরণে একটি সাধারণ addint রিডাকশন কার্নেল দেখানো হয়েছে। এখানে আরও জটিল findMinAndMax রিডাকশন কার্নেল দেওয়া হল যা ১-ডাইমেনশনাল Allocation-এ সর্বনিম্ন ও সর্বোচ্চ long ভ্যালুর লোকেশন খুঁজে বের করে:

#define LONG_MAX (long)((1UL << 63) - 1)
#define LONG_MIN (long)(1UL << 63)

#pragma rs reduce(findMinAndMax) \
  initializer(fMMInit) accumulator(fMMAccumulator) \
  combiner(fMMCombiner) outconverter(fMMOutConverter)

// Either a value and the location where it was found, or INITVAL.
typedef struct {
  long val;
  int idx;     // -1 indicates INITVAL
} IndexedVal;

typedef struct {
  IndexedVal min, max;
} MinAndMax;

// In discussion below, this initial value { { LONG_MAX, -1 }, { LONG_MIN, -1 } }
// is called INITVAL.
static void fMMInit(MinAndMax *accum) {
  accum->min.val = LONG_MAX;
  accum->min.idx = -1;
  accum->max.val = LONG_MIN;
  accum->max.idx = -1;
}

//----------------------------------------------------------------------
// In describing the behavior of the accumulator and combiner functions,
// it is helpful to describe hypothetical functions
//   IndexedVal min(IndexedVal a, IndexedVal b)
//   IndexedVal max(IndexedVal a, IndexedVal b)
//   MinAndMax  minmax(MinAndMax a, MinAndMax b)
//   MinAndMax  minmax(MinAndMax accum, IndexedVal val)
//
// The effect of
//   IndexedVal min(IndexedVal a, IndexedVal b)
// is to return the IndexedVal from among the two arguments
// whose val is lesser, except that when an IndexedVal
// has a negative index, that IndexedVal is never less than
// any other IndexedVal; therefore, if exactly one of the
// two arguments has a negative index, the min is the other
// argument. Like ordinary arithmetic min and max, this function
// is commutative and associative; that is,
//
//   min(A, B) == min(B, A)               // commutative
//   min(A, min(B, C)) == min((A, B), C)  // associative
//
// The effect of
//   IndexedVal max(IndexedVal a, IndexedVal b)
// is analogous (greater . . . never greater than).
//
// Then there is
//
//   MinAndMax minmax(MinAndMax a, MinAndMax b) {
//     return MinAndMax(min(a.min, b.min), max(a.max, b.max));
//   }
//
// Like ordinary arithmetic min and max, the above function
// is commutative and associative; that is:
//
//   minmax(A, B) == minmax(B, A)                  // commutative
//   minmax(A, minmax(B, C)) == minmax((A, B), C)  // associative
//
// Finally define
//
//   MinAndMax minmax(MinAndMax accum, IndexedVal val) {
//     return minmax(accum, MinAndMax(val, val));
//   }
//----------------------------------------------------------------------

// This function can be explained as doing:
//   *accum = minmax(*accum, IndexedVal(in, x))
//
// This function simply computes minimum and maximum values as if
// INITVAL.min were greater than any other minimum value and
// INITVAL.max were less than any other maximum value.  Note that if
// *accum is INITVAL, then this function sets
//   *accum = IndexedVal(in, x)
//
// After this function is called, both accum->min.idx and accum->max.idx
// will have nonnegative values:
// - x is always nonnegative, so if this function ever sets one of the
//   idx fields, it will set it to a nonnegative value
// - if one of the idx fields is negative, then the corresponding
//   val field must be LONG_MAX or LONG_MIN, so the function will always
//   set both the val and idx fields
static void fMMAccumulator(MinAndMax *accum, long in, int x) {
  IndexedVal me;
  me.val = in;
  me.idx = x;

  if (me.val <= accum->min.val)
    accum->min = me;
  if (me.val >= accum->max.val)
    accum->max = me;
}

// This function can be explained as doing:
//   *accum = minmax(*accum, *val)
//
// This function simply computes minimum and maximum values as if
// INITVAL.min were greater than any other minimum value and
// INITVAL.max were less than any other maximum value.  Note that if
// one of the two accumulator data items is INITVAL, then this
// function sets *accum to the other one.
static void fMMCombiner(MinAndMax *accum,
                        const MinAndMax *val) {
  if ((accum->min.idx < 0) || (val->min.val < accum->min.val))
    accum->min = val->min;
  if ((accum->max.idx < 0) || (val->max.val > accum->max.val))
    accum->max = val->max;
}

static void fMMOutConverter(int2 *result,
                            const MinAndMax *val) {
  result->x = val->min.idx;
  result->y = val->max.idx;
}

মনে রাখবেন: এখানে আরও উদাহরণ রিডাকশন কার্নেল আছে।

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

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

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

RenderScript ইনপুটের প্রতিটি কোঅর্ডিনেটের জন্য একবার আপনার অ্যাকুমুলেটর ফাংশন কল করে। সাধারণত, আপনার ফাংশন ইনপুট অনুযায়ী কোনওভাবে অ্যাকুমুলেটর ডেটা আইটেম আপডেট করে।

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

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

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

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

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

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

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

উদাহরণ: findMinAndMax কার্নেলে, outconverter ফাংশনটি একটি int2 ফলাফল মান শুরু করে যা সমস্ত অ্যাকুমুলেটর ডেটা আইটেমের সংমিশ্রণ থেকে প্রাপ্ত সর্বনিম্ন এবং সর্বোচ্চ মানের অবস্থানগুলি ধরে রাখে।

রিডাকশন কার্নেল লেখা

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

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

#pragma-এ থাকা আইটেমের অর্থ নিচে দেওয়া হল:

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

    static void initializerName(accumType *accum) { … }

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

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

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

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

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

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

  • combiner(combinerName)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

কোনও নির্দিষ্ট কার্নেল লঞ্চের জন্য RenderScript-এর মাধ্যমে তৈরি করা অ্যাকুমুলেটর ডেটা আইটেমের সংখ্যার উপর নির্ভর করবেন না। একই ইনপুট সহ একই কার্নেল দুবার লঞ্চ করলে একই সংখ্যক অ্যাকুমুলেটর ডেটা আইটেম তৈরি হবে, এমন কোনও গ্যারান্টি নেই।

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

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

এর একটি ফলাফল হল findMinAndMax কার্নেল নির্ধারক নয়: ইনপুটে একই সর্বনিম্ন বা সর্বোচ্চ মান একাধিকবার থাকলে, কার্নেল কোন মানটি খুঁজে পাবে তা জানার কোনও উপায় নেই।

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

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

নিচের নিয়মে প্রায়ই বলা হয় যে দুটি অ্যাকুমুলেটর ডেটা আইটেমের "একই ভ্যালু" থাকতে হবে। এর অর্থ কী? আপনি কার্নেলকে দিয়ে কী করাতে চান তার উপর এটি নির্ভর করে। addint-এর মতো গাণিতিক রিডাকশনের ক্ষেত্রে, "একই" বলতে সাধারণত গাণিতিক সমতা বোঝায়। "যেকোনও একটি বেছে নিন" সার্চের ক্ষেত্রে, যেমন findMinAndMax ("সর্বনিম্ন ও সর্বোচ্চ ইনপুট ভ্যালুর লোকেশন খুঁজুন") যেখানে একই ইনপুট ভ্যালু একাধিকবার থাকতে পারে, সেখানে প্রদত্ত ইনপুট ভ্যালুর সব লোকেশনকে "একই" হিসেবে বিবেচনা করতে হবে। আপনি "সবচেয়ে বাঁদিকে থাকা সর্বনিম্ন ও সর্বোচ্চ ইনপুট ভ্যালুর লোকেশন খুঁজে পেতে" একই ধরনের কার্নেল লিখতে পারেন যেখানে (ধরা যাক) লোকেশন ১০০-তে থাকা সর্বনিম্ন ভ্যালু লোকেশন ২০০-তে থাকা একই ধরনের সর্বনিম্ন ভ্যালুর চেয়ে বেশি পছন্দ করা হয়; এই কার্নেলের জন্য, "একই" বলতে একই লোকেশন বোঝাবে, শুধু একই ভ্যালু নয় এবং অ্যাকুমুলেটর ও কম্বাইনার ফাংশন findMinAndMax-এর থেকে আলাদা হতে হবে।

ইনিশিয়ালাইজার ফাংশনকে অবশ্যই একটি আইডেন্টিটি ভ্যালু তৈরি করতে হবে। অর্থাৎ, যদি I এবং A হল ইনিশিয়ালাইজার ফাংশন দ্বারা ইনিশিয়ালাইজ করা অ্যাকিউমুলেটর ডেটা আইটেম এবং I কখনও অ্যাকিউমুলেটর ফাংশনে পাস করা হয়নি (তবে A হতে পারে), তাহলে
  • combinerName(&A, &I)-কে অবশ্যই A একই রকম রাখতে হবে
  • combinerName(&I, &A)-কে অবশ্যই I-কে A-এর মতোই রাখতে হবে

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

উদাহরণ: findMinAndMax কার্নেলে, একটি অ্যাকুমুলেটর ডেটা আইটেম INITVAL-এ ইনিশিয়ালাইজ করা হয়।

  • fMMCombiner(&A, &I) A একই থাকে, কারণ I হল INITVAL।
  • fMMCombiner(&I, &A) I-কে A-এ সেট করে, কারণ I হল INITVAL।

সুতরাং, INITVAL হল একটি পরিচয় ভ্যালু।

কম্বাইনার ফাংশন অবশ্যই বিনিময়যোগ্য হতে হবে। অর্থাৎ, যদি A এবং B হল ইনিশিয়ালাইজার ফাংশন দ্বারা ইনিশিয়ালাইজ করা অ্যাকিউমুলেটর ডেটা আইটেম এবং সেটি অ্যাকিউমুলেটর ফাংশনে শূন্য বা তার বেশি বার পাস করা হয়ে থাকে, তাহলে combinerName(&A, &B)-কে অবশ্যই A-কে একই ভ্যালুতে সেট করতে হবে যেটি combinerName(&B, &A) B-কে সেট করে।

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

উদাহরণ: findMinAndMax কার্নেলে, fMMCombiner(&A, &B) হল A = minmax(A, B)-এর মতো একই এবং minmax হল বিনিময়যোগ্য, তাই fMMCombiner-ও তাই।

কম্বাইনার ফাংশন অবশ্যই অ্যাসোসিয়েটিভ হতে হবে। অর্থাৎ, যদি A, B এবং C ইনিশিয়ালাইজার ফাংশন দ্বারা ইনিশিয়ালাইজ করা অ্যাকুমুলেটর ডেটা আইটেম হয় এবং সেটি অ্যাকুমুলেটর ফাংশনে শূন্য বা তার বেশি বার পাস করা হয়ে থাকে, তাহলে নিম্নলিখিত দুটি কোড সিকোয়েন্স অবশ্যই A-কে একই ভ্যালুতে সেট করবে:

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

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

  • A = A + B
    A = A + C
    // Same as
    //   A = (A + B) + C
  • B = B + C
    A = A + B
    // Same as
    //   A = A + (B + C)
    //   B = B + C

যোগফল হল অ্যাসোসিয়েটিভ এবং তাই কম্বাইনার ফাংশনও তাই।

যেমন: findMinAndMax কার্নেলে,

fMMCombiner(&A, &B)
এর সমান
A = minmax(A, B)
সুতরাং, দুটি সিকোয়েন্স হল
  • A = minmax(A, B)
    A = minmax(A, C)
    // Same as
    //   A = minmax(minmax(A, B), C)
  • B = minmax(B, C)
    A = minmax(A, B)
    // Same as
    //   A = minmax(A, minmax(B, C))
    //   B = minmax(B, C)

minmax হল অ্যাসোসিয়েটিভ, তাই fMMCombiner-ও তাই।

অ্যাকিউমুলেটর ফাংশন এবং কম্বাইনার ফাংশন একসাথে অবশ্যই বেসিক ফোল্ডিং রুল মেনে চলবে। অর্থাৎ, A এবং B যদি অ্যাকুমুলেটর ডেটা আইটেম হয়, A-কে ইনিশিয়ালাইজার ফাংশন দ্বারা ইনিশিয়ালাইজ করা হয়েছে এবং অ্যাকুমুলেটর ফাংশনে শূন্য বা তার বেশি বার পাস করা হয়েছে, B-কে ইনিশিয়ালাইজ করা হয়নি এবং args হল অ্যাকুমুলেটর ফাংশনে নির্দিষ্ট কলের জন্য ইনপুট আর্গুমেন্ট এবং বিশেষ আর্গুমেন্টের তালিকা, তাহলে নিম্নলিখিত দুটি কোড সিকোয়েন্স অবশ্যই A-কে একই ভ্যালুতে সেট করবে:

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

উদাহরণ: addint কার্নেলে, ইনপুট ভ্যালু V-এর জন্য:

  • বিবৃতি ১ A += V-এর মতো একই
  • বিবৃতি ২ B = 0-এর সমান
  • বিবৃতি ৩ হল B += V-এর মতো, যা B = V-এর মতো
  • বিবৃতি ৪ হল A += B-এর মতো, যা A += V-এর মতো

স্টেটমেন্ট ১ এবং ৪, A-কে একই ভ্যালুতে সেট করে, তাই এই কার্নেল বেসিক ফোল্ডিং নিয়ম মেনে চলে।

উদাহরণ: findMinAndMax কার্নেলে, X কোঅর্ডিনেটে ইনপুট ভ্যালু V-এর জন্য:

  • বিবৃতি ১ A = minmax(A, IndexedVal(V, X))-এর মতো একই
  • বিবৃতি ২ B = INITVAL-এর সমান
  • স্টেটমেন্ট ৩ এর সমান
    B = minmax(B, IndexedVal(V, X))
    যেহেতু B হল প্রাথমিক ভ্যালু, তাই এটি-এর সমান
    B = IndexedVal(V, X)
  • বিবৃতি ৪ এর সাথে একই
    A = minmax(A, B)
    যা এর সমান
    A = minmax(A, IndexedVal(V, X))

স্টেটমেন্ট ১ এবং ৪, A-কে একই ভ্যালুতে সেট করে এবং তাই এই কার্নেল বেসিক ফোল্ডিং নিয়ম মেনে চলে।

জাভা কোড থেকে রিডাকশন কার্নেল কল করা

filename.rs ফাইলে সংজ্ঞায়িত kernelName নামের রিডাকশন কার্নেলের জন্য ScriptC_filename ক্লাসে তিনটি পদ্ধতি প্রতিফলিত হয়:

Kotlin

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

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

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

জাভা

// Method 1
public javaFutureType reduce_kernelName(Allocation ain1, …,
                                        Allocation ainN);

// Method 2
public javaFutureType reduce_kernelName(Allocation ain1, …,
                                        Allocation ainN,
                                        Script.LaunchOptions sc);

// Method 3
public javaFutureType reduce_kernelName(devecSiIn1Type[] in1, …,
                                        devecSiInNType[] inN);

addint কার্নেল কল করার কিছু উদাহরণ এখানে দেওয়া হল:

Kotlin

val script = ScriptC_example(renderScript)

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

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

জাভা

ScriptC_example script = new ScriptC_example(renderScript);

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

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

কার্নেলের অ্যাকুমুলেটর ফাংশনে প্রতিটি ইনপুট আর্গুমেন্টের জন্য পদ্ধতি ১-এ একটি ইনপুট Allocation আর্গুমেন্ট আছে। RenderScript রানটাইম চেক করে দেখে যে সব ইনপুট অ্যালোকেশনের ডাইমেনশন একই কিনা এবং প্রতিটি ইনপুট অ্যালোকেশনের Element টাইপ অ্যাকিউমুলেটর ফাংশনের প্রোটোটাইপের সংশ্লিষ্ট ইনপুট আর্গুমেন্টের সাথে মিলছে কিনা। এইসব চেকের মধ্যে কোনওটি ব্যর্থ হলে, RenderScript একটি ব্যতিক্রম থ্রো করে। কার্নেল সেইসব ডাইমেনশনের প্রতিটি কোঅর্ডিনেটে এক্সিকিউট করে।

পদ্ধতি ২ পদ্ধতি ১-এর মতোই, তবে পদ্ধতি ২-এ একটি অতিরিক্ত আর্গুমেন্ট sc থাকে যা কার্নেল এক্সিকিউশনকে কোঅর্ডিনেটের একটি সাবসেটে সীমাবদ্ধ করতে ব্যবহার করা যেতে পারে।

পদ্ধতি ৩ পদ্ধতি ১-এর মতোই, তবে অ্যালোকেশন ইনপুট নেওয়ার পরিবর্তে এটি জাভা অ্যারে ইনপুট নেয়। এটি একটি সুবিধা যা আপনাকে স্পষ্টভাবে একটি অ্যালোকেশন তৈরি করতে এবং জাভা অ্যারে থেকে এতে ডেটা কপি করার জন্য কোড লেখার ঝামেলা থেকে বাঁচায়। তবে, পদ্ধতি ১-এর পরিবর্তে পদ্ধতি ৩ ব্যবহার করলে কোডের পারফর্ম্যান্স বৃদ্ধি পায় না। প্রতিটি ইনপুট অ্যারের জন্য, পদ্ধতি ৩ উপযুক্ত Element ধরন এবং setAutoPadding(boolean) চালু সহ একটি অস্থায়ী ১-ডাইমেনশনাল অ্যালোকেশন তৈরি করে এবং অ্যারেটি অ্যালোকেশনে কপি করে, যেন Allocation-এর উপযুক্ত copyFrom() পদ্ধতি দ্বারা করা হয়েছে। তারপর এটি পদ্ধতি ১-কে কল করে, সেইসব অস্থায়ী অ্যালোকেশন পাস করে।

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

javaFutureType, রিফ্লেক্টেড রিডাকশন পদ্ধতির রিটার্ন টাইপ হল ScriptC_filename ক্লাসের মধ্যে একটি রিফ্লেক্টেড স্ট্যাটিক নেস্টেড ক্লাস। এটি রিডাকশন কার্নেল রান করার ভবিষ্যতের ফলাফল দেখায়। রান করার প্রকৃত ফলাফল পেতে, সেই ক্লাসের get() মেথড কল করুন, যা javaResultType টাইপের ভ্যালু রিটার্ন করে। get() হল সিঙ্ক্রোনাস।

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 আউটকনভার্টার ফাংশনের resultType থেকে নির্ধারণ করা হয়। resultType আনসাইনড টাইপ (স্কেলার, ভেক্টর বা অ্যারে) না হলে, javaResultType সরাসরি সংশ্লিষ্ট Java টাইপ হয়। resultType যদি আনসাইনড টাইপ হয় এবং আরও বড় জাভা সাইনড টাইপ থাকে, তাহলে javaResultType হল সেই বড় জাভা সাইনড টাইপ; অন্যথায়, এটি সরাসরি সংশ্লিষ্ট জাভা টাইপ। যেমন:

  • resultType যদি int, int2 বা int[15] হয়, তাহলে javaResultType হল int, Int2 বা int[]। resultType-এর সব ভ্যালু javaResultType-এর মাধ্যমে দেখানো যেতে পারে।
  • resultType যদি uint, uint2, বা uint[15] হয়, তাহলে javaResultType হল long, Long2, বা long[]। resultType-এর সব ভ্যালু javaResultType-এর মাধ্যমে দেখানো যেতে পারে।
  • resultType যদি ulong, ulong2, বা ulong[15] হয়, তাহলে javaResultType হবে long, Long2, বা long[]। resultType-এর কিছু ভ্যালু javaResultType-এর মাধ্যমে দেখানো যায় না।

javaFutureType হল আউটকনভার্টার ফাংশনের resultType-এর সাথে সম্পর্কিত ভবিষ্যতের ফলাফলের ধরন।

  • resultType অ্যারে টাইপ না হলে, javaFutureType হল result_resultType।
  • resultType যদি memberType ধরনের মেম্বার সহ Count দৈর্ঘ্যের একটি অ্যারে হয়, তাহলে javaFutureType হল resultArrayCount_memberType।

যেমন:

Kotlin

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

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

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

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

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

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

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

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

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

জাভা

public class ScriptC_filename extends ScriptC {
  // for kernels with int result
  public static class result_int {
    public int get() { … }
  }

  // for kernels with int[10] result
  public static class resultArray10_int {
    public int[] get() { … }
  }

  // for kernels with int2 result
  //   note that the Java type name "Int2" is not the same as the script type name "int2"
  public static class result_int2 {
    public Int2 get() { … }
  }

  // for kernels with int2[10] result
  //   note that the Java type name "Int2" is not the same as the script type name "int2"
  public static class resultArray10_int2 {
    public Int2[] get() { … }
  }

  // for kernels with uint result
  //   note that the Java type "long" is a wider signed type than the unsigned script type "uint"
  public static class result_uint {
    public long get() { … }
  }

  // for kernels with uint[10] result
  //   note that the Java type "long" is a wider signed type than the unsigned script type "uint"
  public static class resultArray10_uint {
    public long[] get() { … }
  }

  // for kernels with uint2 result
  //   note that the Java type "Long2" is a wider signed type than the unsigned script type "uint2"
  public static class result_uint2 {
    public Long2 get() { … }
  }

  // for kernels with uint2[10] result
  //   note that the Java type "Long2" is a wider signed type than the unsigned script type "uint2"
  public static class resultArray10_uint2 {
    public Long2[] get() { … }
  }
}

javaResultType যদি অবজেক্ট টাইপ (অ্যারে টাইপ সহ) হয়, তাহলে একই ইনস্ট্যান্সে javaFutureType.get()-এ করা প্রতিটি কল একই অবজেক্ট রিটার্ন করবে।

javaResultType যদি resultType ধরনের সব ভ্যালু দেখাতে না পারে এবং রিডাকশন কার্নেল যদি এমন ভ্যালু তৈরি করে যা দেখানো যায় না, তাহলে javaFutureType.get() একটি ব্যতিক্রম থ্রো করে।

পদ্ধতি ৩ এবং devecSiInXType

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

  • inXType int হলে, devecSiInXType int হবে।
  • inXType int2 হলে, devecSiInXType int হবে। অ্যারে হল একটি ফ্ল্যাটেন করা উপস্থাপনা: এতে অ্যালোকেশনের ২-কম্পোনেন্ট ভেক্টর এলিমেন্টের তুলনায় দ্বিগুণ সংখ্যক স্কেলার এলিমেন্ট আছে। Allocation-এর copyFrom() পদ্ধতি যেভাবে কাজ করে এটিও ঠিক সেইভাবেই কাজ করে।
  • inXType uint হলে, deviceSiInXType int হবে। Java অ্যারেতে একটি সাইন করা ভ্যালু, অ্যালোকেশনে একই বিটপ্যাটার্নের একটি আনসাইন করা ভ্যালু হিসেবে ব্যাখ্যা করা হয়। এটি Allocation-এর copyFrom() পদ্ধতির মতোই কাজ করে।
  • inXType uint2 হলে, deviceSiInXType int হবে। এটি int2 এবং uint যেভাবে ম্যানেজ করা হয় তার একটি কম্বিনেশন: অ্যারে হল একটি ফ্ল্যাটেন করা উপস্থাপনা এবং Java অ্যারে সাইন করা ভ্যালুগুলি RenderScript আনসাইন করা এলিমেন্ট ভ্যালু হিসেবে ব্যাখ্যা করা হয়।

মনে রাখবেন যে পদ্ধতি ৩-এর জন্য, ইনপুট টাইপগুলি ফলাফলের ধরন থেকে আলাদাভাবে পরিচালনা করা হয়:

  • স্ক্রিপ্টের ভেক্টর ইনপুট জাভা সাইডে ফ্ল্যাটেন করা হয়, কিন্তু স্ক্রিপ্টের ভেক্টর ফলাফল ফ্ল্যাটেন করা হয় না।
  • স্ক্রিপ্টের আনসাইন করা ইনপুট Java-তে একই সাইজের সাইন করা ইনপুট হিসেবে দেখানো হয়, যেখানে স্ক্রিপ্টের আনসাইন করা ফলাফল Java-তে আরও বড় সাইন করা ধরন হিসেবে দেখানো হয় (ulong-এর ক্ষেত্রে ছাড়া)।

আরও উদাহরণ রিডাকশন কার্নেল

#pragma rs reduce(dotProduct) \
  accumulator(dotProductAccum) combiner(dotProductSum)

// Note: No initializer function -- therefore,
// each accumulator data item is implicitly initialized to 0.0f.

static void dotProductAccum(float *accum, float in1, float in2) {
  *accum += in1*in2;
}

// combiner function
static void dotProductSum(float *accum, const float *val) {
  *accum += *val;
}
// Find a zero Element in a 2D allocation; return (-1, -1) if none
#pragma rs reduce(fz2) \
  initializer(fz2Init) \
  accumulator(fz2Accum) combiner(fz2Combine)

static void fz2Init(int2 *accum) { accum->x = accum->y = -1; }

static void fz2Accum(int2 *accum,
                     int inVal,
                     int x /* special arg */,
                     int y /* special arg */) {
  if (inVal==0) {
    accum->x = x;
    accum->y = y;
  }
}

static void fz2Combine(int2 *accum, const int2 *accum2) {
  if (accum2->x >= 0) *accum = *accum2;
}
// Note that this kernel returns an array to Java
#pragma rs reduce(histogram) \
  accumulator(hsgAccum) combiner(hsgCombine)

#define BUCKETS 256
typedef uint32_t Histogram[BUCKETS];

// Note: No initializer function --
// therefore, each bucket is implicitly initialized to 0.

static void hsgAccum(Histogram *h, uchar in) { ++(*h)[in]; }

static void hsgCombine(Histogram *accum,
                       const Histogram *addend) {
  for (int i = 0; i < BUCKETS; ++i)
    (*accum)[i] += (*addend)[i];
}

// Determines the mode (most frequently occurring value), and returns
// the value and the frequency.
//
// If multiple values have the same highest frequency, returns the lowest
// of those values.
//
// Shares functions with the histogram reduction kernel.
#pragma rs reduce(mode) \
  accumulator(hsgAccum) combiner(hsgCombine) \
  outconverter(modeOutConvert)

static void modeOutConvert(int2 *result, const Histogram *h) {
  uint32_t mode = 0;
  for (int i = 1; i < BUCKETS; ++i)
    if ((*h)[i] > (*h)[mode]) mode = i;
  result->x = mode;
  result->y = (*h)[mode];
}

অতিরিক্ত কোড স্যাম্পেল

BasicRenderScript, RenderScriptIntrinsic, এবং Hello Compute স্যাম্পেলগুলি এই পৃষ্ঠায় কভার করা API-এর ব্যবহার আরও প্রদর্শন করে।