إنشاء كائن VkInstance

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

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

إنشاء VkInstance

لإنشاء كائن VkInstance يلزم استخدام الدالة vkCreateInstance():

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

حيث تأخذ ثلاث متغيرات وهي:

  • pCreateInfo والذي يشير للبنية VkInstanceCreateInfo التي تحتوي معلومات تطبيقك والطبقات والإضافات التي يستخدمها
  • pAllocator مؤشر لمخصص الذاكرة الذي تريد استخدامه لحجز الذاكرة، ضعه NULL [١] لاستخدام المخصص الافتراضي
  • pInstance مؤشر سيوضع فيه الكائن بعد إنشاءه إذا نجح استدعاء الدالة وأعادت VK_SUCCESS

البنية VkInstanceCreateInfo تحتوي على معلومات تطبيقك والطبقات والإضافات التي يستخدمها، أعضاء هذه البنية:

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_INSTANCE_CREATE_INFO، هذا النمط عام في جميع تراكيب الواجهة، فكما أشرنا سابقاً أنه موجود في حال أريد تعديل الواجهة في الإصدارات المستقبلية، فيمكن تغيير VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO إلى شيء مثل VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO_EX مع إمكانية تمريرها لـvkCreateInstance() دون الحاجة لإنشاء دالة جديدة
  • العضو pNext يجب أن يكون NULL، هذا النمط عام في جميع تراكيب الواجهة، فهي ليست موجهة لمستخدم Vulkan بل تستخدمها الطبقات والإضافات لإلحاق أي معلومات تخصها مع كائن VkInstance مكونة قائمة متصلة linked list، كل تركيب خاص بالإضافة يحتوي في مقدمته على sType والذي يحدد ماهي الإضافة، وكذلك مؤشر pNext للإضافة التالية، ربما تطلع على الملف المصدري لأحد الإضافات مثل إضافة VK_LAYER_LUNARG_parameter_validation التي تستخدم للتحقق من صحة المعاملات المررة للدوال وترى كيف تستخدم، لكن تذكر أنها ليست موجهة لك ويجب أن تمرر NULL
  • flags غير مستخدم حالياً في الإصدار 1.0 وموجود لأنه قد يستخدم في المستقبل، ضعه صفر 0
  • pApplicationInfo يحتوي على مؤشر لبنية VkApplicationInfo والتي تحتوي معلومات تطبيقك، سنتحدث عنها بعد قليل
  • enabledLayerCount يحتوي على عدد الطبقات التي تريد تفعيلها والتي ستمرر أسماءها في العضو ppEnabledLayerNames، يمكنك تمرير 0 إذا لاتريد تفعيل أي طبقة، وقتها يجب أن تجعل قيمة ppEnabledLayerNames تساوي NULL
  • ppEnabledLayerNames حيث تشير لأسماء الطبقات التي تريد تفعيلها، في حال لم تكن قيمة enabledLayerCount مساوية للصفر، فيجب أن يحتوي هذا العضو على مؤشر لمصفوفة مؤشرات لنصوص UTF-8 كل منها منتهي بـ\0، سنحدث عن الإضافات بعد قليل
  • enabledExtensionCount تشبه enabledLayerCount، ولكن تحتوي على عدد الإضافات التي تريد تفعيلها
  • ppEnabledExtensionNames تحتوي على أسماء الإضافات التي تريد تفعيلها

البنية VkApplicationInfo تحتوي معلومات التطبيق:

typedef struct VkApplicationInfo {
    VkStructureType    sType;
    const void*        pNext;
    const char*        pApplicationName;
    uint32_t           applicationVersion;
    const char*        pEngineName;
    uint32_t           engineVersion;
    uint32_t           apiVersion;
} VkApplicationInfo;

