فرمت عددی داده های گرافیکی و محاسبات شیدر می تواند تاثیر بسزایی در عملکرد بازی شما داشته باشد.
فرمت های بهینه موارد زیر را انجام می دهند:
- افزایش کارایی استفاده از حافظه پنهان GPU
- کاهش مصرف پهنای باند حافظه، صرفه جویی در مصرف انرژی و افزایش عملکرد
- به حداکثر رساندن توان محاسباتی در برنامه های سایه زن
- استفاده از رم پردازنده را در بازی خود به حداقل برسانید
فرمت های ممیز شناور
اکثر محاسبات و داده ها در گرافیک سه بعدی مدرن از اعداد ممیز شناور استفاده می کنند. Vulkan در اندروید از اعداد ممیز شناور استفاده می کند که اندازه آنها 32 یا 16 بیت است. یک عدد ممیز شناور 32 بیتی معمولاً با دقت تک یا دقت کامل شناخته می شود. یک عدد ممیز شناور 16 بیتی، نیم دقت.
Vulkan یک نوع ممیز شناور 64 بیتی تعریف می کند، اما این نوع معمولاً توسط دستگاه های Vulkan در اندروید پشتیبانی نمی شود و استفاده از آن توصیه نمی شود. یک عدد ممیز شناور 64 بیتی معمولا به عنوان دقت مضاعف شناخته می شود.
فرمت های عدد صحیح
از اعداد صحیح علامت دار و بدون علامت نیز برای داده ها و محاسبات استفاده می شود. اندازه عدد صحیح استاندارد 32 بیت است. پشتیبانی از سایر اندازه های بیت به دستگاه بستگی دارد. دستگاه های Vulkan دارای اندروید معمولاً از اعداد صحیح 16 بیتی و 8 بیتی پشتیبانی می کنند. Vulkan یک نوع عدد صحیح 64 بیتی را تعریف می کند، اما این نوع معمولاً توسط دستگاه های Vulkan در اندروید پشتیبانی نمی شود و استفاده از آن توصیه نمی شود.
رفتار نیمه دقیق نابهینه
معماریهای مدرن GPU دو مقدار 16 بیتی را با هم در یک جفت 32 بیتی ترکیب میکنند و دستورالعملهایی را پیادهسازی میکنند که روی این جفت کار میکنند. برای عملکرد بهینه، از استفاده از متغیرهای شناور اسکالر 16 بیتی خودداری کنید. بردار کردن داده ها به بردارهای دو یا چهار عنصری. کامپایلر سایه زن ممکن است بتواند از مقادیر اسکالر در عملیات برداری استفاده کند. با این حال، اگر برای بهینهسازی اسکالرها به کامپایلر تکیه میکنید، خروجی کامپایلر را برای تأیید بردارسازی بررسی کنید.
تبدیل به و از ممیز شناور دقیق 32 بیتی و 16 بیتی هزینه محاسباتی دارد. با به حداقل رساندن تبدیل های دقیق در کد خود، هزینه های اضافی را کاهش دهید.
تفاوت عملکرد بین نسخه های 16 بیتی و 32 بیتی الگوریتم های خود را معیار قرار دهید. نیم دقت همیشه منجر به بهبود عملکرد نمی شود، به خصوص برای محاسبات پیچیده. الگوریتمهایی که از دستورالعملهای ضربافزودن ذوب شده (FMA) روی دادههای بردار استفاده میکنند، کاندیدای خوبی برای بهبود عملکرد با دقت نیمی هستند.
پشتیبانی از فرمت عددی
همه دستگاههای Vulkan در اندروید از اعداد ممیز شناور تک دقیق، ۳۲ بیتی و اعداد صحیح ۳۲ بیتی در محاسبات دادهها و سایهبان پشتیبانی میکنند. پشتیبانی از فرمت های دیگر تضمین نمی شود که در دسترس باشد و در صورت موجود بودن، برای همه موارد استفاده تضمین نمی شود.
Vulkan دارای دو دسته پشتیبانی از فرمت های عددی اختیاری است: حسابی و ذخیره سازی. قبل از استفاده از یک فرمت خاص، مطمئن شوید که یک دستگاه آن را در هر دو دسته پشتیبانی می کند.
پشتیبانی حسابی
یک دستگاه Vulkan باید پشتیبانی حسابی را برای یک قالب عددی اعلام کند تا در برنامه های سایه زن قابل استفاده باشد. دستگاههای Vulkan در اندروید معمولاً از فرمتهای زیر برای محاسبات پشتیبانی میکنند:
- عدد صحیح 32 بیتی (اجباری)
- ممیز شناور 32 بیتی (اجباری)
- عدد صحیح 8 بیتی (اختیاری)
- عدد صحیح 16 بیتی (اختیاری)
- ممیز شناور نیمه دقیق 16 بیتی (اختیاری)
برای تعیین اینکه آیا یک دستگاه Vulkan از اعداد صحیح 16 بیتی برای محاسبات پشتیبانی می کند یا خیر، ویژگی های دستگاه را با فراخوانی تابع vkGetPhysicalDeviceFeatures2() بازیابی کنید و بررسی کنید که آیا فیلد shaderInt16
در ساختار نتیجه VkPhysicalDeviceFeatures2 درست است یا خیر.
برای تعیین اینکه آیا دستگاه Vulkan از شناورهای 16 بیتی یا اعداد صحیح 8 بیتی پشتیبانی می کند، مراحل زیر را انجام دهید:
- بررسی کنید که آیا دستگاه از افزونه VK_KHR_shader_float16_int8 Vulkan پشتیبانی می کند یا خیر. پسوند برای پشتیبانی 16 بیتی شناور و اعداد صحیح 8 بیتی مورد نیاز است.
- اگر
VK_KHR_shader_float16_int8
پشتیبانی میشود، یک نشانگر ساختار VkPhysicalDeviceShaderFloat16Int8Features را به زنجیرهVkPhysicalDeviceFeatures2.pNext
اضافه کنید. - پس از فراخوانی
vkGetPhysicalDeviceFeatures2()
، فیلدهایshaderFloat16
وshaderInt8
ساختار نتیجهVkPhysicalDeviceShaderFloat16Int8Features
را بررسی کنید. اگر مقدار فیلدtrue
باشد، قالب برای محاسبات برنامه سایه زن پشتیبانی می شود.
اگرچه در Vulkan 1.1 یا نمایه Android Baseline 2022 الزامی نیست، پشتیبانی از افزونه VK_KHR_shader_float16_int8
در دستگاههای Android بسیار رایج است.
پشتیبانی ذخیره سازی
یک دستگاه Vulkan باید از یک قالب عددی اختیاری برای انواع ذخیره سازی خاص پشتیبانی کند. برنامه افزودنی VK_KHR_16bit_storage پشتیبانی از فرمت های اعداد صحیح 16 بیتی و ممیز شناور 16 بیتی را اعلام می کند. چهار نوع ذخیره سازی توسط افزونه تعریف شده است. یک دستگاه می تواند اعداد 16 بیتی را برای هیچ، برخی یا همه انواع حافظه پشتیبانی کند.
انواع ذخیره سازی عبارتند از:
- ذخیره سازی اشیاء بافر
- اشیاء بافر یکنواخت
- بلوک های ثابت را فشار دهید
- رابط های ورودی و خروجی سایه زن
بیشتر، اما نه همه، دستگاه های Vulkan 1.1 در اندروید از فرمت های 16 بیتی در اشیاء بافر ذخیره سازی پشتیبانی می کنند. پشتیبانی بر اساس مدل GPU را فرض نکنید. ممکن است دستگاههایی با درایورهای قدیمیتر برای یک GPU معین، از اشیاء بافر ذخیرهسازی پشتیبانی نکنند، در حالی که دستگاههایی با درایورهای جدیدتر این کار را انجام میدهند.
پشتیبانی از فرمتهای 16 بیتی در بافرهای یکنواخت، بلوکهای ثابت فشاری و رابطهای ورودی/خروجی سایهزن معمولاً به سازنده GPU وابسته است. در اندروید، یک 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;
}
}
سطح دقیق برای داده ها
یک عدد ممیز شناور با نیم دقت می تواند محدوده کوچکتری از مقادیر را با دقت کمتری نسبت به یک عدد ممیز شناور تک دقیق نشان دهد. نیمه دقیق اغلب یک انتخاب ساده و بدون تلفات ادراکی نسبت به تک دقت است. با این حال، نیم دقت ممکن است در همه موارد استفاده عملی نباشد. برای برخی از انواع داده ها، کاهش دامنه و دقت می تواند منجر به مصنوعات گرافیکی یا ارائه نادرست شود.
انواع داده هایی که کاندیدهای خوبی برای نمایش در ممیز شناور نیمه دقیق هستند عبارتند از:
- داده ها را در مختصات فضای محلی قرار دهید
- UVs بافت برای بافت های کوچکتر با پوشش محدود UV که می تواند در محدوده مختصات -1.0 تا 1.0 محدود شود.
- داده های نرمال، مماس و دو تانژانت
- داده های رنگ راس
- دادههایی با نیازهای دقت پایین با محوریت 0.0
انواع داده هایی که برای نمایش در شناور نیمه دقیق توصیه نمی شوند عبارتند از:
- موقعیت داده ها در مختصات جهانی جهانی
- UVs بافت برای موارد استفاده با دقت بالا مانند مختصات عنصر UI در یک صفحه اطلس
دقت در کد سایه زن
زبانهای برنامهنویسی سایهزن OpenGL Shading Language (GLSL) و زبان Shader سطح بالا (HLSL) از مشخصات دقت آرام یا دقت صریح برای انواع عددی پشتیبانی میکنند. دقت آرام به عنوان یک توصیه برای کامپایلر سایه زن در نظر گرفته می شود. دقت صریح لازمه دقت مشخص شده است. دستگاههای Vulkan در اندروید معمولاً از فرمتهای 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;