গ্রাফিক্স ডেটা এবং শেডার গণনার সাংখ্যিক বিন্যাস আপনার গেমের পারফরম্যান্সের উপর উল্লেখযোগ্য প্রভাব ফেলতে পারে।
সর্বোত্তম ফরম্যাটগুলো নিম্নলিখিত কাজগুলো করে:
- জিপিইউ ক্যাশে ব্যবহারের দক্ষতা বৃদ্ধি করুন
- মেমরি ব্যান্ডউইথের ব্যবহার কমিয়ে শক্তি সাশ্রয় করে এবং কর্মক্ষমতা বৃদ্ধি করে।
- শেডার প্রোগ্রামগুলিতে গণনামূলক থ্রুপুট সর্বাধিক করুন
- আপনার গেমের সিপিইউ এবং র্যামের ব্যবহার কমান।
ফ্লোটিং পয়েন্ট ফর্ম্যাট
আধুনিক 3D গ্রাফিক্সের বেশিরভাগ গণনা এবং ডেটা ফ্লোটিং পয়েন্ট সংখ্যা ব্যবহার করে। অ্যান্ড্রয়েডের ভলকান ৩২ বা ১৬ বিট আকারের ফ্লোটিং পয়েন্ট সংখ্যা ব্যবহার করে। একটি ৩২-বিট ফ্লোটিং পয়েন্ট সংখ্যাকে সাধারণত সিঙ্গেল প্রিসিশন বা ফুল প্রিসিশন বলা হয়; আর একটি ১৬-বিট ফ্লোটিং পয়েন্ট সংখ্যাকে হাফ প্রিসিশন বলা হয়।
ভুলকান একটি ৬৪-বিট ফ্লোটিং পয়েন্ট টাইপ সংজ্ঞায়িত করে, কিন্তু অ্যান্ড্রয়েডের ভুলকান ডিভাইসগুলোতে এই টাইপটি সাধারণত সমর্থিত নয় এবং এর ব্যবহার সুপারিশ করা হয় না। একটি ৬৪-বিট ফ্লোটিং পয়েন্ট সংখ্যাকে সাধারণত ডাবল প্রিসিশন বলা হয়।
পূর্ণসংখ্যার ফর্ম্যাট
ডেটা এবং গণনার জন্য সাইনড এবং আনসাইনড পূর্ণসংখ্যাও ব্যবহৃত হয়। স্ট্যান্ডার্ড পূর্ণসংখ্যার আকার হলো ৩২ বিট। অন্যান্য বিট আকারের সমর্থন ডিভাইস-নির্ভর। অ্যান্ড্রয়েড চালিত ভলকান ডিভাইসগুলো সাধারণত ১৬-বিট এবং ৮-বিট পূর্ণসংখ্যা সমর্থন করে। ভলকান একটি ৬৪-বিট পূর্ণসংখ্যা টাইপ সংজ্ঞায়িত করে, কিন্তু অ্যান্ড্রয়েডের ভলকান ডিভাইসগুলোতে এই টাইপটি সাধারণত সমর্থিত নয় এবং এর ব্যবহার সুপারিশ করা হয় না।
উপ-সর্বোত্তম অর্ধ-নির্ভুল আচরণ
আধুনিক জিপিইউ আর্কিটেকচার দুটি ১৬-বিট মানকে একত্রিত করে একটি ৩২-বিট জোড়া তৈরি করে এবং সেই জোড়ার উপর কাজ করার জন্য নির্দেশাবলী প্রয়োগ করে। সর্বোত্তম পারফরম্যান্সের জন্য, স্কেলার ১৬-বিট ফ্লোট ভেরিয়েবল ব্যবহার করা এড়িয়ে চলুন; ডেটাকে দুই বা চার-উপাদানের ভেক্টরে রূপান্তর করুন। শেডার কম্পাইলার ভেক্টর অপারেশনে স্কেলার মান ব্যবহার করতে সক্ষম হতে পারে। তবে, আপনি যদি স্কেলার অপ্টিমাইজ করার জন্য কম্পাইলারের উপর নির্ভর করেন, তাহলে ভেক্টরাইজেশন যাচাই করার জন্য কম্পাইলারের আউটপুট পরীক্ষা করুন।
৩২-বিট এবং ১৬-বিট প্রিসিশন ফ্লোটিং পয়েন্টে রূপান্তর করার জন্য গণনার প্রয়োজন হয়। আপনার কোডে প্রিসিশন রূপান্তর কমিয়ে এই অতিরিক্ত খরচ হ্রাস করুন।
আপনার অ্যালগরিদমগুলোর ১৬-বিট এবং ৩২-বিট সংস্করণের মধ্যে পারফরম্যান্সের পার্থক্য যাচাই করুন। হাফ প্রিসিশন সবসময় পারফরম্যান্সের উন্নতি ঘটায় না, বিশেষ করে জটিল গণনার ক্ষেত্রে। যে অ্যালগরিদমগুলো ভেক্টরাইজড ডেটার উপর ফিউজড মাল্টিপ্লাই-অ্যাড (FMA) নির্দেশাবলী ব্যাপকভাবে ব্যবহার করে, সেগুলো হাফ প্রিসিশনে উন্নত পারফরম্যান্সের জন্য উপযুক্ত।
সংখ্যাসূচক বিন্যাস সমর্থন
অ্যান্ড্রয়েডের সমস্ত ভলকান ডিভাইস ডেটা এবং শেডার গণনার ক্ষেত্রে সিঙ্গেল-প্রিসিশন, ৩২-বিট ফ্লোটিং পয়েন্ট সংখ্যা এবং ৩২-বিট পূর্ণসংখ্যা সমর্থন করে। অন্যান্য ফরম্যাটের জন্য সমর্থন উপলব্ধ থাকার কোনো নিশ্চয়তা নেই এবং যদি উপলব্ধ থাকেও, তবে তা সমস্ত ব্যবহারের ক্ষেত্রের জন্য নিশ্চিত নয়।
ভলকানে ঐচ্ছিক সাংখ্যিক ফরম্যাটের জন্য দুই ধরনের সাপোর্ট রয়েছে: অ্যারিথমেটিক এবং স্টোরেজ। কোনো নির্দিষ্ট ফরম্যাট ব্যবহার করার আগে, নিশ্চিত করুন যে ডিভাইসটি উভয় ক্যাটাগরিতেই সেটিকে সাপোর্ট করে।
পাটিগণিত সহায়তা
শেডার প্রোগ্রামে ব্যবহারযোগ্য হওয়ার জন্য একটি ভলকান ডিভাইসকে অবশ্যই কোনো একটি সাংখ্যিক ফরম্যাটের জন্য গাণিতিক সমর্থন ঘোষণা করতে হবে। অ্যান্ড্রয়েডের ভলকান ডিভাইসগুলো সাধারণত গাণিতিক কাজের জন্য নিম্নলিখিত ফরম্যাটগুলো সমর্থন করে:
- ৩২-বিট পূর্ণসংখ্যা (বাধ্যতামূলক)
- ৩২-বিট ফ্লোটিং পয়েন্ট (বাধ্যতামূলক)
- ৮-বিট পূর্ণসংখ্যা (ঐচ্ছিক)
- ১৬-বিট পূর্ণসংখ্যা (ঐচ্ছিক)
- ১৬-বিট হাফ-প্রিসিশন ফ্লোটিং পয়েন্ট (ঐচ্ছিক)
কোনো ভলকান ডিভাইস গাণিতিক কাজের জন্য ১৬-বিট পূর্ণসংখ্যা সমর্থন করে কিনা তা নির্ধারণ করতে, vkGetPhysicalDeviceFeatures2() ফাংশনটি কল করে ডিভাইসটির ফিচারগুলো পুনরুদ্ধার করুন এবং VkPhysicalDeviceFeatures2 রেজাল্ট স্ট্রাকচারের shaderInt16 ফিল্ডটি true কিনা তা পরীক্ষা করুন।
কোনো ভলকান ডিভাইস ১৬-বিট ফ্লোট নাকি ৮-বিট ইন্টিজার সমর্থন করে, তা নির্ধারণ করতে নিম্নলিখিত ধাপগুলো অনুসরণ করুন:
- ডিভাইসটি VK_KHR_shader_float16_int8 ভলকান এক্সটেনশনটি সমর্থন করে কিনা তা পরীক্ষা করুন। ১৬-বিট ফ্লোট এবং ৮-বিট ইন্টিজার সমর্থনের জন্য এই এক্সটেনশনটি প্রয়োজন।
- যদি
VK_KHR_shader_float16_int8সমর্থিত হয়, তাহলে একটি VkPhysicalDeviceShaderFloat16Int8Features স্ট্রাকচার পয়েন্টারকে একটিVkPhysicalDeviceFeatures2.pNextচেইনে যুক্ত করুন। -
vkGetPhysicalDeviceFeatures2()কল করার পরVkPhysicalDeviceShaderFloat16Int8Featuresরেজাল্ট স্ট্রাকচারেরshaderFloat16এবংshaderInt8ফিল্ডগুলো পরীক্ষা করুন। যদি ফিল্ডের মানtrueহয়, তাহলে ফরম্যাটটি শেডার প্রোগ্রাম অ্যারিথমেটিকের জন্য সমর্থিত।
ভলকান ১.১ বা ২০২২ অ্যান্ড্রয়েড বেসলাইন প্রোফাইলে এটি আবশ্যিক না হলেও, অ্যান্ড্রয়েড ডিভাইসগুলোতে VK_KHR_shader_float16_int8 এক্সটেনশনটির সমর্থন খুবই প্রচলিত।
স্টোরেজ সমর্থন
একটি ভলকান ডিভাইসকে নির্দিষ্ট স্টোরেজ টাইপের জন্য একটি ঐচ্ছিক সাংখ্যিক ফরম্যাটের সমর্থন ঘোষণা করতে হয়। VK_KHR_16bit_storage এক্সটেনশনটি ১৬-বিট পূর্ণসংখ্যা এবং ১৬-বিট ফ্লোটিং-পয়েন্ট ফরম্যাটের সমর্থন ঘোষণা করে। এই এক্সটেনশনটি চারটি স্টোরেজ টাইপ সংজ্ঞায়িত করে। একটি ডিভাইস কোনোটির জন্যই নয়, কয়েকটির জন্য, অথবা সমস্ত স্টোরেজ টাইপের জন্যই ১৬-বিট সংখ্যা সমর্থন করতে পারে।
স্টোরেজের প্রকারগুলি হলো:
- স্টোরেজ বাফার অবজেক্ট
- ইউনিফর্ম বাফার অবজেক্ট
- ক্রমাগত ব্লক ধাক্কা দিন
- শেডার ইনপুট এবং আউটপুট ইন্টারফেস
অ্যান্ড্রয়েডের বেশিরভাগ, তবে সব নয়, ভলকান ১.১ ডিভাইস স্টোরেজ বাফার অবজেক্টে ১৬-বিট ফরম্যাট সমর্থন করে। জিপিইউ মডেলের উপর ভিত্তি করে সমর্থনের বিষয়টি ধরে নেবেন না। কোনো নির্দিষ্ট জিপিইউ-এর জন্য পুরোনো ড্রাইভারযুক্ত ডিভাইসগুলো স্টোরেজ বাফার অবজেক্ট সমর্থন নাও করতে পারে, অথচ নতুন ড্রাইভারযুক্ত ডিভাইসগুলো তা করে থাকে।
ইউনিফর্ম বাফার, পুশ কনস্ট্যান্ট ব্লক এবং শেডার ইনপুট/আউটপুট ইন্টারফেসে ১৬-বিট ফরম্যাটের সমর্থন সাধারণত জিপিইউ প্রস্তুতকারকের উপর নির্ভর করে। অ্যান্ড্রয়েডে, একটি জিপিইউ সাধারণত এই তিন ধরনের সবকটিই সমর্থন করে অথবা কোনোটিই করে না।
একটি উদাহরণ ফাংশন যা ভলকান অ্যারিথমেটিক এবং স্টোরেজ ফরম্যাট সমর্থনের জন্য পরীক্ষা করে:
struct ReducedPrecisionSupportInfo {
// Arithmetic support
bool has_8_bit_int_ = false;
bool has_16_bit_int_ = false;
bool has_16_bit_float_ = false;
// Storage support
bool has_16_bit_SSBO_ = false;
bool has_16_bit_UBO_ = false;
bool has_16_bit_push_ = false;
bool has_16_bit_input_output_ = false;
// Use 16-bit floats if we have arithmetic
// support and at least SSBO storage support.
bool use_16bit_floats_ = false;
};
void CheckFormatSupport(VkPhysicalDevice physical_device,
ReducedPrecisionSupportInfo &info) {
// Retrieve the device extension list so we
// can check for our desired extensions.
uint32_t device_extension_count;
vkEnumerateDeviceExtensionProperties(physical_device, nullptr,
&device_extension_count, nullptr);
std::vector<VkExtensionProperties> device_extensions(device_extension_count);
vkEnumerateDeviceExtensionProperties(physical_device, nullptr,
&device_extension_count, device_extensions.data());
bool has_16_8_extension = HasDeviceExtension("VK_KHR_shader_float16_int8",
device_extensions);
// Initialize the device features structure and
// chain the storage features structure and 8/16-bit
// support structure if applicable.
VkPhysicalDeviceFeatures2 device_features;
memset(&device_features, 0, sizeof(device_features));
device_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
VkPhysicalDeviceShaderFloat16Int8Features f16_int8_features;
memset(&f16_int8_features, 0, sizeof(f16_int8_features));
f16_int8_features.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FLOAT16_INT8_FEATURES_KHR;
VkPhysicalDevice16BitStorageFeatures storage_features;
memset(&storage_features, 0, sizeof(storage_features));
storage_features.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES;
device_features.pNext = &storage_features;
if (has_16_8_extension) {
storage_features.pNext = &f16_int8_features;
}
vkGetPhysicalDeviceFeatures2(physical_device, &device_features);
// Parse the storage features and determine
// what kinds of 16-bit storage access are available.
if (storage_features.storageBuffer16BitAccess ||
storage_features.uniformAndStorageBuffer16BitAccess) {
info.has_16_bit_SSBO_ = true;
}
info.has_16_bit_UBO_ = storage_features.uniformAndStorageBuffer16BitAccess;
info.has_16_bit_push_ = storage_features.storagePushConstant16;
info.has_16_bit_input_output_ = storage_features.storageInputOutput16;
info.has_16_bit_int_ = device_features.features.shaderInt16;
if (has_16_8_extension) {
info.has_16_bit_float_ = f16_int8_features.shaderFloat16;
info.has_8_bit_int_ = f16_int8_features.shaderInt8;
}
// Get arithmetic and at least some form of storage
// support before enabling 16-bit float usage.
if (info.has_16_bit_float_ && info.has_16_bit_SSBO_) {
info.use_16bit_floats_ = true;
}
}
ডেটার জন্য নির্ভুলতার স্তর
একটি হাফ-প্রিসিশন ফ্লোটিং পয়েন্ট সংখ্যা, একটি সিঙ্গেল-প্রিসিশন ফ্লোটিং পয়েন্ট সংখ্যার তুলনায় কম প্রিসিশনে একটি ছোট পরিসরের মান উপস্থাপন করতে পারে। সিঙ্গেল-প্রিসিশনের তুলনায় হাফ-প্রিসিশন প্রায়শই একটি সহজ এবং উপলব্ধিগতভাবে লসহীন বিকল্প। তবে, সব ক্ষেত্রে হাফ-প্রিসিশন ব্যবহারিক নাও হতে পারে। কিছু ধরণের ডেটার জন্য, এই সীমিত পরিসর এবং প্রিসিশনের কারণে গ্রাফিক আর্টিফ্যাক্ট বা ভুল রেন্ডারিং হতে পারে।
যেসব ডেটা টাইপ হাফ-প্রিসিশন ফ্লোটিং পয়েন্টে উপস্থাপনের জন্য উপযুক্ত, সেগুলো হলো:
- স্থানীয় স্থানের স্থানাঙ্কে অবস্থানের ডেটা
- সীমিত ইউভি র্যাপিং সহ ছোট টেক্সচারের জন্য টেক্সচার ইউভি, যা -১.০ থেকে ১.০ স্থানাঙ্ক পরিসরে সীমাবদ্ধ করা যেতে পারে।
- অভিলম্ব, স্পর্শক এবং দ্বি-স্পর্শক ডেটা
- ভার্টেক্স রঙের ডেটা
- 0.0-কে কেন্দ্র করে কম নির্ভুলতার প্রয়োজনীয়তাযুক্ত ডেটা
হাফ-প্রিসিশন ফ্লোটে উপস্থাপনের জন্য যেসব ডেটা টাইপ সুপারিশ করা হয় না , সেগুলো হলো:
- বৈশ্বিক স্থানাঙ্কে অবস্থানের ডেটা
- অ্যাটলাস শীটে UI উপাদানের স্থানাঙ্কের মতো উচ্চ-নির্ভুল ব্যবহারের ক্ষেত্রে টেক্সচার UV।
শেডার কোডে নির্ভুলতা
OpenGL Shading Language (GLSL) এবং High-level Shader Language (HLSL) শেডার প্রোগ্রামিং ভাষাগুলো নিউমেরিক টাইপের জন্য রিল্যাক্সড প্রিসিশন বা এক্সপ্লিসিট প্রিসিশন নির্দিষ্ট করা সমর্থন করে। রিল্যাক্সড প্রিসিশনকে শেডার কম্পাইলারের জন্য একটি সুপারিশ হিসেবে গণ্য করা হয়। এক্সপ্লিসিট প্রিসিশন হলো নির্দিষ্ট প্রিসিশনের একটি আবশ্যিক শর্ত। অ্যান্ড্রয়েডের ভলকান ডিভাইসগুলো সাধারণত রিল্যাক্সড প্রিসিশন দ্বারা প্রস্তাবিত হলে ১৬-বিট ফরম্যাট ব্যবহার করে। অন্যান্য ভলকান ডিভাইস, বিশেষ করে ডেস্কটপ কম্পিউটারে ব্যবহৃত গ্রাফিক্স হার্ডওয়্যার, যেগুলোতে ১৬-বিট ফরম্যাটের সমর্থন নেই, সেগুলো রিল্যাক্সড প্রিসিশন উপেক্ষা করে ৩২-বিট ফরম্যাট ব্যবহার করতে পারে।
GLSL-এ স্টোরেজ এক্সটেনশন
স্টোরেজ এবং ইউনিফর্ম বাফার স্ট্রাকচারে ১৬-বিট বা ৮-বিট নিউমেরিক ফরম্যাটের সাপোর্ট চালু করার জন্য উপযুক্ত GLSL এক্সটেনশনগুলো সংজ্ঞায়িত করতে হবে। প্রাসঙ্গিক এক্সটেনশন ডিক্লারেশনগুলো হলো:
// Enable 16-bit formats in storage and uniform buffers.
#extension GL_EXT_shader_16bit_storage : require
// Enable 8-bit formats in storage and uniform buffers.
#extension GL_EXT_shader_8bit_storage : require
এই এক্সটেনশনগুলো GLSL-এর জন্য নির্দিষ্ট এবং HLSL-এ এগুলোর কোনো সমতুল্য নেই।
GLSL-এ শিথিল নির্ভুলতা
একটি ফ্লোটিং পয়েন্ট টাইপের আগে সিঙ্গেল-প্রিসিশন ফ্লোট বোঝাতে highp কোয়ালিফায়ার এবং হাফ-প্রিসিশন ফ্লোট বোঝাতে mediump কোয়ালিফায়ার ব্যবহার করুন। Vulkan-এর জন্য GLSL কম্পাইলারগুলো লিগ্যাসি lowp কোয়ালিফায়ারকে mediump হিসেবে ব্যাখ্যা করে। রিল্যাক্সড প্রিসিশনের কিছু উদাহরণ:
mediump vec4 my_vector; // Suggest 16-bit half precision
highp mat4 my_matrix; // Suggest 32-bit single precision
GLSL-এ সুস্পষ্ট নির্ভুলতা
১৬-বিট ফ্লোটিং পয়েন্ট টাইপের ব্যবহার সক্ষম করতে আপনার GLSL কোডে GL_EXT_shader_explicit_arithmetic_types_float16 এক্সটেনশনটি অন্তর্ভুক্ত করুন:
#extension GL_EXT_shader_explicit_arithmetic_types_float16 : require
GLSL-এ নিম্নলিখিত কীওয়ার্ডগুলি ব্যবহার করে ১৬-বিট ফ্লোটিং পয়েন্ট স্কেলার, ভেক্টর এবং ম্যাট্রিক্স টাইপ ঘোষণা করুন:
float16_t f16vec2 f16vec3 f16vec4
f16mat2 f16mat3 f16mat4
f16mat2x2 f16mat2x3 f16mat2x4
f16mat3x2 f16mat3x3 f16mat3x4
f16mat4x2 f16mat4x3 f16mat4x4
নিম্নলিখিত কীওয়ার্ডগুলি ব্যবহার করে GLSL-এ ১৬-বিট পূর্ণসংখ্যা, স্কেলার এবং ভেক্টর টাইপ ঘোষণা করুন:
int16_t i16vec2 i16vec3 i16vec4
uint16_t u16vec2 u16vec3 u16vec4
HLSL-এ শিথিল নির্ভুলতা
HLSL রিল্যাক্সড প্রিসিশনের পরিবর্তে মিনিমাল প্রিসিশন শব্দটি ব্যবহার করে। একটি মিনিমাল প্রিসিশন টাইপ কীওয়ার্ড সর্বনিম্ন প্রিসিশন নির্দিষ্ট করে, কিন্তু টার্গেট হার্ডওয়্যারের জন্য উচ্চতর প্রিসিশন আরও ভালো বিকল্প হলে কম্পাইলার তার পরিবর্তে উচ্চতর প্রিসিশন ব্যবহার করতে পারে। একটি মিনিমাল প্রিসিশন ১৬-বিট ফ্লোট min16float কীওয়ার্ড দ্বারা নির্দিষ্ট করা হয়। মিনিমাল প্রিসিশন সাইনড এবং আনসাইনড ১৬-বিট ইন্টিজার যথাক্রমে min16int এবং min16uint কীওয়ার্ড দ্বারা নির্দিষ্ট করা হয়। মিনিমাল প্রিসিশন ডিক্লারেশনের অতিরিক্ত উদাহরণ নিচে দেওয়া হলো:
// Four element vector and four-by-four matrix types
min16float4 my_vector4;
min16float4x4 my_matrix4x4;
HLSL-এ সুস্পষ্ট নির্ভুলতা
হাফ-প্রিসিশন ফ্লোটিং-পয়েন্ট half বা float16_t কীওয়ার্ড দ্বারা নির্দিষ্ট করা হয়। সাইনড এবং আনসাইনড ১৬-বিট পূর্ণসংখ্যা যথাক্রমে int16_t এবং uint16_t কীওয়ার্ড দ্বারা নির্দিষ্ট করা হয়। সুস্পষ্ট প্রিসিশন ঘোষণার অতিরিক্ত উদাহরণ নিচে দেওয়া হলো:
// Four element vector and four-by-four matrix types
half4 my_vector4;
half4x4 my_matrix4x4;