أعضاؤها:

  • sType يجب أن يكون VK_STRUCTURE_TYPE_APPLICATION_INFO
  • pNext يجب أن يكون NULL
  • pApplicationName نص UTF-8 يمثل اسم التطبيق، فائدة أنه يمكن المشغل من التعرف على تطبيقك، مفيد للتنقيح، وقد تستخدمه المشغلات لو كان تطبيقك مشهور بتحسين المشغل وتغير تصرفه ليناسب تطبيقك، هذا العضو اختياري ويمكنك أن تضع NULL
  • applicationVersion ضع به اصدار تطبيقك، يمكنك استخدام الماكرو VK_MAKE_VERSION هكذا مثلاً VK_MAKE_VERSION(0, 0, 1)
  • pEngineName نص UTF-8 يمثل اسم المحرك لو كنت تستخدم واحد في حال كان تطبيقك مصمم كمكوّنات بحيث تكون وحدة الرسوميات مكوّن منفصل، مثلاً يمكنك كتابة مثلاً "VulkanRenderer"، أو ضعه NULL إذا لم ترد استخدامه
  • engineVersion ضع به اصدار محركك أو فقط 0 إذا وضعت NULL في pEngineName
  • apiVersion ضع به اصدار Vulkan المستخدم، كوننا نستخدم الإصدار الأول من Vulkan ضع قيمته VK_API_VERSION_1_0، انتبه ولاتخطئ مستخدماً الماكرو المشابه VK_VERSION_1_0، لأن vkCreateInstance() ستفشل وتعطيك الخطأ VK_ERROR_INCOMPATIBLE_DRIVER إشارةً لعدم دعم الإصدار الذي طلبته

نظام إصدارات Vulkan

حالياً Vulkan في إصدارها الأول 1.0، إصدار Vulkan يكون بالصيغة -من اليسار لليمين- major.minor.patch مثلاً 1.0.53، حيث أن:

  • major يشير للإصدار الرئيسي (1)، أي تغيير في هذا الرقم يعني أن الواجهة البرمجية تغيرت ويلزم أن تعدل أجزاء كبيرة من تطبيقك كي يتوافق مع الإصدار الجديد
  • minor يشير للإصدار الفرعي (0)، تعني حصول تغيير في الواجهة البرمجية دون كسر التوافقية، كإضافة دوال ووظائف جديدة
  • patch يشير لرقم الإصلاح (53)، وهذا يحدث عند إصلاح بعض المشاكل والأخطاء في الوثائق من أخطاء كتابية أو غموض وغيرها من التعديلات الطفيفة، أو كمشاكل الملفات الرأسية الخاصة بلغتي C/C++ لأنها جزء من الـ SDK وليست جزء من الواجهة البرمجية، قد تحتاج فقط لعمل مراجعة بسيطة للكود في الحالة الأخيرة

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

الطبقات

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

هناك طريقتين لتفعيل الطبقات، إما باستخدام العضوين enabledLayerCount و ppEnabledLayerNames في البنية VkInstanceCreateInfo عند إنشاء الكائن VkInstance، أو باستخدام متغير النظام VK_INSTANCE_LAYERS والذي تضع به أسماء الطبقات التي تريد تفعيلها مفصولة بالرمز ; في Windows أو : في Linux وهكذا لاتكون بحاجة لتعديل برنامجك، ويمكنك عمل هذا سواء من سطر الأوامر قبل تشغيل برنامجك، في Linux:

$ export VK_INSTANCE_LAYERS=VK_LAYER_LUNARG_core_validation:VK_LAYER_LUNARG_api_dump
$ echo $VK_INSTANCE_LAYERS
VK_LAYER_LUNARG_core_validation:VK_LAYER_LUNARG_api_dump
$ ./app

وفي Windows (استخدم ; للفصل بين أسماء الطبقات):

>set VK_INSTANCE_LAYERS=VK_LAYER_LUNARG_core_validation:VK_LAYER_LUNARG_api_dump

>echo %VK_INSTANCE_LAYERS%
VK_LAYER_LUNARG_core_validation;VK_LAYER_LUNARG_api_dump

>app.exe

أو من إعدادات التشغيل في بيئة التطوير التي تستخدمها، في Visual Studio يمكنك إضافة متغيرات النظام عن طريق Project > Properties... > Configuration Properties > Debugging > Environment.

أمثلة لبعض الطبقات الملحقة مع Vulkan SDK:

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

هناك بعض الطبقات الأخرى الأقل أهمية، في حال أضيفت طبقة جديدة مهمة للـ SDK سأحدث المقال بتفاصيلها.

