Vulkan. Panduan pengembang. Gambarlah segitiga

Saya seorang penerjemah untuk Suku CG di Izhevsk, dan saya terus mengunggah terjemahan manual API Vulkan. Tautan sumber - vulkan-tutorial.com .



Publikasi ini didedikasikan untuk terjemahan bagian Menggambar segitiga, yaitu subbagian Setup, kode Dasar dan bab Instance.



Kandungan
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









Kode Dasar







Struktur umum



Di bab sebelumnya, kami membahas cara membuat project untuk Vulkan, mengonfigurasinya dengan benar, dan mengujinya menggunakan cuplikan kode. Dalam bab ini, kita akan mulai dengan dasar-dasarnya.



Perhatikan kode berikut:



#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;
}
      
      





Pertama, kami menyertakan file header Vulkan dari LunarG SDK. File header stdexcepts



dan iostream



digunakan untuk penanganan kesalahan dan distribusi. File header cstdlib



menyediakan makro EXIT_SUCCESS



dan EXIT_FAILURE



.



Program itu sendiri dibungkus dalam kelas HelloTriangleApplication, di mana kita akan menyimpan objek Vulkan sebagai anggota pribadi kelas. Di sana kita juga akan menambahkan fungsi untuk menginisialisasi setiap objek yang dipanggil dari fungsi tersebut initVulkan



. Setelah itu, mari buat loop utama untuk rendering frame. Untuk melakukan ini, isi fungsi di mainLoop



mana loop akan dijalankan hingga jendela ditutup. Setelah menutup jendela dan keluar, mainLoop



sumber daya harus dilepaskan. Untuk melakukan ini, isi cleanup



.



Jika kesalahan kritis terjadi selama operasi, kami akan melempar pengecualian std::runtime_error



yang akan ditangkap dalam fungsi main



, dan deskripsi akan ditampilkan di std::cerr



. Salah satu kesalahan tersebut mungkin, misalnya, pesan bahwa ekstensi yang diperlukan tidak didukung. Untuk menangani banyak tipe pengecualian standar, kami menangkap yang lebih umum std::exception



.



Hampir setiap bab berikutnya akan menambahkan fungsi baru yang dipanggil dari initVulkan



, dan objek Vulkan baru yang perlu dirilis di cleanup



akhir program.



Pengelolaan sumber daya



Jika objek Vulkan tidak lagi diperlukan, objek tersebut harus dihancurkan. C ++ memungkinkan Anda untuk secara otomatis membatalkan alokasi sumber daya menggunakan RAII atau petunjuk cerdas yang disediakan oleh file header <memory>



. Namun, dalam tutorial ini kami memutuskan untuk menulis secara eksplisit kapan harus mengalokasikan dan membatalkan alokasi objek Vulkan. Bagaimanapun, ini adalah kekhasan pekerjaan Vulkan - untuk menjelaskan secara detail setiap operasi untuk menghindari kemungkinan kesalahan.



Setelah membaca tutorial, Anda dapat mengimplementasikan pengelolaan sumber daya otomatis dengan menulis kelas C ++ yang menerima objek Vulkan di konstruktor dan membebaskannya di destruktor. Anda juga dapat menerapkan penghapus Anda sendiri untuk std::unique_ptr



atau std::shared_ptr



, tergantung pada kebutuhan Anda. Konsep RAII direkomendasikan untuk program yang lebih besar, tetapi akan sangat membantu untuk mempelajarinya lebih lanjut.



Objek Vulkan dibuat langsung menggunakan fungsi seperti vkCreateXXX , atau dialokasikan melalui objek lain menggunakan fungsi seperti vkAllocateXXX . Setelah memastikan bahwa objek tidak sedang digunakan di mana pun, Anda harus memusnahkannya dengan vkDestroyXXX atau vkFreeXXX . Parameter untuk fitur ini biasanya bervariasi tergantung pada jenis objek, tapi ada satu parameter umum: pAllocator



. Ini adalah parameter opsional yang memungkinkan Anda menggunakan callback untuk alokasi memori khusus. Kami tidak membutuhkannya di manual, kami akan menyebarkannya sebagai argumen nullptr



.



Integrasi GLFW



Vulkan berfungsi dengan baik tanpa membuat jendela saat menggunakan rendering di luar layar, tetapi jauh lebih baik saat hasilnya terlihat di layar.

Pertama, ganti baris dengan yang #include <vulkan/vulkan.h>



