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

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

تؤدي التنسيقات المثلى ما يلي:

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

تنسيقات النقاط العائمة

تستخدم معظم العمليات الحسابية والبيانات في الرسومات ثلاثية الأبعاد الحديثة أرقام النقاط العائمة. يستخدم 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. تحقَّق مما إذا كان الجهاز متوافقًا مع إضافة VK_KHR_shader_float16_int8 في Vulkan. هذا الملحق مطلوب للتوافق مع عدد عشري 16 بت وعدد صحيح 8 بت.
  2. في حال توفُّر VK_KHR_shader_float16_int8، أضِف مؤشر بنية VkPhysicalDeviceShaderFloat16Int8Features إلى سلسلة 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 بت في المخازن المؤقتة المنتظمة، ودفع الكتل الثابتة، وواجهات الإدخال/الإخراج في أداة التظليل بشكل عام على الشركة المصنّعة لوحدة معالجة الرسومات. على نظام التشغيل 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
  • البيانات العادية، والمماسية، وذات المماس البتة
  • بيانات لون Vertex
  • يشير ذلك المصطلح إلى البيانات ذات المتطلبات المنخفضة الدقة التي تتمحور حول 0.0.

تشمل أنواع البيانات التي لا يوصى بها للتمثيل في نسبة عائمة نصف الدقة ما يلي:

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

الدقة في رمز أداة التظليل

تدعم لغات البرمجة OpenGL (GLSL) ولغة التظليل ذات المستوى العالي (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 المصطلح الحد الأدنى للدقة بدلاً من الدقة الهادئة. تحدد الكلمة الرئيسية ذات نوع أدنى من الدقة الحد الأدنى للدقة، ولكن قد يستبدل المحول البرمجي دقة أعلى إذا كانت الدقة الأعلى خيارًا أفضل للجهاز المستهدف. وتحدّد الكلمة الرئيسية min16float قيمة عائمة 16 بت بحدٍ أدنى للدقة. ويتم تحديد الحد الأدنى للدقة في الأعداد الصحيحة المكوّنة من 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;