الإضافات

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

في Vulkan يجب أن تصرّح عن جميع الإضافات التي تريد استخدام دوالها وذلك باستخدام VkInstanceCreateInfo.ppEnabledExtensionNames عند إنشائك للكائن VkInstance، وإلا فإنه لن تستطيع استخدام دوال تلك الإضافة.

معظم دوال الإضافات لايحملها محمّل Vulkan مع دوال Vulkan الأساسية، بل يلزمك تحميلها يدوياً باستخدام الدالة vkGetInstanceProcAddr() (الـSDK توفر فقط تعريفات هذه الدوال في الملفات الرأسية):

PFN_vkVoidFunction vkGetInstanceProcAddr(
    VkInstance                                  instance,
    const char*                                 pName);

حيث تأخذ الكائن VkInstance في أول متغير، ثم اسم الدالة في المتغير الثاني، مثلاً لتحميل الدالة vkCreateDebugReportCallbackEXT:

auto pfn_vkCreateDebugReportCallbackEXT = reinterpret_cast<PFN_vkCreateDebugReportCallbackEXT>(
  vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT")
);

هناك دالة أخرى مشابهة وهي vkGetDeviceProcAddr() وهي موجودة كي تمكن المحمل من تحميل دوال إضافات خاصة بالجهاز، سنتحدث عنها عند حديثنا عن الأجهزة وعن الكائن VkDevice.

هناك العديد من الإضافات المتاحة في Vulkan، سنتحدث الآن فقط عن الإضافة VK_EXT_debug_report والتي تسمح لك بتسجيل دالة تستدعيها Vulkan وطبقاتها لطباعة رسائل التنقيح والأخطاء، حيث توفر ثلاثة دوال وتركيب بيانات لهذا الغرض.

حتى تستخدم الإضافة VK_EXT_debug_report فإنك بحاجة لإنشاء كائن VkDebugReportCallbackEXT باستخدام الدالة:

VkResult vkCreateDebugReportCallbackEXT(
    VkInstance                                  instance,
    const VkDebugReportCallbackCreateInfoEXT*   pCreateInfo,
    const VkAllocationCallbacks*                pAllocator,
    VkDebugReportCallbackEXT*                   pCallback);

حيث تأخذ في المعامل الأول الكائن VkInstance، وتأخذ مؤشر للبنية VkDebugReportCallbackCreateInfoEXT التي تحتوي معلومات حول الكائن VkDebugReportCallbackEXT الذي تريد إنشاءه، وتأخذ مؤشر للمخصص الذاكرة في المعامل الثالث، وفي الرابع تأخذ مؤشر للمتغير الذي سيخزن فيه الكائن VkDebugReportCallbackEXT الذي ستنشئه الدالة.

البنية VkDebugReportCallbackCreateInfoEXT تحتوي على معلومات حول الكائن VkDebugReportCallbackEXT:

typedef struct VkDebugReportCallbackCreateInfoEXT {
    VkStructureType                 sType;
    const void*                     pNext;
    VkDebugReportFlagsEXT           flags;
    PFN_vkDebugReportCallbackEXT    pfnCallback;
    void*                           pUserData;
} VkDebugReportCallbackCreateInfoEXT;

أعضاؤها:

  • sType يجب أن يكون VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT
  • pNext يجب أن يكون NULL
  • flags يحدد نوع رسائل التنقيح التي تريد تفعيلها، هناك خمسة أنواع للرسائل (يمكنك استخدام العملية | لتفعيل نوعين فأكثر، مثل VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT):
    • VK_DEBUG_REPORT_INFORMATION_BIT_EXT رسائل معلومات عامة
    • VK_DEBUG_REPORT_WARNING_BIT_EXT رسائل التحذيرات
    • VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT رسائل التحذيرات
    • VK_DEBUG_REPORT_ERROR_BIT_EXT رسائل الأخطاء
    • VK_DEBUG_REPORT_DEBUG_BIT_EXT رسائل التنقيح
  • pfnCallback مؤشر للدالة التي سيتم استدعاءها عندما تتم طباعة رسالة، سنعرض تعريفها بعد قليل
  • pUserData مؤشر لأي بيانات ترغب بتمريرها للدالة pfnCallback في الحقل pUserData، يمكن تمرير NULL إذا لم ترد استخدامه

