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

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

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

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

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

تستخدم معظم العمليات الحسابية والبيانات في رسومات ثلاثية الأبعاد الحديثة أرقامًا ذات فاصلة عائمة. يستخدم Vulkan على Android أرقام النقطة العائمة التي يبلغ حجمها 32 أو 16 بت. يُشار عادةً إلى العدد العشري ذي 32 بت باسم الدقة الفردية أو الدقة الكاملة، وإلى العدد العشري ذي 16 بت باسم نصف الدقة.

تحدّد Vulkan نوعًا من الأرقام العشرية ذات الدقة المزدوجة (64 بت)، ولكن لا تتوافق أجهزة Android التي تعمل بنظام Vulkan مع هذا النوع بشكل عام، ولا يُنصح باستخدامه. يُشار عادةً إلى العدد العشري المكوّن من 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;