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

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

في ما يلي التنسيقات المثلى:

  • زيادة كفاءة استخدام ذاكرة التخزين المؤقت لوحدة GPU
  • تقليل استهلاك معدل نقل البيانات في الذاكرة، وتوفير الطاقة وزيادة الأداء
  • زيادة معدل نقل البيانات الحسابية إلى أقصى حد في برامج 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 صحيحًا.

shaderInt16

لتحديد ما إذا كان جهاز Vulkan متوافقًا مع الأعداد العشرية 16 بت أو الأعداد الصحيحة 8 بت، اتّبِع الخطوات التالية:

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

على الرغم من أنّ هذه الإضافة ليست شرطًا في Vulkan 1.1 أو ملف Android Baseline الشخصي لعام 2022، فإنّ دعم الإضافة VK_KHR_shader_float16_int8 شائع جدًا على أجهزة 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;
  }
}

مستوى دقة البيانات

يمكن أن يمثّل عدد النقطة العائمة بنصف الدقة نطاقًا أصغر من القيم بدقة أقل من عدد النقطة العائمة بدقة واحدة. غالبًا ما يكون النصف الدقيق خيارًا بسيطًا وبدون فقدان الإدراك مقارنةً بالدقة الكاملة. ومع ذلك، قد لا تكون الدقة النصف مناسبة في جميع حالات الاستخدام. بالنسبة إلى بعض أنواع البيانات، يمكن أن يؤدي انخفاض النطاق والدقة إلى حدوث عيوب في الرسم البياني أو عرض غير صحيح.

تشمل أنواع البيانات التي تكون مرشحة بشكل جيد للتمثيل بالتنسيق الثنائي الدقة النقطة العائمة ما يلي:

  • بيانات الموقع الجغرافي في إحداثيات الفضاء المحلي
  • الأشعة فوق البنفسجية القوامية للزخارف الأصغر مع التفاف محدود للأشعة فوق البنفسجية يمكن تقييده بنطاق إحداثيات يتراوح من -1.0 إلى 1.0
  • بيانات السطح العادي والمماسي والمماسي ثنائي الاتجاه
  • بيانات لون الرأس
  • بيانات ذات متطلبات دقة منخفضة تركز على 0.0

في ما يلي أنواع البيانات التي لا ننصح بتمثيلها باستخدام الأرقام العشرية ذات الدقة النصف:

  • بيانات الموقع الجغرافي في إحداثيات العالم العالمية
  • إحداثيات UV للزخرفة لحالات الاستخدام العالية الدقة، مثل إحداثيات عناصر واجهة المستخدم في جدول الخريطة

الدقة في رمز برنامج التظليل

تتيح لغتا برمجة OpenGL Shading Language (GLSL) وHigh-level Shader Language (HLSL) تحديد دقة relaxed أو دقة صريحة للأنواع الرقمية. يتم التعامل مع الدقة المريحة كتوصية لالمحول البرمجي للتظليل. الدقة الصريحة هي أحد متطلبات الدقة المحدّدة. تستخدم أجهزة 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;