مثال لدالة يمكن تمريرها للعضو pfnCallback حيث تطبع الرسالة للـstderr (دالة to_string أخذتها من الملف الرأسي vulkan/vulan.hpp):

static inline std::string to_string(VkDebugReportFlagsEXT value)
{
 if (!value) return "";
 std::string result;
 if (value & VK_DEBUG_REPORT_INFORMATION_BIT_EXT) result += "Information | ";
 if (value & VK_DEBUG_REPORT_WARNING_BIT_EXT) result += "Warning | ";
 if (value & VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT) result += "PerformanceWarning | ";
 if (value & VK_DEBUG_REPORT_ERROR_BIT_EXT) result += "Error | ";
 if (value & VK_DEBUG_REPORT_DEBUG_BIT_EXT) result += "Debug | ";
 return result.substr(0, result.size() - 3);
}

static VkBool32 VKAPI_PTR PrintToStderrCallback(VkDebugReportFlagsEXT flags,
                                      VkDebugReportObjectTypeEXT objectType,
                                      uint64_t object,
                                      size_t location,
                                      int32_t messageCode,
                                      const char* pLayerPrefix,
                                      const char* pMessage,
                                      void* pUserData)
{
  // غير مستخدمة
  (void)objectType;
  (void)object;
  (void)location;
  (void)messageCode;
  (void)pUserData; // المتغير الذي مررته عندما أنشأت كائن التنقيح

  std::fprintf(stderr, "[%s] %s:%s\n", to_string(flags).c_str(), pLayerPrefix, pMessage); 

  return VK_FALSE;
}

لن أتطرق للمعاملات الممررة لدالتنا، معظمها لها معنى يختلف باختلاف الطبقة التي استدعت دالتك، التي لها معنى معرّف هي flags والتي تمثل نوع الرسالة، وpLayerPrefix وهي الإسم الكامل أو الرمزي للطبقة التي استدعت دالتك، وpMessage والتي تحتوي الرسالة، وهناك أيضاً المؤشر pUserData وهو نفسه المؤشر الذي يشير للبيانات التي تمررتها عندما أنشأت كائن التنقيح.

مهم أن تعرف أن الدالة الممررة تعيد VkBool32، ويجب أن تعيد VK_FALSE، فلو أعدت VK_TRUE فإن الدالة التي استدعت دالتك ستجبر على التوقف ولن تكمل الاستدعاء.

عند الإنتهاء من البرنامج ولهدم الكائن VkDebugReportCallbackEXT يمكنك استدعاء الدالة (يجب تحميلها باستخدام vkGetInstanceProcAddr()):

void vkDestroyDebugReportCallbackEXT(
    VkInstance                                  instance,
    VkDebugReportCallbackEXT                    callback,
    const VkAllocationCallbacks*                pAllocator);

هناك أيضاً الدالة vkDebugReportMessageEXT() والتي تستخدم لإرسال رسالة تنقيح باستخدام الكائن VkDebugReportCallbackEXT، لكن لن نغطيها، ففي العادة يستخدمها مطوري الإضافات.

ستجد في آخر المقال مثال كامل لإستخدام هذه الإضافة.

هدم الكائن VkInstance

عندما ننتهي من Vulkan -عادةً في نهاية البرنامج- فيجب استدعاء الدالة vkDestroyInstance():

void vkDestroyInstance(
    VkInstance                                  instance,
    const VkAllocationCallbacks*                pAllocator);

يجب قبلها أن تكون هدمت جميع كائنات Vulkan التي أنشأتها من دوال vkCreate*()، صحيح أن نظام التشغيل سيحرر جميع الموارد التي حجزتها العملية عند إنهائها كما في حالة إغلاق البرنامج، إلا أنه من الأفضل أن تصمم برنامجك تحت فرضية أنه قد يتغير ويعمل كوحدة مستقلة مستقبلاً أو يكون ملف DLL وسيستمر البرنامج بالعمل حتى بعد الانتهاء من استخدام Vulkan.

