Vulkan

من ويكي
اذهب إلى: تصفح، ابحث

Vulkan هي واجهة برمجية منخفضة المستوى متعددة المنصات للتصيير اللحظي والحوسبة طورتها مجموعة Khronos، تكافئ Direct3D 12 من مايكروسوف و Metal من أبل.

بدأ مشروع Vulkan في البداية بالإسم glNext ليمثل المشروع إمتداد لواجهة OpenGL لتناسب العتاد الحديث، لكن المشروع تباطأ نظراً للإختلاف الكبير بين OpenGL وأهداف المشروع، فواجهة OpenGL صممت في أوائل التسعينات للمسرعات الرسومية القديمة، ولم تعد تعكس الطريقة التي تعمل بها كروت الشاشة الحديثة، حدث إنعطاف في المشروع عندما تبرعت AMD بواجهتها الخاصة Mantle والذي أضاف دفعة كبيرة للمشروع وقلل الجدول الزمني، النتيجة النهائية هي واجهة برمجية جديدة ومستقلة عن OpenGL تسمى Vulkan تتشابه في جوهرها مع Mental.

الإعلان عن Vulkan في مؤتمر مطورين الألعاب GDC 2015 (العرض التقديمي المستخدم).

يرجى الانتباه إلى أن Vulkan مجرد مواصفات، الذي يطبق هذه المواصفات هم مصممين بطاقات الشاشة والمعالجات الرسومية، جودة المشغلات drivers التي توفرها هذه الشركات هي ما يحدد سرعة الأداء، الشيء المميز في Vulkan أنها وفرت اختبارات توافقية قياسية كي يستخدمها مطوري المشغلات للتحقق من توافقية مشغلاتهم مع Vulkan، وذلك لتفادي الإشكالات الحاصلة مع OpenGL عندما يختلف تصرف بعض الدوال باختلاف المشغل.

أيضاً Vulkan ليست محدودة فقط لكروت الشاشة، بل يمكن استخدامها مع المعالجات المركزية، والمعالجات الرسومية المدمجة.

Vulkan حالياً مدعومة على Windows 7 و 8 و 10 وعلى Linux وعلى Android.

نظرة عامة

Vulkan واجهة منخفضة المستوى وتمثل الطريقة التي تعمل بها الأجهزة الحديثة، تضع على عاتق المبرمج مسؤولية تخصيص وإدارة الموارد والتي كان يتولاها المشغل في OpenGL، ذلك لكون المطور أدرى بتطبيقه من المشغل، وهذا يساهم في جعل أداءها أكثر توقع، أيضاً لاتقوم Vulkan بالتحقق من صحة وسلامة المتغيرات الممررة لما قد يسببه ذلك من بطئ أداء البرنامج، بل توفر بدلاً من هذا طبقة تنقيح يمكن إستخدامها أثناء تطوير البرنامج وإزالتها عند نشر البرنامج، ولا تقوم بمزامنة الوصول للموارد عندما تقوم خيوط المعالجة المختلفة بمحاولة الوصول للموارد لما قد يسببه من محدودية المعالجة المتوازية، بل يتولى المبرمج هذه المسئولية بنفسه بما يناسب تطبيقه.

في Vulkan هناك طوابير معالجة لكل جهاز سواء كان كرت شاشة أو غيره من أجهزة التسريع، أهمها طوابير الرسوميات graphics queues وطوابير الحوسبة compute queues، عندما تريد تصيير شيء، فإنك تسجّل أوامر التصيير والرسم في ذاكرة أوامر command buffer، يمكن تسجيل هذه الأوامر بالتوازي بحيث توزع أوامر التصيير والرسم على عدة خيوط معالجة threads ثم ترسل ذاكرة الأوامر هذه لطابور المعالجة كي ينفذها الجهاز ويعود لك بالنتيجة، هذه الميزة هي أحد أهم ميزات Vulkan لأنها تساهم في الاستفادة من الأنوية المتعددة في المعالجات خصوصاً عندما ترسل عدد كبير جداً من الأوامر، قارن هذا مع OpenGL والتي لا تمكن إلا من تشغيل خيط معالجة واحد يشغل نواة واحدة ويبقى باقي الأنوية خاملة، يمكنك الإستفادة من هذا التوفير سواء لإرسال المزيد من الأوامر للجهاز وعمل مثلاً مؤثرات إضافية أو لتسريع التطبيق وتوفير تجربة أكثر سلاسة للمستخدمين، تمكن Vulkan أيضاً من إعادة إرسال الأوامر التي سجلتها دون الحاجة لإعادة تسجيلها مع كل مرة تريد تنفيذ نفس الأمر.

