التحسين بدقة مخفّضة

يمكن أن يكون للتنسيق الرقمي لبيانات الرسومات واحتسابات التظليل تأثير كبير في أداء لعبتك.

تنفِّذ التنسيقات المثالية ما يلي:

  • زيادة كفاءة استخدام ذاكرة التخزين المؤقت لوحدة معالجة الرسومات
  • تقليل استهلاك معدل نقل البيانات في الذاكرة، ما يؤدي إلى توفير الطاقة وتحسين الأداء
  • زيادة معدّل نقل البيانات الحسابية في برامج التظليل
  • تقليل استخدام ذاكرة الوصول العشوائي لوحدة المعالجة المركزية في لعبتك

تنسيقات الفاصلة العائمة

تستخدم معظم العمليات الحسابية والبيانات في رسومات ثلاثية الأبعاد الحديثة أرقامًا ذات فاصلة عائمة. يستخدم 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 بت، اتّبِع الخطوات التالية:

  1. تحقَّق مما إذا كان الجهاز يتيح استخدام إضافة Vulkan VK_KHR_shader_float16_int8. الإضافة مطلوبة لتوفير دعم للفاصلة العائمة 16 بت والأعداد الصحيحة 8 بت.
  2. إذا كان VK_KHR_shader_float16_int8 متاحًا، أضِف مؤشر بنية VkPhysicalDeviceShaderFloat16Int8Features إلى سلسلة VkPhysicalDeviceFeatures2.pNext.
  3. تحقَّق من الحقلَين shaderFloat16 وshaderInt8 في بنية النتيجة VkPhysicalDeviceShaderFloat16Int8Features بعد طلب vkGetPhysicalDeviceFeatures2(). إذا كانت قيمة الحقل true، يكون التنسيق متوافقًا مع العمليات الحسابية لبرنامج التظليل.

مع أنّ هذه الإضافة ليست شرطًا في Vulkan 1.1 أو ملف Android الأساسي لعام 2022، إلا أنّها شائعة جدًا على أجهزة Android.VK_KHR_shader_float16_int8

دعم التخزين

يجب أن يتيح جهاز 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 الأخرى، خاصةً أجهزة الكمبيوتر المكتبي التي تستخدم معدّات رسومات لا تتوافق مع تنسيقات 16 بت، الدقة المخفَّضة وتستمر في استخدام تنسيقات 32 بت.

إضافات مساحة التخزين في 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;