فولكان. دليل المطور. ارسم مثلثًا

أنا مترجم في CG Tribe في إيجيفسك ، وأواصل تحميل ترجمات دليل Vulkan API. رابط المصدر - vulkan-tutorial.com .



هذا المنشور مخصص لترجمة قسم رسم المثلث ، أي القسم الفرعي للإعداد ، وكود القاعدة وفصول المثيل.



المحتوى
1.



2.



3.



4.





  1. (pipeline)


5.



  1. Staging


6. Uniform-



  1. layout
  2. sets


7.



  1. Image view image sampler
  2. image sampler


8.



9.



10. -



11. Multisampling



FAQ









الكود الأساسي







الهيكل العام



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



ضع في اعتبارك الكود التالي:



#include <vulkan/vulkan.h>

#include <iostream>
#include <stdexcept>
#include <cstdlib>

class HelloTriangleApplication {
public:
    void run() {
        initVulkan();
        mainLoop();
        cleanup();
    }

private:
    void initVulkan() {

    }

    void mainLoop() {

    }

    void cleanup() {

    }
};

int main() {
    HelloTriangleApplication app;

    try {
        app.run();
    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}
      
      





أولاً ، نقوم بتضمين ملف رأس Vulkan من LunarG SDK. رأس الملفات stdexcepts



و iostream



تستخدم لمعالجة الأخطاء والتوزيع. ملف الرأس cstdlib



يوفر وحدات الماكرو EXIT_SUCCESS



و EXIT_FAILURE



.



يتم تغليف البرنامج نفسه في فئة HelloTriangleApplication ، حيث سنخزن كائنات Vulkan كأعضاء خاصين في الفصل. هناك سنضيف أيضًا وظائف لتهيئة كل كائن ، يتم استدعاؤه من الوظيفة initVulkan



. بعد ذلك ، لنقم بإنشاء حلقة رئيسية لتصيير الإطارات. للقيام بذلك ، قم بملء وظيفة mainLoop



حيث سيتم تنفيذ الحلقة حتى يتم إغلاق النافذة. بعد إغلاق النافذة والخروج mainLoop



يجب تحرير الموارد. للقيام بذلك ، املأ cleanup



.



إذا حدث خطأ فادح أثناء العملية ، فسنطرح استثناءًا std::runtime_error



سيتم اكتشافه في الوظيفة main



، وسيتم عرض الوصف فيه std::cerr



. قد يكون أحد هذه الأخطاء ، على سبيل المثال ، رسالة مفادها أن الامتداد المطلوب غير مدعوم. للتعامل مع العديد من أنواع الاستثناءات القياسية ، نلاحظ نوعًا أكثر عمومية std::exception



.



سيضيف كل فصل لاحق تقريبًا وظائف initVulkan



جديدة تُستدعى من ، وكائنات Vulkan الجديدة التي يجب تحريرها في cleanup



نهاية البرنامج.



إدارة الموارد



إذا لم تعد هناك حاجة إلى كائنات Vulkan ، فيجب تدميرها. يتيح لك C ++ إلغاء تخصيص الموارد تلقائيًا باستخدام RAII أو المؤشرات الذكية التي يوفرها ملف الرأس <memory>



. ومع ذلك ، في هذا البرنامج التعليمي ، قررنا أن نكتب بوضوح متى يتم تخصيص كائنات Vulkan وإلغاء تخصيصها. في الواقع ، هذه هي خصوصية عمل فولكان - لوصف كل عملية بالتفصيل من أجل تجنب الأخطاء المحتملة.



بعد قراءة البرنامج التعليمي ، يمكنك تنفيذ إدارة الموارد التلقائية عن طريق كتابة فئات C ++ التي تتلقى كائنات Vulkan في المُنشئ وتحريرها في المدمر. يمكنك أيضًا تنفيذ المحدد الخاص بك لـ std::unique_ptr



or std::shared_ptr



، حسب المتطلبات. يوصى بمفهوم RAII للبرامج الأكبر ، ولكن من المفيد معرفة المزيد عنها.



يتم إنشاء كائنات Vulkan مباشرة باستخدام وظيفة مثل vkCreateXXX ، أو تخصيصها من خلال كائن آخر باستخدام وظيفة مثل vkAllocateXXX . بعد التأكد من أن الكائن ليس قيد الاستخدام في أي مكان آخر ، يجب تدميره باستخدام vkDestroyXXX أو vkFreeXXX . عادةً ما تختلف معلمات هذه الوظائف اعتمادًا على نوع الكائن ، ولكن هناك معلمة واحدة مشتركة: pAllocator



... هذه معلمة اختيارية تتيح لك استخدام عمليات الاسترجاعات لتخصيص الذاكرة المخصصة. لن نحتاجه في الدليل ، سنقوم بتمريره كحجة nullptr



.



تكامل GLFW



يعمل Vulkan بشكل جيد بدون إنشاء نافذة عند استخدام العرض خارج الشاشة ، ولكنه أفضل بكثير عندما تكون النتيجة مرئية على الشاشة.

أولاً ، استبدل الخط بما #include <vulkan/vulkan.h>



يلي:



#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
      
      





أضف وظيفة initWindow



وأضف مكالمتها من الطريقة run



قبل المكالمات الأخرى. سنستخدم initWindow



GLFW لتهيئة النافذة وإنشائها.



void run() {
    initWindow();
    initVulkan();
    mainLoop();
    cleanup();
}

private:
    void initWindow() {

    }
      
      





initWindow



يجب أن يكون الاستدعاء الأول دالة glfwInit()



تقوم بتهيئة مكتبة GLFW. تم تصميم GLFW في الأصل للعمل مع OpenGL. لا نحتاج إلى سياق OpenGL ، لذا أشر إلى أننا لسنا بحاجة إلى إنشائه باستخدام الاستدعاء التالي:



glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
      
      





قم بتعطيل القدرة على تغيير حجم النافذة مؤقتًا ، نظرًا لأن التعامل مع هذا الموقف يتطلب دراسة منفصلة:



glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
      
      





يبقى لإنشاء نافذة. للقيام بذلك ، أضف عضوًا خاصًا GLFWwindow* window;



وقم بتهيئة النافذة باستخدام:



window = glfwCreateWindow(800, 600, "Vulkan", nullptr, nullptr);
      
      





تحدد المعلمات الثلاثة الأولى عرض النافذة وارتفاعها وعنوانها. المعلمة الرابعة اختيارية ، فهي تتيح لك تحديد الشاشة التي سيتم عرض النافذة عليها. المعلمة الأخيرة خاصة بـ OpenGL.



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



:



const uint32_t WIDTH = 800;
const uint32_t HEIGHT = 600;
      
      





واستبدل المكالمة لإنشاء نافذة بها



window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
      
      





يجب أن يكون لديك الوظيفة التالية initWindow



:



void initWindow() {
    glfwInit();

    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
    glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);

    window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
}


      
      





