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

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

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

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

لتحديد ما إذا كان جهاز 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، هذا يعني أنّ التنسيق متوافق مع العمليات الحسابية لبرنامج أداة التظليل.

على الرغم من أنّ استخدام إضافة 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
  • البيانات العادية والمماسية وظل التمام
  • بيانات لون Vertex
  • البيانات التي تتطلّب دقة منخفضة وتتمحور حول 0.0

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

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

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

تتيح لغات البرمجة OpenGL Shading Language (GLSL) ولغة التظليل عالية المستوى (HLSL) ، مواصفات الدقة المريحة أو الدقة الصريحة للأنواع الرقمية. يتم التعامل مع الدقة المنخفضة كاقتراح لمجمِّع 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;