berikut ini:



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





Tambahkan fungsi initWindow



dan tambahkan panggilannya dari metode run



sebelum panggilan lain. Kami akan menggunakan initWindow



GLFW untuk menginisialisasi dan membuat jendela.



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

private:
    void initWindow() {

    }
      
      





Panggilan pertama ke initWindow



harus berupa fungsi glfwInit()



yang menginisialisasi pustaka GLFW. GLFW awalnya dirancang untuk bekerja dengan OpenGL. Kita tidak membutuhkan konteks OpenGL, jadi tunjukkan bahwa kita tidak perlu membuatnya menggunakan panggilan berikut:



glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
      
      





Nonaktifkan sementara kemampuan untuk mengubah ukuran jendela, karena menangani situasi ini memerlukan pertimbangan terpisah:



glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
      
      





Tetap membuat jendela. Untuk melakukan ini, tambahkan anggota pribadi GLFWwindow* window;



dan inisialisasi jendela dengan:



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





Tiga parameter pertama menentukan lebar, tinggi dan judul jendela. Parameter keempat adalah opsional, ini memungkinkan Anda untuk menentukan monitor di mana jendela akan ditampilkan. Parameter terakhir khusus untuk OpenGL.



Alangkah baiknya menggunakan konstanta untuk lebar dan tinggi jendela, karena kita akan membutuhkan nilai ini di tempat lain. Tambahkan baris berikut sebelum definisi kelas HelloTriangleApplication



:



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





dan ganti panggilan untuk membuat jendela



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





Anda harus memiliki fungsi berikut initWindow



:



void initWindow() {
    glfwInit();

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

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


      
      





Mari kita gambarkan loop utama dalam metode mainLoop



untuk menjaga aplikasi tetap berjalan hingga jendela ditutup:



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





Kode ini seharusnya tidak menimbulkan pertanyaan apa pun. Ini menangani acara seperti menekan tombol X sebelum pengguna menutup jendela. Juga dari loop ini kita akan memanggil fungsi untuk membuat frame individual.



Setelah menutup jendela, kita perlu membebaskan sumber daya dan keluar dari GLFW. Pertama, mari tambahkan cleanup



kode berikut:



void cleanup() {
    glfwDestroyWindow(window);

    glfwTerminate();
}
      
      





Hasilnya, setelah memulai program, Anda akan melihat jendela dengan nama Vulkan



yang akan ditampilkan hingga program ditutup. Sekarang kita memiliki kerangka untuk bekerja dengan Vulkan, mari kita lanjutkan untuk membuat objek Vulkan pertama kita!



Kode C ++











Contoh







Instansiasi



Hal pertama yang perlu Anda lakukan adalah membuat instance untuk menginisialisasi perpustakaan. Instance adalah link antara program Anda dan library Vulkan, dan untuk membuatnya, Anda perlu memberikan beberapa informasi tentang program Anda kepada driver.



Tambahkan metode createInstance



dan panggil dari fungsi initVulkan



.



void initVulkan() {
    createInstance();
}
      
      





Tambahkan anggota instance ke kelas kami untuk memegang pegangan instance:



private:
VkInstance instance;
      
      





Sekarang kita perlu mengisi struktur khusus dengan informasi tentang program. Secara teknis, data bersifat opsional, namun ini memungkinkan pengemudi memperoleh informasi yang berguna untuk mengoptimalkan pekerjaan dengan program Anda. Struktur ini disebut 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;
}
      
      





Seperti yang disebutkan, banyak struktur di Vulkan memerlukan definisi tipe eksplisit dalam anggota sType . Selain itu, struktur ini, seperti banyak struktur lainnya, berisi elemen pNext



yang memungkinkan Anda memberikan informasi untuk ekstensi. Kami menggunakan inisialisasi nilai untuk mengisi struktur dengan nol.



Sebagian besar informasi di Vulkan diteruskan melalui struktur, jadi Anda perlu mengisi satu struktur lagi guna memberikan informasi yang cukup untuk membuat instance. Struktur berikut diperlukan, ini memberi tahu pengemudi ekstensi global dan lapisan validasi mana yang ingin kita gunakan. "Global" berarti ekstensi berlaku untuk seluruh program dan tidak untuk perangkat tertentu.



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





