يمكن أن يؤثر التنسيق الرقمي لبيانات الرسومات وعمليات حساب التظليل بشكل كبير في أداء لعبتك.
تنفِّذ التنسيقات المثالية ما يلي:
- زيادة كفاءة استخدام ذاكرة التخزين المؤقت لوحدة معالجة الرسومات
- تقليل استهلاك معدل نقل البيانات في الذاكرة، ما يؤدي إلى توفير الطاقة وتحسين الأداء
- تحقيق الحدّ الأقصى من معدّل نقل البيانات الحسابية في برامج التظليل
- تقليل استخدام ذاكرة الوصول العشوائي (RAM) لوحدة المعالجة المركزية في لعبتك
تنسيقات النقطة العائمة
تستخدم معظم العمليات الحسابية والبيانات في رسومات ثلاثية الأبعاد الحديثة أرقامًا ذات فاصلة عائمة. يستخدم Vulkan على Android أرقامًا ذات فاصلة عشرية بحجم 32 أو 16 بت. يُشار عادةً إلى رقم النقطة العائمة المكوّن من 32 بت باسم الدقة الفردية أو الدقة الكاملة، وإلى رقم النقطة العائمة المكوّن من 16 بت باسم نصف الدقة.
تحدّد Vulkan نوعًا من الفاصلة العائمة يبلغ 64 بت، ولكن لا تتوافق أجهزة Vulkan على Android مع هذا النوع بشكل شائع، ولا يُنصح باستخدامه. يُشار عادةً إلى عدد الفاصلة العائمة المكوّن من 64 بت باسم الدقة المزدوجة.
تنسيقات الأعداد الصحيحة
تُستخدَم أيضًا الأعداد الصحيحة الموقّعة وغير الموقّعة للبيانات والحسابات. يبلغ حجم العدد الصحيح العادي 32 بت. ويعتمد توفّر أحجام البتات الأخرى على الجهاز. تتوافق أجهزة Vulkan التي تعمل بنظام التشغيل Android عادةً مع الأعداد الصحيحة ذات 16 بت و8 بت. تحدّد Vulkan نوع عدد صحيح 64 بت، ولكن لا تتوافق أجهزة Vulkan على Android مع هذا النوع بشكل عام، ولا يُنصح باستخدامه.
سلوك غير مثالي لنصف الدقة
تجمع بنى وحدات معالجة الرسومات الحديثة بين قيمتَين من 16 بت في زوج من 32 بت، وتنفّذ تعليمات تعمل على الزوج. للحصول على الأداء الأمثل، تجنَّب استخدام متغيرات عددية ذات فاصلة عائمة 16 بت، وحوِّل البيانات إلى متجهات ذات عنصرَين أو أربعة عناصر. قد يتمكّن برنامج تجميع التظليل من استخدام قيم عددية في عمليات المتجهات. ومع ذلك، إذا كنت تعتمد على المترجم البرمجي لتحسين القيم العددية، عليك فحص ناتج المترجم البرمجي للتحقّق من عملية تحويل التعليمات البرمجية إلى متجهات.
يؤدي التحويل من وإلى النقطة العائمة بدقة 32 بت و16 بت إلى تكلفة حسابية. يمكنك تقليل النفقات العامة عن طريق تقليل عمليات التحويل الدقيق في الرمز.
مقارنة الأداء بين إصدارَي 16 بت و32 بت من الخوارزميات لا تؤدي الدقة النصفية دائمًا إلى تحسين الأداء، خاصةً بالنسبة إلى العمليات الحسابية المعقّدة. تُعد الخوارزميات التي تستخدم بشكل كبير تعليمات الضرب والجمع المدمجة (FMA) على البيانات المتجهة خيارات جيدة لتحسين الأداء بنصف الدقة.
التوافق مع التنسيق الرقمي
تتيح جميع أجهزة Vulkan على Android استخدام أرقام الفاصلة العائمة ذات الدقة الفردية التي تبلغ 32 بت وأرقام الأعداد الصحيحة التي تبلغ 32 بت في عمليات حساب البيانات والتظليل. لا يمكننا ضمان توفّر تنسيقات أخرى، وفي حال توفّرها، لا يمكننا ضمان توفّرها لجميع حالات الاستخدام.
تتضمّن Vulkan فئتين من التوافق مع التنسيقات الرقمية الاختيارية: العمليات الحسابية والتخزين. قبل استخدام تنسيق معيّن، تأكَّد من أنّ الجهاز يتوافق معه في كلتا الفئتين.
إتاحة العمليات الحسابية
يجب أن يتيح جهاز Vulkan إمكانية إجراء العمليات الحسابية بتنسيق رقمي لكي يكون قابلاً للاستخدام في برامج التظليل. تتيح أجهزة Vulkan على Android عادةً التنسيقات التالية للعمليات الحسابية:
- عدد صحيح 32 بت (إلزامي)
- نقطة عائمة 32 بت (إلزامي)
- عدد صحيح 8 بت (اختياري)
- عدد صحيح 16 بت (اختياري)
- نقطة عائمة بنصف الدقة تبلغ 16 بت (اختيارية)
لتحديد ما إذا كان جهاز Vulkan يتيح استخدام أعداد صحيحة ذات 16 بت في العمليات الحسابية، استرجِع ميزات الجهاز من خلال استدعاء الدالة vkGetPhysicalDeviceFeatures2() والتحقّق مما إذا كان الحقل shaderInt16
في بنية النتائج VkPhysicalDeviceFeatures2 مضبوطًا على "صحيح".
لتحديد ما إذا كان جهاز Vulkan يتيح استخدام أعداد صحيحة ذات 16 بت أو أعداد صحيحة ذات 8 بت، اتّبِع الخطوات التالية:
- تحقَّق مما إذا كان الجهاز يتيح استخدام إضافة Vulkan VK_KHR_shader_float16_int8. الإضافة مطلوبة لتوفير التوافق مع الأعداد العشرية ذات 16 بت والأعداد الصحيحة ذات 8 بت.
- في حال توفّر
VK_KHR_shader_float16_int8
، أضِف مؤشر بنية VkPhysicalDeviceShaderFloat16Int8Features إلى سلسلةVkPhysicalDeviceFeatures2.pNext
. - تحقَّق من الحقلَين
shaderFloat16
وshaderInt8
في بنية النتيجةVkPhysicalDeviceShaderFloat16Int8Features
بعد طلبvkGetPhysicalDeviceFeatures2()
. إذا كانت قيمة الحقلtrue
، يكون التنسيق متوافقًا مع العمليات الحسابية لبرنامج التظليل.
مع أنّ Vulkan 1.1 أو ملف Android الأساسي لعام 2022 لا يشترطان توفُّر إضافة VK_KHR_shader_float16_int8
، إلا أنّها شائعة جدًا على أجهزة Android.
دعم التخزين
يجب أن يتيح جهاز Vulkan استخدام تنسيق رقمي اختياري لأنواع تخزين معيّنة. توضّح إضافة VK_KHR_16bit_storage إمكانية استخدام تنسيقات الأعداد الصحيحة ذات 16 بت وتنسيقات الأعداد العشرية ذات 16 بت. تحدّد الإضافة أربعة أنواع من وحدات التخزين. يمكن أن يتيح الجهاز أرقامًا من 16 بت لبعض أنواع التخزين أو كلها أو لا يتيحها لأي منها.
أنواع مساحة التخزين هي:
- عناصر المخزن المؤقت
- عناصر المخزن المؤقت الموحّد
- كتل الثوابت
- واجهات إدخال وإخراج Shader
تتيح معظم أجهزة Vulkan 1.1 على Android، وليس كلها، استخدام تنسيقات 16 بت في عناصر مخزن مؤقت للتخزين. لا تفترض التوافق استنادًا إلى طراز وحدة معالجة الرسومات. قد لا تتوافق الأجهزة التي تتضمّن برامج تشغيل قديمة لوحدة معالجة الرسومات المحدّدة مع عناصر المخزن المؤقت، بينما تتوافق الأجهزة التي تتضمّن برامج تشغيل أحدث معها.
يعتمد توفّر تنسيقات 16 بت في المخازن المؤقتة الموحّدة وكتل الثوابت المؤقتة وواجهات إدخال/إخراج المظلّلات بشكل عام على الشركة المصنّعة لوحدة معالجة الرسومات. على أجهزة Android، تتوافق وحدة معالجة الرسومات عادةً مع جميع هذه الأنواع الثلاثة أو لا تتوافق مع أي منها.
في ما يلي مثال على دالة تختبر إمكانية استخدام تنسيقات التخزين والحساب في Vulkan:
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;
}
}
مستوى الدقة للبيانات
يمكن أن يمثّل رقم النقطة العائمة بنصف الدقة نطاقًا أصغر من القيم بدقة أقل من رقم النقطة العائمة بدقة مفردة. غالبًا ما يكون نصف الدقة خيارًا بسيطًا وغير قابل للفقدان من الناحية الإدراكية مقارنةً بالدقة الفردية. ومع ذلك، قد لا يكون استخدام الدقة النصفية عمليًا في جميع حالات الاستخدام. بالنسبة إلى بعض أنواع البيانات، يمكن أن يؤدي النطاق والدقة المنخفضَين إلى ظهور تشوّهات في الرسومات أو عرض غير صحيح.
تشمل أنواع البيانات التي تتناسب مع التمثيل بنقطة عائمة ذات دقة نصفية ما يلي:
- بيانات الموضع في إحداثيات المساحة المحلية
- إحداثيات UV للنسيج الخاص بالصور الأصغر حجمًا التي يمكن أن يتم فيها لفّ إحداثيات UV بشكل محدود، ويمكن أن يتم حصرها في نطاق إحداثيات من -1.0 إلى 1.0
- بيانات المتجهات العمودية والمماسية والمزدوجة
- بيانات ألوان الرؤوس
- البيانات التي تتطلّب دقة منخفضة وتتمركز حول 0.0
أنواع البيانات التي لا يُنصح بتمثيلها في عدد عشري بنصف الدقة تشمل:
- بيانات الموقع الجغرافي في إحداثيات العالم الشامل
- إحداثيات UV للزخرفة لحالات الاستخدام العالية الدقة، مثل إحداثيات عناصر واجهة المستخدم في ورقة أطلس
الدقة في رمز التظليل
تتيح لغتا البرمجة OpenGL Shading Language (GLSL) وHigh-level Shader Language (HLSL) تحديد الدقة المخفَّفة أو الدقة الصريحة للأنواع الرقمية. يتم التعامل مع الدقة المخفَّضة كاقتراح لمترجم برنامج التظليل. الدقة الصريحة هي شرط للدقة المحدّدة. تستخدم أجهزة Vulkan على Android بشكل عام تنسيقات 16 بت عندما تقترحها الدقة المخفَّفة. قد تتجاهل أجهزة Vulkan الأخرى الدقة المخفَّضة وتستمر في استخدام تنسيقات 32 بت، خاصةً على أجهزة الكمبيوتر المكتبية التي تستخدم أجهزة رسومات لا تتوافق مع تنسيقات 16 بت.
إضافات مساحة التخزين في GLSL
يجب تحديد إضافات GLSL المناسبة لتفعيل التوافق مع التنسيقات الرقمية ذات 16 بت أو 8 بت في بنى التخزين والمخزن المؤقت الموحّد. في ما يلي إقرارات الإضافات ذات الصلة:
// 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
لنقطة عائمة نصف دقيقة.
تفسّر برامج تجميع GLSL لإصدار Vulkan المعدِّل lowp
القديم على أنّه mediump
.
في ما يلي بعض الأمثلة على الدقة المخفَّفة:
mediump vec4 my_vector; // Suggest 16-bit half precision
highp mat4 my_matrix; // Suggest 32-bit single precision
الدقة الصريحة في GLSL
أدرِج الإضافة GL_EXT_shader_explicit_arithmetic_types_float16
في رمز GLSL لتفعيل استخدام أنواع الفاصلة العائمة ذات 16 بت:
#extension GL_EXT_shader_explicit_arithmetic_types_float16 : require
يمكنك تعريف أنواع الأعداد القياسية والمتجهات والمصفوفات ذات الفاصلة العائمة التي تبلغ 16 بت في GLSL باستخدام الكلمات الرئيسية التالية:
float16_t f16vec2 f16vec3 f16vec4
f16mat2 f16mat3 f16mat4
f16mat2x2 f16mat2x3 f16mat2x4
f16mat3x2 f16mat3x3 f16mat3x4
f16mat4x2 f16mat4x3 f16mat4x4
يمكنك تعريف أنواع عددية ومتجهة للأعداد الصحيحة ذات 16 بت في GLSL باستخدام الكلمات الرئيسية التالية:
int16_t i16vec2 i16vec3 i16vec4
uint16_t u16vec2 u16vec3 u16vec4
الدقة المخفَّفة في HLSL
تستخدم لغة HLSL المصطلح الدقة الدنيا بدلاً من الدقة المخفَّضة. تحدّد الكلمة الرئيسية لنوع الدقة الأدنى الحد الأدنى للدقة، ولكن قد يستبدل المترجم دقة أعلى إذا كانت الدقة الأعلى خيارًا أفضل للأجهزة المستهدفة. يتم تحديد عدد عشري عائم بدقة بسيطة يبلغ 16 بت باستخدام الكلمة الرئيسية min16float
. يتم تحديد الأعداد الصحيحة الموقّعة وغير الموقّعة ذات الدقة الدنيا والتي تبلغ 16 بت باستخدام الكلمتَين الرئيسيتَين min16int
وmin16uint
على التوالي. في ما يلي أمثلة إضافية على بيانات الحد الأدنى من الدقة:
// Four element vector and four-by-four matrix types
min16float4 my_vector4;
min16float4x4 my_matrix4x4;
الدقة الصريحة في HLSL
يتم تحديد الفاصلة العائمة ذات الدقة النصفية باستخدام الكلمتَين الرئيسيتَين half
أو float16_t
. يتم تحديد الأعداد الصحيحة ذات 16 بت الموقّعة وغير الموقّعة باستخدام الكلمتَين الرئيسيتَين int16_t
وuint16_t
على التوالي. تشمل الأمثلة الإضافية على بيانات الدقة الواضحة ما يلي:
// Four element vector and four-by-four matrix types
half4 my_vector4;
half4x4 my_matrix4x4;