ביצוע אופטימיזציה ברמת דיוק נמוכה יותר

לפורמט המספרי של נתונים גרפיים וחישובים של תוכנת ההצללה (shader) יכולה משפיעה באופן משמעותי על ביצועי המשחק.

פורמטים אופטימליים משמשים לביצוע הפעולות הבאות:

  • הגברת היעילות של השימוש במטמון GPU
  • הפחתת הצריכה של רוחב הפס בזיכרון, חיסכון בחשמל והגברת הביצועים
  • מיקסום התפוקה החישובית בתוכניות הצללה (shader)
  • צמצום השימוש בזיכרון ה-RAM במעבד (CPU) במשחק

פורמטים של נקודה צפה (floating-point)

רוב החישובים והנתונים בגרפיקה תלת-ממדית מודרנית משתמשים בנקודה צפה . ב-Vulkan ב-Android משתמשים במספרי נקודה צפה (floating-point) שהם 32 או בגודל 16 ביט. מספר נקודה צפה (floating-point) של 32 סיביות נקרא דיוק יחיד או דיוק מלא; מספר נקודה צפה (floating-point) של 16 ביט המדויק.

ב-Vulkan מגדירים סוג נקודה צפה (floating-point) של 64 ביט, אך הסוג הזה לא נפוץ נתמך על ידי מכשירי Vulkan ב-Android, והשימוש בו לא מומלץ. A 64-bit מספר הנקודה הצפה נקרא בדרך כלל דיוק כפול.

פורמטים של מספר שלם

מספרים שלמים חתומים ולא חתומים משמשים גם לנתונים ולחישובים. הגודל הסטנדרטי של מספר שלם הוא 32 סיביות. התמיכה בגדלים אחרים של ביטים היא במכשיר תלויה. מכשירי Vulkan עם Android בדרך כלל תומכים ב-16 ביט וב-8 ביט שלמים. ב-Vulkan מגדירים סוג מספר שלם של 64 ביט, אך הסוג הזה אינו נפוץ נתמך על ידי מכשירי Vulkan ב-Android, והשימוש בו לא מומלץ.

התנהגות לא אופטימלית של חצי דיוק

ארכיטקטורות GPU מודרניות משלבות שני ערכים של 16 ביט יחד בצמד של 32 ביט. להטמיע את ההוראות שפועלות על הצמד. כדי ליהנות מביצועים אופטימליים, מומלץ להימנע שימוש במשתנים צפים סקלריים של 16 ביט; להפוך נתונים לשניים או ארבעה רכיבים של שאילתות. המהדר של תוכנת ההצללה (shader) עשוי להשתמש בערכים סקלריים בווקטור ב-AI. עם זאת, אם מסתמכים על המהדר כדי לבצע אופטימיזציה של סקלרים, את פלט המהדר כדי לאמת וקטורים.

להמרה לנקודה צפה של 32 סיביות ו-16 סיביות ולנקודה צפה (floating-point) עלות חישובית. צמצום התקורה על ידי צמצום ההמרות המדויקות

ההבדלים בביצועים בין גרסת 16 סיביות לגרסת 32 סיביות של אלגוריתמים. חצי דיוק לא תמיד מוביל לשיפור בביצועים, במיוחד לצורך חישובים מורכבים. אלגוריתמים שעושים שימוש נרחב ב-Fused הוראות להכפלה (FMA) לגבי נתונים וקטוריים הן מועמדים טובים ביצועים משופרים בחצי דיוק.

תמיכה בפורמט מספרי

כל מכשירי Vulkan ב-Android תומכים בנקודה צפה ברמת דיוק יחידה של 32 ביט ומספרים שלמים של 32 ביט בחישובים של נתונים ותוכנת ההצללה. תמיכה עבור לא מובטח שפורמטים אחרים יהיו זמינים, ואם הם זמינים, לא מובטח לכל התרחישים לדוגמה.

ב-Vulkan יש שתי קטגוריות של תמיכה בפורמטים מספריים אופציונליים: אריתמטי ואחסון. לפני שמשתמשים בפורמט ספציפי, צריך לוודא שהמכשיר תומך בו קטגוריות.

תמיכה בחשבון

מכשיר Vulkan צריך להצהיר על תמיכה אריתמטית בפורמט מספרי כדי שהוא להיות שימושי לתוכנות הצללה (shader). בדרך כלל, מכשירי Vulkan ב-Android תומכים ב את הפורמטים הבאים לחשבון:

  • מספר שלם בגרסת 32 ביט (חובה)
  • נקודה צפה (floating-point) של 32 ביט
  • מספר שלם בגרסת 8 ביט (אופציונלי)
  • מספר שלם בגרסת 16 ביט (אופציונלי)
  • נקודה צפה (floating-point) של 16 ביט (אופציונלי)

כדי לקבוע אם מכשיר Vulkan תומך במספרים שלמים של 16 ביט לחישוב: לאחזר את תכונות המכשיר על ידי קריאה הפונקציה vkGetTechnicalDeviceFeatures2() ובדיקה אם השדה shaderInt16 ברשימה Vk דיגיטלייםDeviceFeatures2 הוא TRUE.