القدرة على المعالجة المتوازية تمكن أيضاً من توفير الطاقة خصوصاً على الجوالات والكمبيوترات المحمولة لأنها تمكن من توزيع المهام على عدة معالجات تعمل بترددات منخفضة، شاهد هذا الفديو من ARM والذي يوضح التوفير المحتمل للطاقة، وضع في الاعتبار أن هذا الفديو قديم، لذا الأداء قد يتحسن كثيراً مع الوقت.

وفرت Vulkan لغة تظليل جديدة اسمها SPIR-V، وهي عبارة عن تعليمات ثنائية binary bytecode، حيث لم تعد بحاجة لنشر المظللات بصيغتها النصية كما هو الحال مع OpenGL، ويمكنك استخدام أي لغة لتوليد تعليمات SPIR-V، أو تصمم أداة تصميم رسومية خاصة بك تولد تعليمات الخاصة بـSPIR-V من محرر رسومي مثلاً، فاللغة بسيطة، علماً أنه يتوفر مع الـ SDK القياسية مترجم يولد تعليمات SPIR-V من مظللات GLSL.

أهم الميزات باختصار:

  • أداء أسهل توقع ومستقر بسبب إزالة جزئيات التحقق وتوفيرها كطبقات قابلة للإزالة، وكذلك الجزئيات التي يضطر بها المشغل لتخمين ماتريد عمله
  • إمكانية استغلال الأنوية المتعددة في المعالج وذلك بتوزيع مهمة تسجيل الأوامر على تلك الأنوية
  • تقليل مهام المشغل للحد الأدنى وتحميلك الكثير من المسؤليات التي كان يتولها في الواجهات البرمجية الأقدم
  • توفير لغة تظليل ثنائية تمكنك من استخدام أي أداة أو لغة لتوليد المظللات

التصميم العام للواجهة البرمجية

Vulkan عبارة عن واجهة برمجية تستخدم C بطريقة حديثة و كائنية، وتدعم الإضافات بطريقة نظيفة، واجهة Vulkan نظيفة ومتناسقة مقارنة مع OpenGL، لكنها مسهبة وتحتاج لكتابة الكثير من الأسطر لإنشاء أبسط البرامج، لذا قد لاتكون الخيار الأفضل للنمذجة والتجارب التي لا تعتمد كثيراً على الأداء، Vulkan عموماً ليست بالواجهة البرمجية البسيطة، بل تحتاج خلفية جيدة في العتاد وبرمجة النظم والرسوميات، لذا ليست واجهة مناسبة لتتعلم معها أساسيات الرسوميات، لكن لو كانت لديك الخلفية الجيدة والأساسيات فستكون Vulkan إضافة كبيرة لأنها ستساعدك في فهم كيفية عمل المسرعات الرسوميات و الواجهات البرمجية ومشغّلاتها.

تبدأ دوال Vulkan بالحرفين vk الصغيرة، مثل vkCreateDevice()، الدوال التي تبدأ بالسابقة vkCreate*() تستخدم لإنشاء كائن، والتي تبدأ بالسابقة vkDestroy*() لهدم الكائن، مع بعض الدوال والوظائف مثل vkGetPhysicalDeviceProperties() و vkCmdClearColorImage().

معظم الدوال تستقبل المتغيرات الممررة لها كبنى C نظراً للعدد الكبير من المتغيرات التي تستقبلها هذه الدول، تبدأ هذه البنى بالحرفين Vk أولهما كبير، مثلاً الدالة vkCreateInstance() تستقبل:

VkResult vkCreateInstance(
    const VkInstanceCreateInfo*                 pCreateInfo,
    const VkAllocationCallbacks*                pAllocator,
    VkInstance*                                 pInstance);

معظم هذه البنى تحتوي عضوين أحدهما VkStructureType sType والآخر const void* pNext مثل:

typedef struct VkInstanceCreateInfo {
    VkStructureType             sType;
    const void*                 pNext;
    VkInstanceCreateFlags       flags;
    const VkApplicationInfo*    pApplicationInfo;
    uint32_t                    enabledLayerCount;
    const char* const*          ppEnabledLayerNames;
    uint32_t                    enabledExtensionCount;
    const char* const*          ppEnabledExtensionNames;
} VkInstanceCreateInfo;

حيث تأخذ sType ثابت يحدد نوع البنية ويبدأ بالسابقة VK_STRUCTURE_TYPE_* والتي يليها اسم البنية مثل VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO في مثالنا السابق، الهدف من العضو sType هو أنه لو تقرر إجراء تغيير في الواجهة البرمجية مستقبلاً سواء بإضافة أو تعديل أحد أعضاء البنية، فكل ما يلزم هو تغيير البنية إلى مثلاً VkInstanceCreateInfoEx وتغيير قيمة sType إلى VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO_EX مع إبقاء الدالة التي تسخدم هذه البنية كما هي دون الحاجة لإضافة دالة vkCreateInstanceEx() جديدة، هذا سيساهم في تقليل عدد الدوال.

