הפורמט המספרי של נתוני הגרפיקה וחישובי ה-shader יכול להשפיע באופן משמעותי על ביצועי המשחק.
פורמטים אופטימליים עומדים בדרישות הבאות:
- הגברת היעילות של השימוש במטמון GPU
- הפחתת צריכת רוחב הפס של הזיכרון, חיסכון בחשמל ושיפור הביצועים
- הגדלת תפוקת החישוב בתוכניות של שידורי ה-shader
- צמצום השימוש בזיכרון ה-RAM של המעבד במשחק
פורמטים של נקודה צפה (floating-point)
רוב החישובים והנתונים בגרפיקה תלת-ממדית מודרנית משתמשים במספרים עם נקודה צפה (floating-point). ב-Vulkan ב-Android נעשה שימוש במספרים של נקודה צפה בגודל 32 או 16 ביט. מספר נקודה צפה (floating-point) של 32 ביט נקרא בדרך כלל 'דיוק יחיד' או 'דיוק מלא', ומספר נקודה צפה של 16 ביט נקרא 'דיוק חצי'.
ב-Vulkan מגדירים סוג נקודה צפה (floating-point) של 64 ביט, אבל לרוב מכשירי Vulkan ב-Android לא תומכים בסוג הזה, ולא מומלץ להשתמש בו. מספר של 64 ביט בספרות עשרוניות צפות נקרא בדרך כלל דיוק כפול.
פורמטים של מספר שלם
מספרים שלמים בעלי סימן ומספרים שלמים ללא סימן משמשים גם לנתונים ולחישובים. גודל המספר השלם הרגיל הוא 32 ביט. תמיכה בגדלים אחרים של ביטים תלויה במכשיר. מכשירי Vulkan עם Android תומכים בדרך כלל במספרים שלמים של 16 ו-8 ביט. Vulkan מגדיר סוג של מספר שלם באורך 64 ביט, אבל הסוג הזה לא נתמך בדרך כלל במכשירי Vulkan ב-Android, ולא מומלץ להשתמש בו.
התנהגות לא אופטימלית של חצי דיוק
ארכיטקטורות GPU מודרניות משלבות שני ערכים של 16 ביט יחד בזוג של 32 ביט ומיישמות הוראות שפועלות על הצמד הזה. כדי לשפר את הביצועים, מומלץ להימנע משימוש במשתני סקלאר של 16 ביט מסוג float, ולהעביר את הנתונים לוקטור של שניים או ארבעה רכיבים. ייתכן שהמהדר של תוכנת ההצללה יוכל להשתמש בערכים סקלריים בפעולות וקטורים. עם זאת, אם מסתמכים על המהדר כדי לבצע אופטימיזציה של סקלרים, עליכם לבדוק את פלט המהדר כדי לאמת את הווקטורים.
יש עלות חישובית של ההמרה לנקודה צפה (floating-point) של 32 ביט ו-16 ביט &#ndash;precision. הפחתת התקורה על ידי צמצום ההמרות המדויקות בקוד.
ההבדלים בביצועים בין האלגוריתמים של האלגוריתמים בגרסת 16 ביט לבין 32 ביט. רמת דיוק של חצי לא תמיד מובילה לשיפור בביצועים, במיוחד כשמדובר בחישובים מורכבים. אלגוריתמים שנעשה בהם שימוש נרחב בהוראות משולבות כפל (FMA) בנתונים וקטוריים הם מועמדים טובים לביצועים משופרים בחצי דיוק.
תמיכה בפורמטים מספריים
בכל מכשירי Vulkan ב-Android יש תמיכה במספרים מדויקים של נקודה צפה (floating-point) של 32 ביט ומספרים שלמים של 32 ביט בחישובים של נתונים ותוכנת הצללה (shader). אין ערובה לתמיכה בפורמטים אחרים, ואם התמיכה קיימת, היא לא מובטחת לכל התרחישים לדוגמה.
ב-Vulkan יש שתי קטגוריות של תמיכה בפורמטים מספריים אופציונליים: אריתמטיקה ואחסון. לפני שמשתמשים בפורמט ספציפי, צריך לוודא שהמכשיר תומך בו בשתי הקטגוריות.
תמיכה בחשבון
מכשיר Vulkan חייב להצהיר על תמיכה אריתמטית בפורמט מספרי כדי שאפשר יהיה להשתמש בו בתוכניות של שידרים (shaders). בדרך כלל, מכשירי Vulkan ב-Android תומכים בפורמטים הבאים לחישוב אריתמטי:
- מספר שלם של 32 ביט (חובה)
- נקודה צפה (float) ב-32 ביט (חובה)
- מספר שלם של 8 ביט (אופציונלי)
- מספר שלם של 16 ביט (אופציונלי)
- נקודה צפה (floating-point) של 16 ביט ברמת דיוק חצי (אופציונלי)
כדי לקבוע אם מכשיר Vulkan תומך במספרים שלמים של 16 ביט לצורכי אריתמטיקה, צריך לאחזר את התכונות של המכשיר באמצעות קריאה לפונקציה vkGetPhysicalDeviceFeatures2() ולבדוק אם השדה shaderInt16
במבנה התוצאה VkPhysicalDeviceFeatures2 הוא true.
כדי לקבוע אם מכשיר Vulkan תומך ב-floats של 16 ביט או במספרים שלמים של 8 ביט, מבצעים את השלבים הבאים:
- בודקים אם המכשיר תומך בתוסף Vulkan VK_KHR_shader_float16_int8. התוסף נדרש לתמיכה ב-float של 16 ביט ובמספר שלם של 8 ביט.
- אם יש תמיכה ב-
VK_KHR_shader_float16_int8
, צריך לצרף למחרוזתVkPhysicalDeviceFeatures2.pNext
את הפונקציה VkPhysicalDeviceShaderFloat16Int8Features. - בודקים את השדות
shaderFloat16
ו-shaderInt8
במבנהVkPhysicalDeviceShaderFloat16Int8Features
של התוצאות אחרי הקריאה ל-vkGetPhysicalDeviceFeatures2()
. אם הערך בשדה הואtrue
, הפורמט נתמך בחשבון של תוכנית ה-shader.
התמיכה בתוסף VK_KHR_shader_float16_int8
נפוצה מאוד במכשירי Android, למרות שהיא לא נדרשת ב-Vulkan 1.1 או בפרופיל הבסיס של Android לשנת 2022.
תמיכה באחסון
מכשיר Vulkan צריך להצהיר על תמיכה בפורמט מספרי אופציונלי בסוגי אחסון ספציפיים. התוסף VK_KHR_16bit_storage מכריז על תמיכה בפורמטים של מספר שלם בן 16 ביט ובפורמטים של נקודה צפה בת 16 ביט. התוסף מגדיר ארבעה סוגי אחסון. מכשיר יכול לתמוך במספרים של 16 ביט באף סוג אחסון, בחלק מסוגי האחסון או בכל סוגי האחסון.
סוגי האחסון הם:
- אובייקטים של מאגר נתונים זמני של אחסון
- אובייקטים אחידים של מאגר נתונים זמני
- דחיפת בלוקים של ערכי קבוע
- ממשקי קלט ופלט של Shader
רוב המכשירים עם Vulkan 1.1 ב-Android תומכים בפורמטים של 16 ביט באובייקטים של מאגרי אחסון, אבל לא כולם. אל תניח שיש תמיכה על סמך מודל ה-GPU. יכול להיות שמכשירים עם מנהלי התקנים ישנים יותר של GPU מסוים לא יתמכו באובייקטים של מאגר אחסון, בעוד שמכשירים עם מנהלי התקנים חדשים יותר כן יתמכו בהם.
התמיכה בפורמטים של 16 ביט במאגרי נתונים זמניים אחידים, בדחיפת בלוקים קבועים וממשקי קלט/פלט של תוכנת ההצללה תלויה בדרך כלל ביצרן ה-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;
}
}
רמת הדיוק של הנתונים
מספר נקודה צפה בחצי דיוק יכול לייצג טווח ערכים קטן יותר ברמת דיוק נמוכה יותר ממספר נקודה צפה עם דיוק יחיד. לרוב, בחירה בפלטפורמה עם דיוק חצי היא בחירה פשוטה ללא אובדן נתונים מבחינה תפיסתית, בהשוואה לפלטפורמה עם דיוק יחיד. עם זאת, יכול להיות שדיוק חצי לא יהיה מעשי בכל תרחישי השימוש. בסוגים מסוימים של נתונים, הטווח המופחת והדיוק שלהם עלולים לגרום לפריטי מידע גרפיים ולעיבוד שגוי.
סוגי נתונים שמתאימים לייצוג בנקודה צפה ברמת דיוק חצי כוללים:
- נתוני מיקום בקואורדינטות של מרחב מקומי
- קואורדינטות UV של טקסטורות קטנות יותר עם גלישת UV מוגבלת שאפשר להגביל לטווח קואורדינטות של -1.0 עד 1.0
- נתונים רגילים, נתוני נגזרת ראשונית ונתוני נגזרת שניונית
- נתוני צבע של קודקודים
- נתונים עם דרישות דיוק נמוכות שמתמקדים ב-0.0
סוגי הנתונים שלא מומלץ לייצוג בהם בתצוגה צפה (float) בחצי דיוק כוללים:
- נתוני מיקום בקואורדינטות גלובליות
- קואורדינטות UV של טקסטורה לתרחישים לדוגמה עם דיוק גבוה, כמו קואורדינטות של רכיבי ממשק משתמש בגיליון אטלס
דיוק בקוד של שפת השיז'ר
שפות התכנות של תוכנת ההצללה (shader) OpenGL (GLSL) ו-High-level Shader Language (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
כדי לאפשר שימוש בסוגי נקודות צפות של 16 ביט, צריך לכלול את התוסף GL_EXT_shader_explicit_arithmetic_types_float16
בקוד ה-GLSL:
#extension GL_EXT_shader_explicit_arithmetic_types_float16 : require
מגדירים ב-GLSL סוגים של סקלר, וקטור ומטריצה של נקודה צפה באורך 16 ביט באמצעות מילות המפתח הבאות:
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
מציינות נקודה צפה (floating-point) ברמת דיוק חצי. מספרים שלמים בעלי 16 ביט עם חתימה או ללא חתימה מצוינים באמצעות מילות המפתח int16_t
ו-uint16_t
, בהתאמה. דוגמאות נוספות להצהרות מפורשות על רמת דיוק:
// Four element vector and four-by-four matrix types
half4 my_vector4;
half4x4 my_matrix4x4;