כדי לקבוע אם מכשיר Vulkan תומך במספרים צפים של 16 ביט או במספרים שלמים של 8 ביט, מבצעים את השלבים הבאים:

  1. בודקים אם המכשיר תומך תוסף VK_KHR_shader_float16_int8 של Vulkan. התוסף הוא הנדרש לתמיכה במספרים צפים ב-16 סיביות ובמספרים שלמים ב-8 ביט.
  2. אם המדיניות VK_KHR_shader_float16_int8 נתמכת, צריך לצרף מצביע על המבנה Vk דיגיטלייםDeviceShaderFloat16Int8Features לרשת VkPhysicalDeviceFeatures2.pNext.
  3. צריך לבדוק את השדות shaderFloat16 ו-shaderInt8 מבנה התוצאה של VkPhysicalDeviceShaderFloat16Int8Features אחרי השיחה vkGetPhysicalDeviceFeatures2() אם הערך בשדה הוא true, הפורמט הוא נתמך בתוכנית של תוכנת ההצללה.

אומנם זו לא דרישה ב-Vulkan 1.1 או שנת 2022 פרופיל Android Baseline, תמיכה ב-VK_KHR_shader_float16_int8 מאוד נפוץ במכשירי Android.

תמיכה בנפח האחסון

מכשיר Vulkan צריך להצהיר על תמיכה בפורמט מספרי אופציונלי בסוגי אחסון מסוימים. התוסף VK_KHR_16bit_storage החברה מצהירה על תמיכה במספר שלם של 16 ביט ובפורמטים של נקודה צפה (floating-point) של 16 ביט. ארבעה מוצרים סוגי האחסון מוגדרים על ידי התוסף. מכשיר יכול לתמוך במספרי 16 ביט ללא, עבור חלק או עבור כל סוגי האחסון.

סוגי האחסון הם:

  • אובייקטים של מאגר נתונים זמני של אחסון
  • אובייקטים אחידים של מאגר נתונים זמני
  • דחיפת בלוקים קבועים
  • ממשקי קלט ופלט של Shader

רוב מכשירי Vulkan 1.1 ב-Android תומכים גם בפורמטים של 16 ביט, אבל לא בכולם. למאגר הנתונים הזמני. אל תניח שיש תמיכה על סמך מודל ה-GPU. מכשירים עם מנהלי התקנים ישנים יותר ל-GPU מסוים, יכול להיות שלא תומכים באובייקטים של מאגר נתונים זמני של אחסון, ואילו מכשירים עם מנהלי התקנים חדשים יותר כן.

תמיכה בפורמטים של 16 ביט במאגרי נתונים זמניים אחידים, בלוקים קבועים ותוכנת הצללה (shader) בדרך כלל ממשקי הקלט/פלט תלויים ביצרן ה-GPU. במצב מופעל ב-Android, יחידת GPU בדרך כלל תומכת בכל שלושת הסוגים האלה או באף אחד מהם אותם.

פונקציה לדוגמה שבודקת תמיכה בחשבון ובפורמט האחסון של 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;
  }
}

רמת הדיוק של הנתונים

מספר נקודה צפה בחצי דיוק יכול לייצג טווח ערכים קטן יותר ברמת דיוק נמוכה יותר ממספר נקודה צפה עם דיוק יחיד. 'חצי דיוק' הוא לרוב בחירה פשוטה, שנראית ללא אובדן בדיוק יחיד. עם זאת, ייתכן שחצי דיוק לא יהיה מעשי בכל תרחישי השימוש. בסוגים מסוימים של נתונים, הטווח המופחת והדיוק עלולים להוביל ליצירת גרפיקה ארטיפקטים או עיבוד שגוי.

סוגי נתונים שיכולים להתאים לייצוג בחצי דיוק נקודה צפה (floating-point):

  • נתוני מיקום בקואורדינטות של המרחב המקומי
  • קרינת UV של טקסטורה למרקמים קטנים יותר עם גלישת UV מוגבלת מוגבל לטווח קואורדינטות -1.0 עד 1.0
  • נתונים רגילים, משיקים ביטנגנסים
  • נתוני צבע של קודקוד
  • נתונים עם דרישות ברמת דיוק נמוכה, מתמקדים ב-0.0

סוגי נתונים שלא מומלץ לייצוג בהם בציפה בעל דיוק חצי דיוק כוללים:

  • נתוני מיקום בקואורדינטות עולמיות
  • קרינת UV עם מרקם עבור תרחישים לדוגמה ברמת דיוק גבוהה, כמו קואורדינטות של רכיבי ממשק משתמש גיליון אטלס

דיוק בקוד של תוכנת ההצללה

את OpenGL Shading Language (GLSL) וכן כלי להצללה (shader) ברמה גבוהה (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 לפני סוג של נקודה צפה (floating-point) כדי להציע מספר ממשי (float) בעל דיוק יחיד, ותוחם 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 כדי לאפשר שימוש בסוגי נקודה צפה (floating-point) של 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 משתמש במונח דיוק מינימלי במקום ברמת דיוק מקלה. מינימלית מילת מפתח מסוג precision מציינת את הדיוק המינימלי, אך המהדר יכול להחליף דיוק גבוה יותר אם רמת דיוק גבוהה יותר היא אפשרות טובה יותר חומרת היעד. ערך צף מדויק מינימלי של 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;