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

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

تحقّق التنسيقات المثلى ما يلي:

  • زيادة كفاءة استخدام ذاكرة التخزين المؤقت لوحدة معالجة الرسومات
  • تقليل استهلاك معدل نقل البيانات في الذاكرة، وتوفير الطاقة وزيادة الأداء
  • زيادة معدل نقل البيانات الحسابية إلى أقصى حد في برامج Shader
  • تقليل استخدام ذاكرة الوصول العشوائي (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 عن توفُّر عمليات حسابية لتنسيق رقمي لكي يكون قابلاً للاستخدام في برامج shaders. تتيح أجهزة 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 متوافقًا، يمكنك إلحاق VkPhysicalDeviceFeatures2.pNext بسلسلة VkPhysicalDeviceFeatures2.pNext مع تضمين المؤشر إلى بنية VkPhysicalDeviceShaderFloat16Int8Features.
  3. تحقَّق من الحقلين shaderFloat16 وshaderInt8 في بنية نتيجة VkPhysicalDeviceShaderFloat16Int8Features بعد استدعاء vkGetPhysicalDeviceFeatures2(). إذا كانت قيمة الحقل هي true، يكون التنسيق متوافقًا مع العمليات الحسابية لبرنامج التظليل.

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

دعم مساحة التخزين

يجب أن يعلن جهاز Vulkan عن توافقه مع تنسيق رقمي اختياري لأنواع تخزين معيّنة. تُعلن إضافة VK_KHR_16bit_storage عن توفّر تنسيقات الأعداد الصحيحة 16 بت وتنسيقات النقطة العائمة 16 بت. تحدِّد الإضافة أربعة أنواع تخزين. يمكن أن يتوافق الجهاز مع الأرقام التي تبلغ 16 بت لكل أنواع التخزين أو بعضها أو بعضها فقط.

أنواع مساحة التخزين هي:

  • عناصر التخزين المؤقت
  • عناصر ذاكرة التخزين المؤقت الموحّدة
  • دفع وحدات ثابتة
  • واجهات إدخال وإخراج Shader

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

يعتمد بشكل عام توافق تنسيقات 16 بت في المخزن المؤقت الموحّد، ووحدات الدفع الثابتة، وshader واجهات الإدخال/الإخراج على الشركة المصنّعة لوحدة معالجة الرسومات. على نظام 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) تحديد دقة relaxed أو دقة صريحة للأنواع الرقمية. يتم التعامل مع الدقة المنخفضة كاقتراح لمجمِّع shaders. الدقة الصريحة هي أحد متطلبات الدقة المحدّدة. تستخدم أجهزة 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;