مثال

هذا تطبيق على ماذكر في المقال (سأستخدم assert للتحقق من نجاح الإستدعاءات لتقليل عدد الأسطر):

#include <vector>
#include <cassert>
#include <cstdio>
#include <vulkan/vulkan.h>

VkBool32 DebugReportCallback(VkDebugReportFlagsEXT flags,
                             VkDebugReportObjectTypeEXT objectType,
                             uint64_t object,
                             size_t location,
                             int32_t messageCode,
                             const char* pLayerPrefix,
                             const char* pMessage,
                             void* pUserData)
{
  (void)flags;
  (void)objectType;
  (void)object;
  (void)location;
  (void)messageCode;
  (void)pUserData;

  std::fprintf(stderr, "%s: %s\n", pLayerPrefix, pMessage);
  return VK_FALSE;
}

int main()
{
  VkResult result;

  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // إنشاء الكائن

  VkInstance instance;

  static VkApplicationInfo applicationInfo{};
  applicationInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
  applicationInfo.pNext = nullptr;
  applicationInfo.pApplicationName = "VkDemo";
  applicationInfo.applicationVersion = VK_MAKE_VERSION(0, 0, 1);
  applicationInfo.pEngineName = "VkRenderer";
  applicationInfo.engineVersion = VK_MAKE_VERSION(0, 0, 1);
  applicationInfo.apiVersion = VK_API_VERSION_1_0;

  const std::vector<const char*> enabledExtensions = {
    "VK_EXT_debug_report"
  };

  VkInstanceCreateInfo instanceCreateInfo{};
  instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
  instanceCreateInfo.pNext = nullptr;
  instanceCreateInfo.flags = 0;
  instanceCreateInfo.pApplicationInfo = &applicationInfo;
  instanceCreateInfo.enabledLayerCount = 0;
  instanceCreateInfo.ppEnabledLayerNames = nullptr;
  instanceCreateInfo.enabledExtensionCount = static_cast<uint32_t>(enabledExtensions.size());
  instanceCreateInfo.ppEnabledExtensionNames = enabledExtensions.data();

  result = vkCreateInstance(&instanceCreateInfo, nullptr, &instance);
  assert(result == VK_SUCCESS);

  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // إنشاء كائن التنقيح

  VkDebugReportCallbackEXT debugReportCallback;

  auto pfn_vkCreateDebugReportCallbackEXT = reinterpret_cast<PFN_vkCreateDebugReportCallbackEXT>(
    vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT")
  );
  assert(pfn_vkCreateDebugReportCallbackEXT != nullptr);

  VkDebugReportCallbackCreateInfoEXT debugReportCallbackCreateInfoEXT{};
  debugReportCallbackCreateInfoEXT.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT;
  debugReportCallbackCreateInfoEXT.pNext = nullptr;
  debugReportCallbackCreateInfoEXT.flags = VK_DEBUG_REPORT_INFORMATION_BIT_EXT |
                                           VK_DEBUG_REPORT_WARNING_BIT_EXT |
                                           VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT |
                                           VK_DEBUG_REPORT_ERROR_BIT_EXT |
                                           VK_DEBUG_REPORT_DEBUG_BIT_EXT;
  debugReportCallbackCreateInfoEXT.pfnCallback = DebugReportCallback;
  debugReportCallbackCreateInfoEXT.pUserData = nullptr;

  result = pfn_vkCreateDebugReportCallbackEXT(instance,
                                              &debugReportCallbackCreateInfoEXT,
                                              nullptr,
                                              &debugReportCallback);
  assert(result == VK_SUCCESS);

  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // هدم الكائنات

  auto pfn_vkDestroyDebugReportCallbackEXT = reinterpret_cast<PFN_vkDestroyDebugReportCallbackEXT>(
    vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT")
  );
  assert(pfn_vkDestroyDebugReportCallbackEXT != nullptr);

  pfn_vkDestroyDebugReportCallbackEXT(instance, debugReportCallback, nullptr);

  ////

  vkDestroyInstance(instance, nullptr);

  return 0;
}
  1. أو ضع nullptr إن كنت تستخدم C++11 وأحدث