রেন্ডারস্ক্রিপ্ট হলো অ্যান্ড্রয়েডে উচ্চ পারফরম্যান্সে গণনা-নিবিড় কাজ চালানোর জন্য একটি ফ্রেমওয়ার্ক। রেন্ডারস্ক্রিপ্ট মূলত ডেটা-প্যারালাল কম্পিউটেশনের জন্য তৈরি, যদিও সিরিয়াল ওয়ার্কলোডও এর থেকে উপকৃত হতে পারে। রেন্ডারস্ক্রিপ্ট রানটাইম একটি ডিভাইসে উপলব্ধ প্রসেসর, যেমন মাল্টি-কোর সিপিইউ এবং জিপিইউ জুড়ে কাজকে সমান্তরালভাবে চালায়। এটি আপনাকে কাজের সময়সূচী নির্ধারণের পরিবর্তে অ্যালগরিদম প্রকাশের উপর মনোযোগ দিতে সাহায্য করে। রেন্ডারস্ক্রিপ্ট বিশেষত ইমেজ প্রসেসিং, কম্পিউটেশনাল ফটোগ্রাফি বা কম্পিউটার ভিশনের মতো অ্যাপ্লিকেশনগুলির জন্য উপযোগী।
RenderScript শুরু করার জন্য, আপনাকে দুটি প্রধান ধারণা বুঝতে হবে:
- ভাষাটি মূলত উচ্চ-ক্ষমতাসম্পন্ন কম্পিউট কোড লেখার জন্য ব্যবহৃত একটি C99-ভিত্তিক ভাষা। 'রাইটিং এ রেন্ডারস্ক্রিপ্ট কার্নেল' অংশে বর্ণনা করা হয়েছে কীভাবে এটি ব্যবহার করে কম্পিউট কার্নেল লিখতে হয়।
- কন্ট্রোল এপিআই (API) রেন্ডারস্ক্রিপ্ট রিসোর্সের জীবনকাল পরিচালনা এবং কার্নেল এক্সিকিউশন নিয়ন্ত্রণের জন্য ব্যবহৃত হয়। এটি তিনটি ভিন্ন ভাষায় উপলব্ধ: জাভা, অ্যান্ড্রয়েড এনডিকে-তে থাকা সি++, এবং সি৯৯ (C99) থেকে উদ্ভূত কার্নেল ভাষা। 'জাভা কোড থেকে রেন্ডারস্ক্রিপ্ট ব্যবহার' এবং 'সিঙ্গেল-সোর্স রেন্ডারস্ক্রিপ্ট' যথাক্রমে প্রথম এবং তৃতীয় বিকল্পটি বর্ণনা করে।
রেন্ডারস্ক্রিপ্ট কার্নেল লেখা
একটি RenderScript কার্নেল সাধারণত <project_root>/src/rs ডিরেক্টরির একটি .rs ফাইলে থাকে; প্রতিটি .rs ফাইলকে একটি স্ক্রিপ্ট বলা হয়। প্রতিটি স্ক্রিপ্টে তার নিজস্ব কার্নেল, ফাংশন এবং ভেরিয়েবলের সেট থাকে। একটি স্ক্রিপ্টে থাকতে পারে:
- একটি প্রাগমা ডিক্লারেশন (
#pragma version(1)) যা এই স্ক্রিপ্টে ব্যবহৃত RenderScript কার্নেল ল্যাঙ্গুয়েজের ভার্সন ঘোষণা করে। বর্তমানে, 1-ই একমাত্র বৈধ মান। - একটি প্রাগমা ডিক্লারেশন (
#pragma rs java_package_name(com.example.app)) যা এই স্ক্রিপ্ট থেকে প্রতিফলিত জাভা ক্লাসগুলির প্যাকেজ নাম ঘোষণা করে। মনে রাখবেন যে আপনার.rsফাইলটি অবশ্যই আপনার অ্যাপ্লিকেশন প্যাকেজের অংশ হতে হবে, কোনো লাইব্রেরি প্রজেক্টে নয়। - শূন্য বা তার বেশি সংখ্যক আহ্বানযোগ্য ফাংশন । একটি আহ্বানযোগ্য ফাংশন হলো একটি একক-থ্রেডেড RenderScript ফাংশন, যাকে আপনি আপনার জাভা কোড থেকে যথেচ্ছ আর্গুমেন্ট দিয়ে কল করতে পারেন। এগুলি প্রায়শই একটি বৃহত্তর প্রসেসিং পাইপলাইনের মধ্যে প্রাথমিক সেটআপ বা ধারাবাহিক গণনার জন্য উপযোগী হয়।
শূন্য বা তার বেশি স্ক্রিপ্ট গ্লোবাল । একটি স্ক্রিপ্ট গ্লোবাল হলো C-এর গ্লোবাল ভেরিয়েবলের মতো। আপনি জাভা কোড থেকে স্ক্রিপ্ট গ্লোবাল অ্যাক্সেস করতে পারেন, এবং এগুলো প্রায়শই 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 একটি এক্সেপশন থ্রো করে।দ্রষ্টব্য: অ্যান্ড্রয়েড ৬.০ (এপিআই লেভেল ২৩)-এর আগে, একটি ম্যাপিং কার্নেলে একাধিক ইনপুট
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জন্য অ্যাকুমুলেটর ফাংশনটি একবার করে এক্সিকিউট হয়। ডিফল্টরূপে, অ্যাকুমুলেটর ডেটা আইটেমের চূড়ান্ত মানটিকে রিডাকশনের ফলাফল হিসাবে গণ্য করা হয় এবং জাভাতে রিটার্ন করা হয়। RenderScript রানটাইম পরীক্ষা করে নিশ্চিত করে যে ইনপুট Allocation-এরElementটাইপটি অ্যাকুমুলেটর ফাংশনের প্রোটোটাইপের সাথে মেলে; যদি না মেলে, RenderScript একটি এক্সেপশন থ্রো করে।একটি রিডাকশন কার্নেলের এক বা একাধিক ইনপুট
Allocationsথাকে, কিন্তু কোনো আউটপুটAllocationsথাকে না।রিডাকশন কার্নেল সম্পর্কে এখানে আরও বিস্তারিতভাবে ব্যাখ্যা করা হয়েছে।
অ্যান্ড্রয়েড ৭.০ (এপিআই লেভেল ২৪) এবং এর পরবর্তী সংস্করণগুলোতে রিডাকশন কার্নেল সমর্থিত।
একটি ম্যাপিং কার্নেল ফাংশন অথবা একটি রিডাকশন কার্নেল অ্যাকুমুলেটর ফাংশন বিশেষ আর্গুমেন্ট
x,y, এবংzব্যবহার করে বর্তমান এক্সিকিউশনের স্থানাঙ্ক অ্যাক্সেস করতে পারে, যেগুলোর টাইপ অবশ্যইintঅথবাuint32_tহতে হবে। এই আর্গুমেন্টগুলো ঐচ্ছিক।একটি ম্যাপিং কার্নেল ফাংশন অথবা একটি রিডাকশন কার্নেল অ্যাকুমুলেটর ফাংশন rs_kernel_context টাইপের একটি ঐচ্ছিক বিশেষ আর্গুমেন্ট ‘
contextগ্রহণ করতে পারে। বর্তমান এক্সিকিউশনের নির্দিষ্ট কিছু প্রোপার্টি কোয়েরি করার জন্য ব্যবহৃত একগুচ্ছ রানটাইম এপিআই-এর ক্ষেত্রে এটির প্রয়োজন হয় — উদাহরণস্বরূপ, rsGetDimX । (contextআর্গুমেন্টটি অ্যান্ড্রয়েড ৬.০ (এপিআই লেভেল ২৩) এবং এর পরবর্তী সংস্করণগুলোতে উপলব্ধ।)- একটি ঐচ্ছিক
init()ফাংশন।init()ফাংশন হলো এক বিশেষ ধরনের আহ্বানযোগ্য ফাংশন যা RenderScript স্ক্রিপ্টটি প্রথমবার ইনস্ট্যানশিয়েট করার সময় চালায়। এর ফলে স্ক্রিপ্ট তৈরির সময়েই কিছু গণনা স্বয়ংক্রিয়ভাবে সম্পন্ন হতে পারে। - শূন্য বা তার বেশি সংখ্যক স্ট্যাটিক স্ক্রিপ্ট গ্লোবাল এবং ফাংশন । একটি স্ট্যাটিক স্ক্রিপ্ট গ্লোবাল একটি স্ক্রিপ্ট গ্লোবালের সমতুল্য, তবে পার্থক্য হলো এটি জাভা কোড থেকে অ্যাক্সেস করা যায় না। একটি স্ট্যাটিক ফাংশন হলো একটি স্ট্যান্ডার্ড C ফাংশন যা স্ক্রিপ্টের যেকোনো কার্নেল বা ইনভোকেবল ফাংশন থেকে কল করা যায়, কিন্তু এটি জাভা 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 সিপিইউ ইনস্ট্রাকশন)।
জাভা থেকে রেন্ডারস্ক্রিপ্ট এপিআই অ্যাক্সেস করা
RenderScript ব্যবহার করে কোনো অ্যান্ড্রয়েড অ্যাপ্লিকেশন তৈরি করার সময়, আপনি জাভা থেকে দুটি উপায়ের একটিতে এর API অ্যাক্সেস করতে পারেন:
-
android.renderscript- এই ক্লাস প্যাকেজের এপিআইগুলো অ্যান্ড্রয়েড ৩.০ (এপিআই লেভেল ১১) এবং তার পরবর্তী সংস্করণের ডিভাইসগুলোতে উপলব্ধ। -
android.support.v8.renderscript- এই প্যাকেজের এপিআইগুলো একটি সাপোর্ট লাইব্রেরির মাধ্যমে পাওয়া যায়, যা আপনাকে অ্যান্ড্রয়েড ২.৩ (এপিআই লেভেল ৯) এবং তার উচ্চতর সংস্করণে চালিত ডিভাইসগুলোতে এগুলো ব্যবহার করার সুযোগ দেয়।
এখানে সুবিধা-অসুবিধাগুলো তুলে ধরা হলো:
- আপনি যদি সাপোর্ট লাইব্রেরি এপিআই ব্যবহার করেন, তাহলে আপনি কোন রেন্ডারস্ক্রিপ্ট ফিচার ব্যবহার করছেন তা নির্বিশেষে, আপনার অ্যাপ্লিকেশনের রেন্ডারস্ক্রিপ্ট অংশটি অ্যান্ড্রয়েড ২.৩ (এপিআই লেভেল ৯) এবং তার উচ্চতর সংস্করণের ডিভাইসগুলোর সাথে সামঞ্জস্যপূর্ণ হবে। এর ফলে, নেটিভ (
android.renderscript) এপিআই ব্যবহারের তুলনায় আপনার অ্যাপ্লিকেশনটি আরও বেশি ডিভাইসে কাজ করতে পারবে। - RenderScript-এর কিছু বৈশিষ্ট্য সাপোর্ট লাইব্রেরি API-এর মাধ্যমে পাওয়া যায় না।
- আপনি যদি সাপোর্ট লাইব্রেরি এপিআই ব্যবহার করেন, তাহলে নেটিভ (
android.renderscript) এপিআই ব্যবহার করার চেয়ে (সম্ভবত উল্লেখযোগ্যভাবে) বড় এপিকে পাবেন।
রেন্ডারস্ক্রিপ্ট সাপোর্ট লাইব্রেরি এপিআই ব্যবহার করে
সাপোর্ট লাইব্রেরি রেন্ডারস্ক্রিপ্ট এপিআইগুলো ব্যবহার করার জন্য, আপনাকে অবশ্যই আপনার ডেভেলপমেন্ট এনভায়রনমেন্ট এমনভাবে কনফিগার করতে হবে যাতে সেগুলোতে অ্যাক্সেস করা যায়। এই এপিআইগুলো ব্যবহার করার জন্য নিম্নলিখিত অ্যান্ড্রয়েড এসডিকে টুলগুলো প্রয়োজন:
- অ্যান্ড্রয়েড এসডিকে টুলস সংস্করণ ২২.২ বা তার উচ্চতর
- অ্যান্ড্রয়েড এসডিকে বিল্ড-টুলস সংস্করণ ১৮.১.০ বা তার উচ্চতর
উল্লেখ্য যে, Android SDK Build-tools 24.0.0 থেকে Android 2.2 (API level 8) আর সমর্থিত নয়।
আপনি অ্যান্ড্রয়েড এসডিকে ম্যানেজার- এ এই টুলগুলির ইনস্টল করা সংস্করণ পরীক্ষা ও আপডেট করতে পারেন।
সাপোর্ট লাইব্রেরি রেন্ডারস্ক্রিপ্ট এপিআই ব্যবহার করতে:
- আপনার প্রয়োজনীয় অ্যান্ড্রয়েড এসডিকে সংস্করণটি ইনস্টল করা আছে কিনা, তা নিশ্চিত করুন।
- RenderScript সেটিংস অন্তর্ভুক্ত করার জন্য অ্যান্ড্রয়েড বিল্ড প্রক্রিয়ার সেটিংস আপডেট করুন:
- আপনার অ্যাপ্লিকেশন মডিউলের app ফোল্ডারে থাকা
build.gradleফাইলটি খুলুন। - ফাইলটিতে নিম্নলিখিত RenderScript সেটিংস যোগ করুন:
গ্রুভি
android { compileSdkVersion 36 defaultConfig { minSdkVersion 9 targetSdkVersion 19 renderscriptTargetApi 18 renderscriptSupportModeEnabled true } }
কোটলিন
android { compileSdkVersion(36) defaultConfig { minSdkVersion(9) targetSdkVersion(19) renderscriptTargetApi = 18 renderscriptSupportModeEnabled = true } }
উপরে তালিকাভুক্ত সেটিংসগুলো অ্যান্ড্রয়েড বিল্ড প্রক্রিয়ার নির্দিষ্ট আচরণ নিয়ন্ত্রণ করে:
-
renderscriptTargetApi- যে বাইটকোড সংস্করণটি তৈরি করা হবে তা নির্দিষ্ট করে। আমরা আপনাকে এই মানটি সর্বনিম্ন API স্তরে সেট করার পরামর্শ দিই যা আপনার ব্যবহৃত সমস্ত কার্যকারিতা সরবরাহ করতে সক্ষম এবংrenderscriptSupportModeEnabledtrueতে সেট করুন। এই সেটিং-এর জন্য বৈধ মান হলো ১১ থেকে শুরু করে সর্বশেষ প্রকাশিত API স্তর পর্যন্ত যেকোনো পূর্ণসংখ্যা। যদি আপনার অ্যাপ্লিকেশন ম্যানিফেস্টে নির্দিষ্ট করা সর্বনিম্ন SDK সংস্করণ একটি ভিন্ন মানে সেট করা থাকে, তবে সেই মানটি উপেক্ষা করা হবে এবং সর্বনিম্ন SDK সংস্করণ সেট করার জন্য বিল্ড ফাইলের টার্গেট মানটি ব্যবহার করা হবে। -
renderscriptSupportModeEnabled- এটি নির্দিষ্ট করে যে, যে ডিভাইসে বাইটকোডটি চালানো হচ্ছে সেটি যদি টার্গেট সংস্করণটি সমর্থন না করে, তবে তৈরি হওয়া বাইটকোডটি একটি সামঞ্জস্যপূর্ণ সংস্করণে ফিরে যাবে।
-
- আপনার অ্যাপ্লিকেশন মডিউলের app ফোল্ডারে থাকা
- আপনার অ্যাপ্লিকেশনের যে ক্লাসগুলো RenderScript ব্যবহার করে, সেগুলোতে সাপোর্ট লাইব্রেরি ক্লাসগুলোর জন্য ইম্পোর্ট যোগ করুন:
কোটলিন
import android.support.v8.renderscript.*
জাভা
import android.support.v8.renderscript.*;
জাভা বা কোটলিন কোড থেকে রেন্ডারস্ক্রিপ্ট ব্যবহার করা
জাভা বা কোটলিন কোড থেকে RenderScript ব্যবহার করতে android.renderscript অথবা android.support.v8.renderscript প্যাকেজে অবস্থিত API ক্লাসগুলোর উপর নির্ভর করতে হয়। বেশিরভাগ অ্যাপ্লিকেশন একই মৌলিক ব্যবহারের ধরণ অনুসরণ করে:
- একটি RenderScript কনটেক্সট ইনিশিয়ালাইজ করুন।
create(Context)` দিয়ে তৈরি করাRenderScriptকনটেক্সটটি নিশ্চিত করে যে RenderScript ব্যবহার করা যাবে এবং পরবর্তী সমস্ত RenderScript অবজেক্টের জীবনকাল নিয়ন্ত্রণ করার জন্য একটি অবজেক্ট প্রদান করে। কনটেক্সট তৈরি করাকে একটি সম্ভাব্য দীর্ঘ-চলমান অপারেশন হিসেবে বিবেচনা করা উচিত, কারণ এটি বিভিন্ন হার্ডওয়্যারে রিসোর্স তৈরি করতে পারে; সম্ভব হলে এটি কোনো অ্যাপ্লিকেশনের ক্রিটিক্যাল পাথে থাকা উচিত নয়। সাধারণত, একটি অ্যাপ্লিকেশনে এক সময়ে কেবল একটিই RenderScript কনটেক্সট থাকে। - স্ক্রিপ্টে পাঠানোর জন্য অন্তত একটি
Allocationতৈরি করুন।Allocationহলো একটি রেন্ডারস্ক্রিপ্ট অবজেক্ট যা একটি নির্দিষ্ট পরিমাণ ডেটা সংরক্ষণের জন্য জায়গা প্রদান করে। স্ক্রিপ্টের কার্নেলগুলো তাদের ইনপুট এবং আউটপুট হিসেবেAllocationঅবজেক্ট গ্রহণ করে, এবং স্ক্রিপ্ট গ্লোবাল হিসেবে বাইন্ড করা থাকলে কার্নেলেrsGetElementAt_ type ()এবংrsSetElementAt_ type ()ব্যবহার করেAllocationঅবজেক্টগুলো অ্যাক্সেস করা যায়।Allocationঅবজেক্টের মাধ্যমে জাভা কোড থেকে রেন্ডারস্ক্রিপ্ট কোডে এবং এর বিপরীতে অ্যারে পাঠানো যায়। সাধারণতcreateTyped()বাcreateFromBitmap()ব্যবহার করেAllocationঅবজেক্ট তৈরি করা হয়। - প্রয়োজনীয় স্ক্রিপ্টগুলো তৈরি করুন। RenderScript ব্যবহার করার সময় আপনার জন্য দুই ধরনের স্ক্রিপ্ট উপলব্ধ রয়েছে:
- ScriptC : এগুলো হলো ব্যবহারকারী-সংজ্ঞায়িত স্ক্রিপ্ট, যা উপরে 'Writing a RenderScript Kernel' অংশে বর্ণনা করা হয়েছে। প্রতিটি স্ক্রিপ্টের একটি জাভা ক্লাস থাকে যা RenderScript কম্পাইলার দ্বারা প্রতিফলিত হয়, যাতে জাভা কোড থেকে স্ক্রিপ্টটি সহজে অ্যাক্সেস করা যায়; এই ক্লাসটির নাম হলো
ScriptC_ filename। উদাহরণস্বরূপ, যদি উপরের ম্যাপিং কার্নেলটিinvert.rsএ অবস্থিত থাকতো এবং একটি RenderScript কনটেক্সট আগে থেকেইmRenderScriptএ থাকতো, তাহলে স্ক্রিপ্টটি ইনস্ট্যানশিয়েট করার জন্য জাভা বা কোটলিন কোডটি হবে:কোটলিন
val invert = ScriptC_invert(renderScript)
জাভা
ScriptC_invert invert = new ScriptC_invert(renderScript);
- স্ক্রিপ্টইন্ট্রিনসিক : এগুলো হলো রেন্ডারস্ক্রিপ্টের বিল্ট-ইন কার্নেল, যা গাউসিয়ান ব্লার, কনভোলিউশন এবং ইমেজ ব্লেন্ডিং-এর মতো সাধারণ অপারেশনের জন্য ব্যবহৃত হয়। আরও তথ্যের জন্য,
ScriptIntrinsic-এর সাবক্লাসগুলো দেখুন।
- ScriptC : এগুলো হলো ব্যবহারকারী-সংজ্ঞায়িত স্ক্রিপ্ট, যা উপরে 'Writing a RenderScript Kernel' অংশে বর্ণনা করা হয়েছে। প্রতিটি স্ক্রিপ্টের একটি জাভা ক্লাস থাকে যা RenderScript কম্পাইলার দ্বারা প্রতিফলিত হয়, যাতে জাভা কোড থেকে স্ক্রিপ্টটি সহজে অ্যাক্সেস করা যায়; এই ক্লাসটির নাম হলো
- অ্যালোকেশনগুলিতে ডেটা পূরণ করুন।
createFromBitmap()দিয়ে তৈরি করা অ্যালোকেশনগুলি ছাড়া, একটি অ্যালোকেশন প্রথমবার তৈরি হওয়ার সময় খালি ডেটা দিয়ে পূরণ করা হয়। একটি অ্যালোকেশন পূরণ করতে,Allocationএর 'copy' মেথডগুলির মধ্যে একটি ব্যবহার করুন। 'copy' মেথডগুলি সিনক্রোনাস । - প্রয়োজনীয় স্ক্রিপ্ট গ্লোবালগুলো সেট করুন। আপনি একই
ScriptC_ filenameক্লাসেরset_ globalnameনামের মেথড ব্যবহার করে গ্লোবালগুলো সেট করতে পারেন। উদাহরণস্বরূপ,thresholdনামের একটিintভেরিয়েবল সেট করার জন্য,set_threshold(int)জাভা মেথডটি ব্যবহার করুন; এবংlookupনামের একটিrs_allocationভেরিয়েবল সেট করার জন্য,set_lookup(Allocation)জাভা মেথডটি ব্যবহার করুন। এইsetমেথডগুলো অ্যাসিঙ্ক্রোনাস । - উপযুক্ত কার্নেল এবং আহ্বানযোগ্য ফাংশনগুলো চালু করুন।
একটি নির্দিষ্ট কার্নেল চালু করার পদ্ধতিগুলো একই
ScriptC_ filenameক্লাসেforEach_ mappingKernelName ()বাreduce_ reductionKernelName ()নামের মেথডগুলোর মাধ্যমে প্রতিফলিত হয়। এই লঞ্চগুলো অ্যাসিঙ্ক্রোনাস । কার্নেলের আর্গুমেন্টের উপর নির্ভর করে, মেথডটি এক বা একাধিক অ্যালোকেশন গ্রহণ করে, যেগুলোর সবগুলোর ডাইমেনশন অবশ্যই একই হতে হবে। ডিফল্টরূপে, একটি কার্নেল সেই ডাইমেনশনের প্রতিটি কোঅর্ডিনেটের উপর এক্সিকিউট হয়; সেই কোঅর্ডিনেটগুলোর একটি সাবসেটের উপর কার্নেল এক্সিকিউট করতে হলে,forEachবাreduceমেথডের শেষ আর্গুমেন্ট হিসেবে একটি উপযুক্তScript.LaunchOptionsপাস করতে হবে।একই
ScriptC_ filenameক্লাসে প্রতিফলিতinvoke_ functionNameমেথডগুলো ব্যবহার করে আহ্বানযোগ্য ফাংশনগুলো চালু করুন। এই চালু করাগুলো অ্যাসিঙ্ক্রোনাস । -
Allocationঅবজেক্ট এবং javaFutureType অবজেক্ট থেকে ডেটা পুনরুদ্ধার করুন। জাভা কোড থেকে কোনোAllocationএর ডেটা অ্যাক্সেস করার জন্য, আপনাকে অবশ্যইAllocationএর "copy" মেথডগুলোর একটি ব্যবহার করে সেই ডেটা জাভাতে কপি করতে হবে। একটি রিডাকশন কার্নেলের ফলাফল পাওয়ার জন্য, আপনাকে অবশ্যইjavaFutureType .get()মেথডটি ব্যবহার করতে হবে। "copy" এবংget()মেথডগুলো সিনক্রোনাস । - RenderScript কনটেক্সটটি ভেঙে ফেলুন। আপনি
destroy()ব্যবহার করে অথবা RenderScript কনটেক্সট অবজেক্টটিকে গার্বেজ কালেক্টেড হতে দিয়ে এটি ধ্বংস করতে পারেন। এর ফলে, ঐ কনটেক্সটের অন্তর্গত যেকোনো অবজেক্টের পরবর্তী ব্যবহারে একটি এক্সেপশন থ্রো হবে।
অ্যাসিঙ্ক্রোনাস এক্সিকিউশন মডেল
রিফ্লেক্টেড forEach , invoke , reduce , এবং set মেথডগুলো অ্যাসিঙ্ক্রোনাস—এগুলোর প্রত্যেকটি অনুরোধ করা কাজটি সম্পন্ন করার আগেই জাভাতে ফিরে যেতে পারে। তবে, প্রতিটি অ্যাকশন যে ক্রমে চালু করা হয়, সেই ক্রমেই সিরিয়ালাইজ করা থাকে।
Allocation ক্লাসটি Allocation-এর মধ্যে এবং বাইরে ডেটা কপি করার জন্য "copy" মেথড প্রদান করে। একটি "copy" মেথড সিনক্রোনাস, এবং একই Allocation-কে প্রভাবিত করে এমন উপরের যেকোনো অ্যাসিঙ্ক্রোনাস অ্যাকশনের সাপেক্ষে এটি সিরিয়ালাইজড হয়।
রিফ্লেক্টেড javaFutureType ক্লাসগুলো একটি রিডাকশনের ফলাফল পাওয়ার জন্য get() মেথড প্রদান করে। get() সিনক্রোনাস, এবং রিডাকশনের (যা অ্যাসিঙ্ক্রোনাস) সাপেক্ষে এটি সিরিয়ালাইজড হয়।
একক-উৎস রেন্ডারস্ক্রিপ্ট
অ্যান্ড্রয়েড ৭.০ (এপিআই লেভেল ২৪) সিঙ্গেল-সোর্স রেন্ডারস্ক্রিপ্ট নামে একটি নতুন প্রোগ্রামিং ফিচার চালু করেছে, যেখানে কার্নেলগুলো জাভা থেকে নয়, বরং যে স্ক্রিপ্টে সেগুলো সংজ্ঞায়িত করা হয়েছে সেখান থেকেই চালু করা হয়। এই পদ্ধতিটি বর্তমানে শুধুমাত্র ম্যাপিং কার্নেলের মধ্যে সীমাবদ্ধ, যেগুলোকে সংক্ষেপে এই অংশে কেবল "কার্নেল" হিসেবে উল্লেখ করা হয়েছে। এই নতুন ফিচারটি স্ক্রিপ্টের ভেতর থেকে rs_allocation টাইপের অ্যালোকেশন তৈরি করাও সমর্থন করে। এখন একটি সম্পূর্ণ অ্যালগরিদম শুধুমাত্র একটি স্ক্রিপ্টের মধ্যেই প্রয়োগ করা সম্ভব, এমনকি যদি একাধিকবার কার্নেল চালু করার প্রয়োজন হয়। এর সুবিধা দ্বিগুণ: কোড আরও সহজে পাঠযোগ্য হয়, কারণ এটি একটি অ্যালগরিদমের প্রয়োগকে একটি ভাষাতেই রাখে; এবং কোড সম্ভাব্যভাবে দ্রুততর হয়, কারণ একাধিকবার কার্নেল চালু করার সময় জাভা এবং রেন্ডারস্ক্রিপ্টের মধ্যে ট্রানজিশন কম হয়।
সিঙ্গেল-সোর্স রেন্ডারস্ক্রিপ্টে, "রাইটিং এ রেন্ডারস্ক্রিপ্ট কার্নেল" অংশে বর্ণিত পদ্ধতি অনুযায়ী কার্নেল লিখতে হয়। এরপর একটি ইনভোকেবল ফাংশন লিখতে হয়, যা কার্নেল চালু করার জন্য rsForEach() ফাংশনটিকে কল করে। এই API-টি প্রথম প্যারামিটার হিসেবে একটি কার্নেল ফাংশন এবং এর পরে ইনপুট ও আউটপুট অ্যালোকেশন গ্রহণ করে। rsForEachWithOptions() নামক একটি অনুরূপ API-তে rs_script_call_t টাইপের একটি অতিরিক্ত আর্গুমেন্ট থাকে, যা কার্নেল ফাংশনের প্রক্রিয়াকরণের জন্য ইনপুট ও আউটপুট অ্যালোকেশনের উপাদানগুলোর একটি উপসেট নির্দিষ্ট করে দেয়।
RenderScript কম্পিউটেশন শুরু করতে, আপনাকে জাভা থেকে ইনভোকেবল ফাংশনটি কল করতে হবে। "জাভা কোড থেকে RenderScript ব্যবহার করা" অংশে দেওয়া ধাপগুলো অনুসরণ করুন। "উপযুক্ত কার্নেলগুলো চালু করা" ধাপে, 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" বিভাগে একটি উদাহরণ কার্নেল রয়েছে যা একটি ছবিকে ইনভার্ট করে। নিচের উদাহরণটি সিঙ্গেল-সোর্স রেন্ডারস্ক্রিপ্ট ব্যবহার করে একটি ছবিতে একাধিক ইফেক্ট প্রয়োগ করার জন্য সেটিকে আরও বিস্তৃত করে। এতে greyscale আরেকটি কার্নেল অন্তর্ভুক্ত রয়েছে, যা একটি রঙিন ছবিকে সাদা-কালোতে পরিণত করে। এরপর process() নামক একটি ইনভোকেবল ফাংশন একটি ইনপুট ছবির উপর পরপর সেই দুটি কার্নেল প্রয়োগ করে এবং একটি আউটপুট ছবি তৈরি করে। ইনপুট এবং আউটপুট উভয়ের জন্য অ্যালোকেশন rs_allocation টাইপের আর্গুমেন্ট হিসেবে পাস করা হয়।
// File: singlesource.rs #pragma version(1) #pragma rs java_package_name(com.android.rssample) static const float4 weight = {0.299f, 0.587f, 0.114f, 0.0f}; uchar4 RS_KERNEL invert(uchar4 in, uint32_t x, uint32_t y) { uchar4 out = in; out.r = 255 - in.r; out.g = 255 - in.g; out.b = 255 - in.b; return out; } uchar4 RS_KERNEL greyscale(uchar4 in) { const float4 inF = rsUnpackColor8888(in); const float4 outF = (float4){ dot(inF, weight) }; return rsPackColorTo8888(outF); } void process(rs_allocation inputImage, rs_allocation outputImage) { const uint32_t imageWidth = rsAllocationGetDimX(inputImage); const uint32_t imageHeight = rsAllocationGetDimY(inputImage); rs_allocation tmp = rsCreateAllocation_uchar4(imageWidth, imageHeight); rsForEach(invert, inputImage, tmp); rsForEach(greyscale, tmp, outputImage); }
আপনি জাভা বা কোটলিন থেকে নিম্নলিখিতভাবে process() ফাংশনটি কল করতে পারেন:
কোটলিন
val RS: RenderScript = RenderScript.create(context) val script = ScriptC_singlesource(RS) val inputAllocation: Allocation = Allocation.createFromBitmapResource( RS, resources, R.drawable.image ) val outputAllocation: Allocation = Allocation.createTyped( RS, inputAllocation.type, Allocation.USAGE_SCRIPT or Allocation.USAGE_IO_OUTPUT ) script.invoke_process(inputAllocation, outputAllocation)
জাভা
// File SingleSource.java RenderScript RS = RenderScript.create(context); ScriptC_singlesource script = new ScriptC_singlesource(RS); Allocation inputAllocation = Allocation.createFromBitmapResource( RS, getResources(), R.drawable.image); Allocation outputAllocation = Allocation.createTyped( RS, inputAllocation.getType(), Allocation.USAGE_SCRIPT | Allocation.USAGE_IO_OUTPUT); script.invoke_process(inputAllocation, outputAllocation);
এই উদাহরণটি দেখায় যে কীভাবে দুটি কার্নেল লঞ্চ জড়িত একটি অ্যালগরিদম সম্পূর্ণরূপে রেন্ডারস্ক্রিপ্ট ভাষাতেই প্রয়োগ করা যেতে পারে। সিঙ্গেল-সোর্স রেন্ডারস্ক্রিপ্ট ছাড়া, আপনাকে জাভা কোড থেকে উভয় কার্নেল লঞ্চ করতে হতো, যা কার্নেল লঞ্চকে কার্নেল ডেফিনিশন থেকে আলাদা করে দিত এবং পুরো অ্যালগরিদমটি বোঝা আরও কঠিন করে তুলত। সিঙ্গেল-সোর্স রেন্ডারস্ক্রিপ্ট কোড কেবল পাঠ করা সহজই নয়, এটি কার্নেল লঞ্চের সময় জাভা এবং স্ক্রিপ্টের মধ্যে ট্রানজিশনও দূর করে। কিছু পুনরাবৃত্তিমূলক অ্যালগরিদম শত শত বার কার্নেল লঞ্চ করতে পারে, যা এই ধরনের ট্রানজিশনের ওভারহেডকে যথেষ্ট পরিমাণে বাড়িয়ে তোলে।
স্ক্রিপ্ট গ্লোবালস
স্ক্রিপ্ট গ্লোবাল হলো একটি স্ক্রিপ্ট ( .rs ) ফাইলের একটি সাধারণ নন- static গ্লোবাল ভেরিয়েবল। filename .rs ফাইলে সংজ্ঞায়িত var নামের একটি স্ক্রিপ্ট গ্লোবালের জন্য, ScriptC_ filename ক্লাসে get_ var নামের একটি মেথড থাকবে। যদি গ্লোবালটি const না হয়, তবে set_ var নামের একটি মেথডও থাকবে।
একটি প্রদত্ত স্ক্রিপ্ট গ্লোবালের দুটি পৃথক মান থাকে — একটি জাভা মান এবং একটি স্ক্রিপ্ট মান। এই মানগুলো নিম্নরূপ আচরণ করে:
- স্ক্রিপ্টে var- এর একটি স্ট্যাটিক ইনিশিয়ালাইজার থাকলে, সেটি জাভা এবং স্ক্রিপ্ট উভয় ক্ষেত্রেই var- এর প্রাথমিক মান নির্দিষ্ট করে দেয়। অন্যথায়, সেই প্রাথমিক মান শূন্য হয়।
- স্ক্রিপ্টের মধ্যে var-কে অ্যাক্সেস করার মাধ্যমে এর স্ক্রিপ্ট ভ্যালু পড়া এবং লেখা হয়।
- `
get_ varমেথডটি জাভা ভ্যালু রিড করে। -
set_ varমেথডটি (যদি থাকে) জাভা ভ্যালুটি তাৎক্ষণিকভাবে লেখে, এবং স্ক্রিপ্ট ভ্যালুটি অ্যাসিঙ্ক্রোনাসলি লেখে।
দ্রষ্টব্য: এর অর্থ হলো, স্ক্রিপ্টের কোনো স্ট্যাটিক ইনিশিয়ালাইজার ছাড়া, স্ক্রিপ্টের ভেতর থেকে কোনো গ্লোবাল ভেরিয়েবলে লেখা মান জাভার কাছে দৃশ্যমান হয় না।
গভীরতায় হ্রাস কার্নেল
হ্রাসকরণ হলো একাধিক ডেটাকে একত্রিত করে একটি একক মানে পরিণত করার প্রক্রিয়া। এটি প্যারালাল প্রোগ্রামিং-এর একটি অত্যন্ত দরকারি মৌলিক উপাদান, যার প্রয়োগগুলো নিম্নরূপ:
- সমস্ত ডেটার যোগফল বা গুণফল গণনা করা
- সমস্ত ডেটার উপর যৌক্তিক অপারেশন (
and,or,xor) গণনা করা - ডেটার মধ্যে সর্বনিম্ন বা সর্বোচ্চ মান খুঁজে বের করা
- ডেটার মধ্যে কোনো নির্দিষ্ট মান বা তার স্থানাঙ্ক অনুসন্ধান করা হচ্ছে
অ্যান্ড্রয়েড ৭.০ (এপিআই লেভেল ২৪) এবং এর পরবর্তী সংস্করণগুলোতে, রেন্ডারস্ক্রিপ্ট রিডাকশন কার্নেল সমর্থন করে, যা ব্যবহারকারী-লিখিত কার্যকর রিডাকশন অ্যালগরিদম ব্যবহারের সুযোগ দেয়। আপনি ১, ২ বা ৩ ডাইমেনশনের ইনপুটের উপর রিডাকশন কার্নেল প্রয়োগ করতে পারেন।
উপরের একটি উদাহরণে একটি সাধারণ addint রিডাকশন কার্নেল দেখানো হয়েছে। এখানে একটি আরও জটিল findMinAndMax রিডাকশন কার্নেল দেওয়া হলো, যা একটি 1-মাত্রিক Allocation সর্বনিম্ন এবং সর্বোচ্চ long ভ্যালুগুলোর অবস্থান খুঁজে বের করে:
#define LONG_MAX (long)((1UL << 63) - 1) #define LONG_MIN (long)(1UL << 63) #pragma rs reduce(findMinAndMax) \ initializer(fMMInit) accumulator(fMMAccumulator) \ combiner(fMMCombiner) outconverter(fMMOutConverter) // Either a value and the location where it was found, or INITVAL. typedef struct { long val; int idx; // -1 indicates INITVAL } IndexedVal; typedef struct { IndexedVal min, max; } MinAndMax; // In discussion below, this initial value { { LONG_MAX, -1 }, { LONG_MIN, -1 } } // is called INITVAL. static void fMMInit(MinAndMax *accum) { accum->min.val = LONG_MAX; accum->min.idx = -1; accum->max.val = LONG_MIN; accum->max.idx = -1; } //---------------------------------------------------------------------- // In describing the behavior of the accumulator and combiner functions, // it is helpful to describe hypothetical functions // IndexedVal min(IndexedVal a, IndexedVal b) // IndexedVal max(IndexedVal a, IndexedVal b) // MinAndMax minmax(MinAndMax a, MinAndMax b) // MinAndMax minmax(MinAndMax accum, IndexedVal val) // // The effect of // IndexedVal min(IndexedVal a, IndexedVal b) // is to return the IndexedVal from among the two arguments // whose val is lesser, except that when an IndexedVal // has a negative index, that IndexedVal is never less than // any other IndexedVal; therefore, if exactly one of the // two arguments has a negative index, the min is the other // argument. Like ordinary arithmetic min and max, this function // is commutative and associative; that is, // // min(A, B) == min(B, A) // commutative // min(A, min(B, C)) == min((A, B), C) // associative // // The effect of // IndexedVal max(IndexedVal a, IndexedVal b) // is analogous (greater . . . never greater than). // // Then there is // // MinAndMax minmax(MinAndMax a, MinAndMax b) { // return MinAndMax(min(a.min, b.min), max(a.max, b.max)); // } // // Like ordinary arithmetic min and max, the above function // is commutative and associative; that is: // // minmax(A, B) == minmax(B, A) // commutative // minmax(A, minmax(B, C)) == minmax((A, B), C) // associative // // Finally define // // MinAndMax minmax(MinAndMax accum, IndexedVal val) { // return minmax(accum, MinAndMax(val, val)); // } //---------------------------------------------------------------------- // This function can be explained as doing: // *accum = minmax(*accum, IndexedVal(in, x)) // // This function simply computes minimum and maximum values as if // INITVAL.min were greater than any other minimum value and // INITVAL.max were less than any other maximum value. Note that if // *accum is INITVAL, then this function sets // *accum = IndexedVal(in, x) // // After this function is called, both accum->min.idx and accum->max.idx // will have nonnegative values: // - x is always nonnegative, so if this function ever sets one of the // idx fields, it will set it to a nonnegative value // - if one of the idx fields is negative, then the corresponding // val field must be LONG_MAX or LONG_MIN, so the function will always // set both the val and idx fields static void fMMAccumulator(MinAndMax *accum, long in, int x) { IndexedVal me; me.val = in; me.idx = x; if (me.val <= accum->min.val) accum->min = me; if (me.val >= accum->max.val) accum->max = me; } // This function can be explained as doing: // *accum = minmax(*accum, *val) // // This function simply computes minimum and maximum values as if // INITVAL.min were greater than any other minimum value and // INITVAL.max were less than any other maximum value. Note that if // one of the two accumulator data items is INITVAL, then this // function sets *accum to the other one. static void fMMCombiner(MinAndMax *accum, const MinAndMax *val) { if ((accum->min.idx < 0) || (val->min.val < accum->min.val)) accum->min = val->min; if ((accum->max.idx < 0) || (val->max.val > accum->max.val)) accum->max = val->max; } static void fMMOutConverter(int2 *result, const MinAndMax *val) { result->x = val->min.idx; result->y = val->max.idx; }
দ্রষ্টব্য: এখানে আরও উদাহরণ রিডাকশন কার্নেল রয়েছে।
একটি রিডাকশন কার্নেল চালানোর জন্য, RenderScript রানটাইম রিডাকশন প্রক্রিয়ার অবস্থা ধরে রাখার জন্য অ্যাকুমুলেটর ডেটা আইটেম নামক এক বা একাধিক ভেরিয়েবল তৈরি করে। RenderScript রানটাইম পারফরম্যান্স সর্বোচ্চ করার জন্য অ্যাকুমুলেটর ডেটা আইটেমের সংখ্যা এমনভাবে নির্বাচন করে। অ্যাকুমুলেটর ডেটা আইটেমের ধরন ( accumType ) কার্নেলের অ্যাকুমুলেটর ফাংশন দ্বারা নির্ধারিত হয় — ঐ ফাংশনের প্রথম আর্গুমেন্টটি হলো একটি অ্যাকুমুলেটর ডেটা আইটেমের পয়েন্টার। ডিফল্টরূপে, প্রতিটি অ্যাকুমুলেটর ডেটা আইটেম শূন্য দিয়ে ইনিশিয়ালাইজ করা হয় (যেন memset দ্বারা); তবে, আপনি ভিন্ন কিছু করার জন্য একটি ইনিশিয়ালাইজার ফাংশন লিখতে পারেন।
উদাহরণ: addint কার্নেলে, ইনপুট মানগুলো যোগ করার জন্য অ্যাকুমুলেটর ডেটা আইটেমগুলো ( int টাইপের) ব্যবহৃত হয়। এখানে কোনো ইনিশিয়ালাইজার ফাংশন নেই, তাই প্রতিটি অ্যাকুমুলেটর ডেটা আইটেমকে শূন্য দিয়ে ইনিশিয়ালাইজ করা হয়।
উদাহরণ: findMinAndMax কার্নেলে, অ্যাকুমুলেটর ডেটা আইটেমগুলো ( MinAndMax টাইপের) এখন পর্যন্ত পাওয়া সর্বনিম্ন এবং সর্বোচ্চ মানগুলোর হিসাব রাখতে ব্যবহৃত হয়। এগুলোকে যথাক্রমে LONG_MAX এবং LONG_MIN এ সেট করার জন্য একটি ইনিশিয়ালাইজার ফাংশন রয়েছে; এবং এই মানগুলোর অবস্থানকে -1-এ সেট করারও ব্যবস্থা আছে, যা নির্দেশ করে যে মানগুলো আসলে ইনপুটের প্রক্রিয়াকৃত (খালি) অংশে উপস্থিত নেই।
RenderScript ইনপুটের প্রতিটি স্থানাঙ্কের জন্য আপনার অ্যাকুমুলেটর ফাংশনটিকে একবার করে কল করে। সাধারণত, আপনার ফাংশনটির কাজ হলো ইনপুট অনুযায়ী অ্যাকুমুলেটর ডেটা আইটেমটিকে কোনো না কোনোভাবে আপডেট করা।
উদাহরণ: addint কার্নেলে, accumulator ফাংশনটি একটি ইনপুট এলিমেন্টের মানকে accumulator ডেটা আইটেমের সাথে যোগ করে।
উদাহরণ: findMinAndMax কার্নেলে, accumulator ফাংশনটি পরীক্ষা করে দেখে যে একটি ইনপুট Element-এর মান accumulator ডেটা আইটেমে রেকর্ড করা সর্বনিম্ন মানের চেয়ে কম বা সমান এবং/অথবা accumulator ডেটা আইটেমে রেকর্ড করা সর্বোচ্চ মানের চেয়ে বেশি বা সমান কিনা, এবং সেই অনুযায়ী accumulator ডেটা আইটেমটি আপডেট করে।
ইনপুট(গুলি)-এর প্রতিটি স্থানাঙ্কের জন্য অ্যাকুমুলেটর ফাংশনটি একবার কল করার পর, RenderScript-কে অবশ্যই অ্যাকুমুলেটর ডেটা আইটেমগুলিকে একত্রিত করে একটি একক অ্যাকুমুলেটর ডেটা আইটেমে পরিণত করতে হবে। এই কাজটি করার জন্য আপনি একটি কম্বাইনার ফাংশন লিখতে পারেন। যদি অ্যাকুমুলেটর ফাংশনটির একটিমাত্র ইনপুট থাকে এবং কোনো বিশেষ আর্গুমেন্ট না থাকে, তাহলে আপনার কম্বাইনার ফাংশন লেখার প্রয়োজন নেই; RenderScript অ্যাকুমুলেটর ডেটা আইটেমগুলিকে একত্রিত করার জন্য অ্যাকুমুলেটর ফাংশনটিই ব্যবহার করবে। (যদি এই ডিফল্ট আচরণটি আপনার পছন্দ না হয়, তবে আপনি একটি কম্বাইনার ফাংশন লিখতে পারেন।)
উদাহরণস্বরূপ: addint কার্নেলে কোনো combiner ফাংশন নেই, তাই accumulator ফাংশনটি ব্যবহৃত হবে। এটিই সঠিক আচরণ, কারণ যদি আমরা কোনো মানের সংগ্রহকে দুটি অংশে ভাগ করি এবং সেই দুটি অংশের মানগুলো আলাদাভাবে যোগ করি, তাহলে সেই দুটি যোগফলের যোগফল পুরো সংগ্রহটির যোগফলের সমান হয়।
উদাহরণ: findMinAndMax কার্নেলে, কম্বাইনার ফাংশনটি পরীক্ষা করে দেখে যে "সোর্স" অ্যাকুমুলেটর ডেটা আইটেম *val এ রেকর্ড করা সর্বনিম্ন মানটি "ডেস্টিনেশন" অ্যাকুমুলেটর ডেটা আইটেম *accum এ রেকর্ড করা সর্বনিম্ন মানের চেয়ে কম কি না, এবং সেই অনুযায়ী *accum আপডেট করে। এটি সর্বোচ্চ মানের জন্যও একই ধরনের কাজ করে। এর ফলে *accum সেই অবস্থায় আপডেট হয়, যে অবস্থায় এটি থাকত যদি সমস্ত ইনপুট মান *accum এ জমা হতো, কিছু *accum এ এবং কিছু *val এ জমা হওয়ার পরিবর্তে।
সমস্ত অ্যাকুমুলেটর ডেটা আইটেম একত্রিত করার পরে, RenderScript জাভাতে ফেরত পাঠানোর জন্য রিডাকশনের ফলাফল নির্ধারণ করে। এই কাজটি করার জন্য আপনি একটি আউটকনভার্টার ফাংশন লিখতে পারেন। যদি আপনি চান যে একত্রিত অ্যাকুমুলেটর ডেটা আইটেমগুলোর চূড়ান্ত মানই রিডাকশনের ফলাফল হোক, তাহলে আপনার আউটকনভার্টার ফাংশন লেখার প্রয়োজন নেই।
উদাহরণ: addint কার্নেলে কোনো outconverter ফাংশন নেই। একত্রিত ডেটা আইটেমগুলোর চূড়ান্ত মান হলো ইনপুটের সমস্ত উপাদানের যোগফল, যা আমরা ফেরত দিতে চাই।
উদাহরণ: findMinAndMax কার্নেলে, outconverter ফাংশনটি সমস্ত অ্যাকুমুলেটর ডেটা আইটেমের সমন্বয়ের ফলে প্রাপ্ত সর্বনিম্ন এবং সর্বোচ্চ মানের অবস্থান ধারণ করার জন্য একটি int2 ফলাফল মান ইনিশিয়ালাইজ করে।
একটি রিডাকশন কার্নেল লেখা
#pragma rs reduce একটি রিডাকশন কার্নেলকে সংজ্ঞায়িত করে, যেখানে এর নাম এবং কার্নেলটি গঠনকারী ফাংশনগুলোর নাম ও ভূমিকা উল্লেখ করা থাকে। এই ধরনের সমস্ত ফাংশন অবশ্যই static হতে হবে। একটি রিডাকশন কার্নেলের জন্য সর্বদা একটি accumulator ফাংশন প্রয়োজন; কার্নেলটি দিয়ে আপনি কী করাতে চান তার উপর নির্ভর করে, আপনি অন্য ফাংশনগুলোর কিছু বা সবগুলো বাদ দিতে পারেন।
#pragma rs reduce(kernelName) \ initializer(initializerName) \ accumulator(accumulatorName) \ combiner(combinerName) \ outconverter(outconverterName)
#pragma এর অন্তর্ভুক্ত উপাদানগুলোর অর্থ নিম্নরূপ:
-
reduce( kernelName )(বাধ্যতামূলক): এটি নির্দেশ করে যে একটি রিডাকশন কার্নেল সংজ্ঞায়িত করা হচ্ছে। একটি রিফ্লেক্টেড জাভা মেথডreduce_ kernelNameকার্নেলটি চালু করবে। initializer( initializerName )(ঐচ্ছিক): এই রিডাকশন কার্নেলের জন্য ইনিশিয়ালাইজার ফাংশনের নাম নির্দিষ্ট করে। যখন আপনি কার্নেলটি চালু করেন, RenderScript প্রতিটি অ্যাকুমুলেটর ডেটা আইটেমের জন্য এই ফাংশনটি একবার কল করে। ফাংশনটি অবশ্যই এইভাবে সংজ্ঞায়িত করতে হবে:static void initializerName(accumType *accum) { … }
accumহলো এই ফাংশন দ্বারা ইনিশিয়ালাইজ করার জন্য একটি অ্যাকুমুলেটর ডেটা আইটেমের পয়েন্টার।যদি আপনি কোনো ইনিশিয়ালাইজার ফাংশন প্রদান না করেন, তাহলে RenderScript প্রতিটি অ্যাকুমুলেটর ডেটা আইটেমকে শূন্য দিয়ে ইনিশিয়ালাইজ করে (যেন
memsetদ্বারা), এবং এর আচরণটি এমন হয় যেন সেখানে এইরকম একটি ইনিশিয়ালাইজার ফাংশন রয়েছে:static void initializerName(accumType *accum) { memset(accum, 0, sizeof(*accum)); }
accumulator( accumulatorName )(বাধ্যতামূলক): এই রিডাকশন কার্নেলের জন্য অ্যাকুমুলেটর ফাংশনের নাম নির্দিষ্ট করে। যখন আপনি কার্নেলটি চালু করেন, RenderScript ইনপুট(গুলি) অনুযায়ী কোনোভাবে একটি অ্যাকুমুলেটর ডেটা আইটেম আপডেট করার জন্য, ইনপুট(গুলি)-এর প্রতিটি স্থানাঙ্কের জন্য এই ফাংশনটিকে একবার কল করে। ফাংশনটি অবশ্যই এইভাবে সংজ্ঞায়িত করতে হবে:static void accumulatorName(accumType *accum, in1Type in1, …, inNType inN [, specialArguments]) { … }
accumহলো একটি অ্যাকুমুলেটর ডেটা আইটেমের পয়েন্টার, যা এই ফাংশনটি পরিবর্তন করবে।in1থেকেin Nপর্যন্ত হলো এক বা একাধিক আর্গুমেন্ট, যা কার্নেল লঞ্চে প্রদত্ত ইনপুটের উপর ভিত্তি করে স্বয়ংক্রিয়ভাবে পূরণ হয়; প্রতিটি ইনপুটের জন্য একটি করে আর্গুমেন্ট। অ্যাকুমুলেটর ফাংশনটি ঐচ্ছিকভাবে যেকোনো বিশেষ আর্গুমেন্ট গ্রহণ করতে পারে।একাধিক ইনপুট সহ একটি কার্নেলের উদাহরণ হলো
dotProduct।-
combiner( combinerName )(ঐচ্ছিক): এই রিডাকশন কার্নেলের জন্য কম্বাইনার ফাংশনের নাম নির্দিষ্ট করে। RenderScript ইনপুট(গুলি) এর প্রতিটি স্থানাঙ্কের জন্য একবার করে অ্যাকুমুলেটর ফাংশনটি কল করার পর, সমস্ত অ্যাকুমুলেটর ডেটা আইটেমকে একটি একক অ্যাকুমুলেটর ডেটা আইটেমে একত্রিত করার জন্য যতবার প্রয়োজন ততবার এই ফাংশনটি কল করে। ফাংশনটি অবশ্যই এইভাবে সংজ্ঞায়িত করতে হবে:
static void combinerName(accumType *accum, const accumType *other) { … }
accumহলো এই ফাংশনের পরিবর্তন করার জন্য একটি 'গন্তব্য' অ্যাকুমুলেটর ডেটা আইটেমের পয়েন্টার।otherহলো এই ফাংশনের*accumএ 'একত্রিত' করার জন্য একটি 'উৎস' অ্যাকুমুলেটর ডেটা আইটেমের পয়েন্টার।দ্রষ্টব্য: এমনটা হতে পারে যে
*accum,*other, অথবা উভয়ই ইনিশিয়ালাইজ করা হয়েছে কিন্তু কখনও accumulator ফাংশনে পাঠানো হয়নি; অর্থাৎ, এক বা উভয়ই কোনো ইনপুট ডেটা অনুযায়ী কখনও আপডেট করা হয়নি। উদাহরণস্বরূপ, findMinAndMax কার্নেলে,fMMCombinerকম্বাইনার ফাংশনটি স্পষ্টভাবেidx < 0কিনা তা পরীক্ষা করে, কারণ এটি এমন একটি অ্যাকুমুলেটর ডেটা আইটেম নির্দেশ করে, যার মান হলো INITVAL ।আপনি যদি কোনো কম্বাইনার ফাংশন প্রদান না করেন, তাহলে 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 এগুলো সবই ভিন্ন।
আপনি কী অনুমান করতে পারেন না?
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, ifI 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 leaveAthe same -
combinerName (& I , & A )must leaveIthe same asA
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 )leavesAthe same, becauseIisINITVAL. -
fMMCombiner(& I , & A )setsItoA, becauseIisINITVAL.
Therefore, INITVAL is indeed an identity value.
The combiner function must be commutative . That is, if A and B are accumulator data items initialized by the initializer function, and that may have been passed to the accumulator function zero or more times, then combinerName (& A , & B ) must set A to the same value that combinerName (& B , & A ) sets B .
Example: In the addint kernel, the combiner function adds the two accumulator data item values; addition is commutative.
Example: In the findMinAndMax kernel, fMMCombiner(& A , & B ) is the same as A = minmax( A , B ) , and minmax is commutative, so fMMCombiner is also.
The combiner function must be associative . That is, if A , B , and C are accumulator data items initialized by the initializer function, and that may have been passed to the accumulator function zero or more times, then the following two code sequences must set A to the same value :
combinerName(&A, &B); combinerName(&A, &C);
combinerName(&B, &C); combinerName(&A, &B);
Example: In the addint kernel, the combiner function adds the two accumulator data item values:
A = A + B A = A + C // Same as // A = (A + B) + C
B = B + C A = A + B // Same as // A = A + (B + C) // B = B + C
Addition is associative, and so the combiner function is also.
Example: In the findMinAndMax kernel,
fMMCombiner(&A, &B)
A = minmax(A, B)
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 asB = V - Statement 4 is the same as
A += B, which is the same asA += 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
which, because B is the initial value, is the same asB = minmax(B, IndexedVal(V, X))
B = IndexedVal(V, X)
- Statement 4 is the same as
which is the same asA = minmax(A, B)
A = minmax(A, IndexedVal(V, X))
Statements 1 and 4 set A to the same value, and so this kernel obeys the basic folding rule.
Calling a reduction kernel from Java code
For a reduction kernel named kernelName defined in the file filename .rs , there are three methods reflected in the class ScriptC_ filename :
কোটলিন
// Function 1 fun reduce_kernelName(ain1: Allocation, …, ainN: Allocation): javaFutureType // Function 2 fun reduce_kernelName(ain1: Allocation, …, ainN: Allocation, sc: Script.LaunchOptions): javaFutureType // Function 3 fun reduce_kernelName(in1: Array<devecSiIn1Type>, …, inN: Array<devecSiInNType>): javaFutureType
জাভা
// Method 1 public javaFutureType reduce_kernelName(Allocation ain1, …, Allocation ainN); // Method 2 public javaFutureType reduce_kernelName(Allocation ain1, …, Allocation ainN, Script.LaunchOptions sc); // Method 3 public javaFutureType reduce_kernelName(devecSiIn1Type[] in1, …, devecSiInNType[] inN);
Here are some examples of calling the addint kernel:
কোটলিন
val script = ScriptC_example(renderScript) // 1D array // and obtain answer immediately val input1 = intArrayOf(…) val sum1: Int = script.reduce_addint(input1).get() // Method 3 // 2D allocation // and do some additional work before obtaining answer val typeBuilder = Type.Builder(RS, Element.I32(RS)).apply { setX(…) setY(…) } val input2: Allocation = Allocation.createTyped(RS, typeBuilder.create()).also { populateSomehow(it) // fill in input Allocation with data } val result2: ScriptC_example.result_int = script.reduce_addint(input2) // Method 1 doSomeAdditionalWork() // might run at same time as reduction val sum2: Int = result2.get()
জাভা
ScriptC_example script = new ScriptC_example(renderScript); // 1D array // and obtain answer immediately int input1[] = …; int sum1 = script.reduce_addint(input1).get(); // Method 3 // 2D allocation // and do some additional work before obtaining answer Type.Builder typeBuilder = new Type.Builder(RS, Element.I32(RS)); typeBuilder.setX(…); typeBuilder.setY(…); Allocation input2 = createTyped(RS, typeBuilder.create()); populateSomehow(input2); // fill in input Allocation with data ScriptC_example.result_int result2 = script.reduce_addint(input2); // Method 1 doSomeAdditionalWork(); // might run at same time as reduction int sum2 = result2.get();
Method 1 has one input Allocation argument for every input argument in the kernel's accumulator function . The RenderScript runtime checks to ensure that all of the input Allocations have the same dimensions and that the Element type of each of the input Allocations matches that of the corresponding input argument of the accumulator function's prototype. If any of these checks fail, RenderScript throws an exception. The kernel executes over every coordinate in those dimensions.
Method 2 is the same as Method 1 except that Method 2 takes an additional argument sc that can be used to limit the kernel execution to a subset of the coordinates.
Method 3 is the same as Method 1 except that instead of taking Allocation inputs it takes Java array inputs. This is a convenience that saves you from having to write code to explicitly create an Allocation and copy data to it from a Java array. However, using Method 3 instead of Method 1 does not increase the performance of the code . For each input array, Method 3 creates a temporary 1-dimensional Allocation with the appropriate Element type and setAutoPadding(boolean) enabled, and copies the array to the Allocation as if by the appropriate copyFrom() method of Allocation . It then calls Method 1, passing those temporary Allocations.
NOTE: If your application will make multiple kernel calls with the same array, or with different arrays of the same dimensions and Element type, you may improve performance by explicitly creating, populating, and reusing Allocations yourself, instead of by using Method 3.
javaFutureType , the return type of the reflected reduction methods, is a reflected static nested class within the ScriptC_ filename class. It represents the future result of a reduction kernel run. To obtain the actual result of the run, call the get() method of that class, which returns a value of type javaResultType . get() is synchronous .
কোটলিন
class ScriptC_filename(rs: RenderScript) : ScriptC(…) { object javaFutureType { fun get(): javaResultType { … } } }
জাভা
public class ScriptC_filename extends ScriptC { public static class javaFutureType { public javaResultType get() { … } } }
javaResultType is determined from the resultType of the outconverter function . Unless resultType is an unsigned type (scalar, vector, or array), javaResultType is the directly corresponding Java type. If resultType is an unsigned type and there is a larger Java signed type, then javaResultType is that larger Java signed type; otherwise, it is the directly corresponding Java type. For example:
- If resultType is
int,int2, orint[15], then javaResultType isint,Int2, orint[]. All values of resultType can be represented by javaResultType . - If resultType is
uint,uint2, oruint[15], then javaResultType islong,Long2, orlong[]. All values of resultType can be represented by javaResultType . - If resultType is
ulong,ulong2, orulong[15], then javaResultType islong,Long2, orlong[]. There are certain values of resultType that cannot be represented by javaResultType .
javaFutureType is the future result type corresponding to the resultType of the outconverter function .
- If resultType is not an array type, then javaFutureType is
result_ resultType. - If resultType is an array of length Count with members of type memberType , then javaFutureType is
resultArray Count _ memberType.
উদাহরণস্বরূপ:
কোটলিন
class ScriptC_filename(rs: RenderScript) : ScriptC(…) { // for kernels with int result object result_int { fun get(): Int = … } // for kernels with int[10] result object resultArray10_int { fun get(): IntArray = … } // for kernels with int2 result // note that the Kotlin type name "Int2" is not the same as the script type name "int2" object result_int2 { fun get(): Int2 = … } // for kernels with int2[10] result // note that the Kotlin type name "Int2" is not the same as the script type name "int2" object resultArray10_int2 { fun get(): Array<Int2> = … } // for kernels with uint result // note that the Kotlin type "long" is a wider signed type than the unsigned script type "uint" object result_uint { fun get(): Long = … } // for kernels with uint[10] result // note that the Kotlin type "long" is a wider signed type than the unsigned script type "uint" object resultArray10_uint { fun get(): LongArray = … } // for kernels with uint2 result // note that the Kotlin type "Long2" is a wider signed type than the unsigned script type "uint2" object result_uint2 { fun get(): Long2 = … } // for kernels with uint2[10] result // note that the Kotlin type "Long2" is a wider signed type than the unsigned script type "uint2" object resultArray10_uint2 { fun get(): Array<Long2> = … } }
জাভা
public class ScriptC_filename extends ScriptC { // for kernels with int result public static class result_int { public int get() { … } } // for kernels with int[10] result public static class resultArray10_int { public int[] get() { … } } // for kernels with int2 result // note that the Java type name "Int2" is not the same as the script type name "int2" public static class result_int2 { public Int2 get() { … } } // for kernels with int2[10] result // note that the Java type name "Int2" is not the same as the script type name "int2" public static class resultArray10_int2 { public Int2[] get() { … } } // for kernels with uint result // note that the Java type "long" is a wider signed type than the unsigned script type "uint" public static class result_uint { public long get() { … } } // for kernels with uint[10] result // note that the Java type "long" is a wider signed type than the unsigned script type "uint" public static class resultArray10_uint { public long[] get() { … } } // for kernels with uint2 result // note that the Java type "Long2" is a wider signed type than the unsigned script type "uint2" public static class result_uint2 { public Long2 get() { … } } // for kernels with uint2[10] result // note that the Java type "Long2" is a wider signed type than the unsigned script type "uint2" public static class resultArray10_uint2 { public Long2[] get() { … } } }
If javaResultType is an object type (including an array type), each call to javaFutureType .get() on the same instance will return the same object.
If javaResultType cannot represent all values of type resultType , and a reduction kernel produces an unrepresentible value, then javaFutureType .get() throws an exception.
Method 3 and devecSiInXType
devecSiInXType is the Java type corresponding to the inXType of the corresponding argument of the accumulator function . Unless inXType is an unsigned type or a vector type, devecSiInXType is the directly corresponding Java type. If inXType is an unsigned scalar type, then devecSiInXType is the Java type directly corresponding to the signed scalar type of the same size. If inXType is a signed vector type, then devecSiInXType is the Java type directly corresponding to the vector component type. If inXType is an unsigned vector type, then devecSiInXType is the Java type directly corresponding to the signed scalar type of the same size as the vector component type. For example:
- If inXType is
int, then devecSiInXType isint. - If inXType is
int2, then devecSiInXType isint. 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 thecopyFrom()methods ofAllocationwork. - If inXType is
uint, then deviceSiInXType isint. 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 thecopyFrom()methods ofAllocationwork. - If inXType is
uint2, then deviceSiInXType isint. This is a combination of the wayint2anduintare 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.