Dua parameter pertama tidak menimbulkan pertanyaan. Dua anggota berikutnya menentukan ekstensi global yang diperlukan. Seperti yang Anda ketahui, Vulkan API sepenuhnya tidak bergantung pada platform. Ini berarti Anda memerlukan ekstensi untuk berinteraksi dengan sistem jendela. GLFW memiliki fungsi built-in praktis yang mengembalikan daftar ekstensi yang diperlukan.



uint32_t glfwExtensionCount = 0;
const char** glfwExtensions;

glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

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





Dua anggota struktur terakhir menentukan lapisan validasi global mana yang akan disertakan. Kita akan membicarakannya lebih detail di bab berikutnya, jadi biarkan nilai-nilai ini kosong untuk saat ini.



createInfo.enabledLayerCount = 0;
      
      





Sekarang Anda telah melakukan semua yang diperlukan untuk membuat sebuah instance. Lakukan panggilan vkCreateInstance



:



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





Biasanya, parameter fungsi untuk membuat objek berada dalam urutan berikut:



  • Arahkan ke struktur dengan informasi yang diperlukan
  • Arahkan ke pengalokasi khusus
  • Arahkan ke variabel tempat deskriptor objek baru akan ditulis


Jika semuanya dilakukan dengan benar, deskriptor instance akan disimpan dalam instance . Hampir semua fungsi Vulkan mengembalikan nilai VkResult , yang bisa berupa VK_SUCCESS



kode kesalahan atau kode kesalahan. Kami tidak perlu menyimpan hasilnya untuk memastikan instance telah dibuat. Mari gunakan pemeriksaan sederhana:



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





Sekarang jalankan program untuk memverifikasi bahwa instance telah berhasil dibuat.



Memeriksa Ekstensi yang Didukung



Jika kita melihat dokumentasi Vulkan , kita dapat menemukan bahwa salah satu kode kesalahan yang mungkin ada adalah VK_ERROR_EXTENSION_NOT_PRESENT



. Kami cukup menentukan ekstensi yang diperlukan dan berhenti bekerja jika tidak didukung. Ini masuk akal untuk ekstensi utama seperti antarmuka sistem jendela, tetapi bagaimana jika kita ingin menguji kemampuan opsional?



Untuk mendapatkan daftar ekstensi yang didukung sebelum membuat instance, gunakan fungsi vkEnumerateInstanceExtensionProperties... Parameter pertama dari fungsi ini opsional, ini memungkinkan Anda untuk memfilter ekstensi dengan lapisan validasi tertentu, jadi kami akan membiarkannya kosong untuk saat ini. Selain itu, fungsi tersebut memerlukan penunjuk ke variabel di mana jumlah ekstensi akan ditulis dan penunjuk ke area memori tempat informasi tentang ekstensi harus ditulis.



Untuk mengalokasikan memori untuk menyimpan informasi ekstensi, Anda harus terlebih dahulu mengetahui jumlah ekstensi. Biarkan parameter terakhir kosong untuk meminta jumlah ekstensi:



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





Alokasikan array untuk menyimpan informasi ekstensi (jangan lupa tentang include <vector>



):



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





Anda sekarang dapat meminta informasi tentang ekstensi.



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





Setiap struktur VkExtensionProperties berisi nama dan versi ekstensi. Mereka dapat dicantumkan dengan loop for sederhana ( \t



berikut adalah tab indentasi):



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

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





Anda dapat menambahkan kode ini ke fungsi createInstance



untuk informasi lebih lanjut tentang dukungan Vulkan. Anda juga dapat mencoba membuat fungsi yang akan memeriksa apakah semua ekstensi yang dikembalikan oleh fungsi glfwGetRequiredInstanceExtensions



tersebut termasuk dalam daftar ekstensi yang didukung.





Pembersihan



VkInstance harus dihancurkan sebelum menutup program. Ini bisa dilakukancleanup



dengan menggunakan fungsi VkDestroyInstance :



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

    glfwDestroyWindow(window);

    glfwTerminate();
}
      
      





Parameter untuk fungsi vkDestroyInstance sudah cukup jelas. Seperti disebutkan di bab sebelumnya, fungsi alokasi dan dealokasi di Vulkan menerima petunjuk opsional ke pengalokasi khusus yang tidak kita gunakan dan kita teruskan nullptr



. Semua resource Vulkan lainnya harus dibersihkan sebelum instance dihancurkan.



Sebelum melanjutkan ke langkah yang lebih kompleks, kita perlu menyiapkan lapisan validasi untuk kemudahan debugging.



Kode C ++



All Articles