هذا المنشور مخصص لترجمة قسم رسم المثلث ، أي القسم الفرعي للإعداد ، وكود القاعدة وفصول المثيل.
المحتوى
الكود الأساسي
الهيكل العام
في الفصل السابق ، تناولنا كيفية إنشاء مشروع لـ 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!
نموذج
تجسيد
أول شيء عليك القيام به هو إنشاء مثيل لتهيئة المكتبة. مثال هو الرابط بين برنامجك ومكتبة 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 ++