هناك أيضاً pNext وهو مؤشر يأخذ المتغيرات التي تريد تمريرها للإضافة في حال كنت تستخدم واحدة، عدى هذا إجعل قيمتها NULL.

معظم الدوال -خصوصاً تلك التي تبدأ بالسابقة vkCreate*()- تأخذ مؤشر لبنية VkAllocationCallbacks، وهي عبارة عن مجموعة مؤشرات لدوال تخصيص ذاكرة:

typedef struct VkAllocationCallbacks {
    void*                                   pUserData;
    PFN_vkAllocationFunction                pfnAllocation;
    PFN_vkReallocationFunction              pfnReallocation;
    PFN_vkFreeFunction                      pfnFree;
    PFN_vkInternalAllocationNotification    pfnInternalAllocation;
    PFN_vkInternalFreeNotification          pfnInternalFree;
} VkAllocationCallbacks;

تسمح لك هذه البنية باستخدام مخصصات ذاكرة خاصة بك في حال أردت مثلاً دراسة سلوك حجز الذاكرة في برنامجك وكم يستهلك، أو عند تشغيل Vulkan في الأنظمة المضمنة محدودة الذاكرة، عادةً تُنشأ الكائنات خارج الأجزاء التي تؤثر على سرعة البرنامج لذا هذه البنية لا يقصد منها استخدام مخصصات ذاكرة سريعة، عادةً يمرر NULL لجعل بيئة التشغيل تستخدم مخصص الذاكرة الافتراضي الذي يأتي معها، لكن من الأفضل ربما أن لا تقطع الطريق على نفسك، وبدلاً من هذا أن تنشئ متغير نوعه VkAllocationCallbacks * مثل:

static const VkAllocationCallbacks *Allocators = nullptr;

وتمرر Allocators، كي يسهل عليك اضافة مخصص ذاكرة لو أردت ذلك مستقبلاً.

نلاحظ أن آخر معامل مرر للدالة vkCreateInstance() هو مؤشر VkInstance * والذي يستخدم لإعادة قيمة الكائن الذي سيمرر لاحقاً للدوال التي تستخدم هذا الكائن، بعض أنواع Vulkan مثل VkInstance * عبارة عن كائنات مخفيّة لايمكنك الوصول لعناصرها الداخلية لأنها قد تتغير في أي لحظة، لذا هي مخفية كي لا تحدث مشاكل في التوافقية في حال اعتمدت عليها ولكي يتمكن مطورين المشغلات من تغيير تركيبها بحرية دون كسر التوافقية مع البرامج، يسمى هذا المبدأ بإخفاء البيانات في التصميم الكائني.

تعيد معظم الدوال أحد قيم VkResult والي تخبرك ما إذا نجح أو فشل استدعاء الدالة، ففي حال نجاحها فإنها ستعيد القيمة VK_SUCCESS أو أي قيمة أخرى في حال الفشل، تحقق دوماً من نجاح الدوال خصوصاً تلك التي تبدأ بالسابقة vkCreate*() لأنه عادةً ستكون هناك دالة تعتمد عليها وقد ينهار برنامجك إذا استقبلت تلك الدالة مؤشر لكائن غير صالح، هناك عدد قليل من الدوال التي يمكن التغاضي عن أخطائها، وهناك دوال قلما تفشل إلا لأسباب مثل وجود خطأ في الإعدادات، في هذه الحالة يمكن تجاهل قيمة VkResult والإعتماد على إضافات التقيح أو برامج التقيح الخارجية مثل RenderDoc، أما بالنسبة للأخطاء التي تظهر في الحالات المتطرفة مثل إستهلاك الذاكرة الرئيسية فمن الأفضل ترك البرنامج ينهار لأنه لايمكن تلافيها.

الملف الرأسي vulkan/vulkan.h و vulkan/vulkan.hpp

الملف الرأسي الذي يحتوي جميع واجهة Vulkan هو vulkan/vulkan.h، توفر Vulkan SDK ملف رأسي للغة C++ بالإسم vulkan/vulkan.hpp طوره في البداية Nvidia ثم أصبحت جزء من Vulkan حيث توفر تغليف خفيف على واجهة الـC لتقليل عدد الأسطر وتسهيل العمل مع الواجهة البرمجية، وهي مجرد ملف رأسي دون أي مكتبات إضافية.

كلاهما متكافئين، لكن الأفضل استخدام الأخير مستقبلاً نظراً لتقليله عدد الأسطر، سنستخدم الملف الرأسي القياسي لكون vulkan/vulkan.hpp غير مستقر في الوقت الحالي.

البدء مع Vulkan