دعنا نصف الحلقة الرئيسية في الطريقة mainLoop



لإبقاء التطبيق قيد التشغيل حتى يتم إغلاق النافذة:



void mainLoop() {
    while (!glfwWindowShouldClose(window)) {
        glfwPollEvents();
    }
}
      
      





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



بعد إغلاق النافذة ، نحتاج إلى تحرير الموارد والخروج من GLFW. أولاً ، دعنا نضيف إلى cleanup



الكود التالي:



void cleanup() {
    glfwDestroyWindow(window);

    glfwTerminate();
}
      
      





نتيجة لذلك ، بعد بدء البرنامج ، سترى نافذة تحمل اسمًا Vulkan



سيتم عرضها حتى يتم إغلاق البرنامج. الآن بعد أن أصبح لدينا هيكل عظمي للعمل مع Vulkan ، دعنا ننتقل إلى إنشاء أول كائن Vulkan!



كود C ++











نموذج







تجسيد



أول شيء عليك القيام به هو إنشاء مثيل لتهيئة المكتبة. مثال هو الرابط بين برنامجك ومكتبة Vulkan ، ومن أجل إنشائه ، ستحتاج إلى تزويد السائق ببعض المعلومات حول برنامجك.



أضف طريقة createInstance



واستدعها من وظيفة initVulkan



.



void initVulkan() {
    createInstance();
}
      
      





أضف عضو مثيل إلى صفنا لعقد معالجة مثيل:



private:
VkInstance instance;
      
      





الآن نحن بحاجة إلى ملء هيكل خاص بمعلومات حول البرنامج. من الناحية الفنية ، تعتبر البيانات اختيارية ، ولكن هذا سيسمح للسائق بالحصول على معلومات مفيدة لتحسين العمل مع برنامجك. هذا الهيكل يسمى VkApplicationInfo



:



