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