void createInstance() {
    VkApplicationInfo appInfo{};
    appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
    appInfo.pApplicationName = "Hello Triangle";
    appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
    appInfo.pEngineName = "No Engine";
    appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
    appInfo.apiVersion = VK_API_VERSION_1_0;
}
      
      





كما ذكرنا ، تتطلب العديد من الهياكل في Vulkan تعريفًا واضحًا للنوع في عضو sType . أيضًا ، تحتوي هذه البنية ، مثل العديد من الهياكل الأخرى ، على عنصر pNext



يسمح لك بتوفير معلومات للامتدادات. نستخدم تهيئة القيمة لملء الهيكل بالأصفار.



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



VkInstanceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
createInfo.pApplicationInfo = &appInfo;
      
      





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



uint32_t glfwExtensionCount = 0;
const char** glfwExtensions;

glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

createInfo.enabledExtensionCount = glfwExtensionCount;
createInfo.ppEnabledExtensionNames = glfwExtensions;
      
      





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



createInfo.enabledLayerCount = 0;
      
      





الآن فعلت كل ما هو ضروري لإنشاء مثيل. قم بإجراء مكالمة vkCreateInstance



:



VkResult result = vkCreateInstance(&createInfo, nullptr, &instance);
      
      





كقاعدة عامة ، تكون معلمات وظائف إنشاء الكائنات بالترتيب التالي:



  • مؤشر إلى هيكل بالمعلومات اللازمة
  • المؤشر لمخصص مخصص
  • مؤشر إلى المتغير حيث سيتم كتابة واصف الكائن الجديد


إذا تم كل شيء بشكل صحيح ، فسيتم تخزين واصف المثيل على سبيل المثال . تقوم جميع وظائف Vulkan تقريبًا بإرجاع قيمة VkResult ، والتي يمكن أن تكون إما VK_SUCCESS



رمز خطأ. لا نحتاج إلى تخزين النتيجة للتأكد من إنشاء المثيل. دعنا نستخدم فحصًا بسيطًا:



if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
    throw std::runtime_error("failed to create instance!");
}
      
      





قم الآن بتشغيل البرنامج للتحقق من إنشاء المثيل بنجاح.



التحقق من الملحقات المدعومة



إذا نظرنا إلى وثائق Vulkan ، يمكننا أن نجد أن أحد رموز الخطأ المحتملة هو VK_ERROR_EXTENSION_NOT_PRESENT



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



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



لتخصيص ذاكرة لتخزين معلومات الامتداد ، تحتاج أولاً إلى معرفة عدد الامتدادات. اترك المعلمة الأخيرة فارغة لطلب عدد الامتدادات:



uint32_t extensionCount = 0;
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
      
      





تخصيص مصفوفة لتخزين معلومات الامتداد (لا تنسى include <vector>



):



std::vector<VkExtensionProperties> extensions(extensionCount);
      
      





يمكنك الآن طلب معلومات حول الامتدادات.



vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());
      
      





تحتوي كل بنية VkExtensionProperties على اسم الامتداد وإصداره . يمكن إدراجها بحلقة for بسيطة ( \t



هنا علامة تبويب المسافة البادئة):



std::cout << "available extensions:\n";

for (const auto& extension : extensions) {
    std::cout << '\t' << extension.extensionName << '\n';
}
      
      





يمكنك إضافة هذا الرمز إلى وظيفة createInstance



لمزيد من المعلومات حول دعم Vulkan. يمكنك أيضًا محاولة إنشاء وظيفة تتحقق مما إذا كانت جميع الامتدادات التي أرجعها الوظيفة glfwGetRequiredInstanceExtensions



مدرجة في قائمة الامتدادات المدعومة.





تنظيف



يجب إتلاف VkInstance قبل إغلاق البرنامج. يمكن القيام بذلك cleanup



باستخدام وظيفة VkDestroyInstance :



void cleanup() {
    vkDestroyInstance(instance, nullptr);

    glfwDestroyWindow(window);

    glfwTerminate();
}
      
      





معلمات دالة vkDestroyInstance تشرح نفسها بنفسها . كما ذكرنا في الفصل السابق ، فإن وظائف التخصيص وإلغاء التخصيص في Vulkan تقبل المؤشرات الاختيارية للمخصصات المخصصة التي لا نستخدمها ونمررها nullptr



. يجب تنظيف جميع موارد Vulkan الأخرى قبل إتلاف المثيل.



قبل الانتقال إلى خطوات أكثر تعقيدًا ، نحتاج إلى إعداد طبقات التحقق من الصحة لسهولة التصحيح.



كود C ++



All Articles