
Данный цикл статей будет посвящен выводу цветного треугольника в Vulkan c использованием библиотеки ash. Я надеюсь, что читатель имел дело с какими либо графическими api, потому что Vulkan - это сложно и очень много кода, но так же и возможность использования крутых фич от GPU
В данной статье будет раскрыто создание vulkan device
- Проверка версий Vulkan 
- Создание AppInfo<'_> 
- Проверка поддерживаемых слоев 
- Проверка поддерживаемых расширений 
- Создание Instance 
- Просмотр доступных устройств и выбор устройства 
- Получение свойств выбранного устройства 
- Получение свойств семейств очередей 
- Получение поддерживаемых слоев и расширений 
- Создание логического устройства 
Для начала создадим пустой проект на rust
cargo new --bin CryEngineДобавим зависимость
cargo add ashuse ash::Entry;
use ash::vk::*;
fn main() {
    /*
       Загружаем функции Vulkan, мы можем подгрузить их динамически с помощью load или
       статически с помощью linked функции, но для этого потребуется добавить features 
       для ash
    */
    let entry = unsafe { Entry::load().unwrap() };
    // Получаем последнюю доступную версию VK_API_VERSION
    let version = unsafe { 
        entry.try_enumerate_instance_version()
       .expect("Error enumerate instance version") 
    };
    let api_version = match version {
        // Тут уже будет поновее версия 1.1+
        Some(version) => {
            version
        },
        // Если доступна только первая версия
        None => {
            API_VERSION_1_0
        }
    };
}
Дальше нам нужно заполнить информацию о нашем приложении, заполнив структуру ApplicationInfo<'_>, она понадобится для создания Vulkan Instance
use ash::Entry;
use ash::vk::*;
fn main() {
    let entry = unsafe { Entry::load().unwrap() };
    // Получаем максимально доступную версию
    let version = unsafe { 
        entry.try_enumerate_instance_version()
       .expect("Error enumerate instance version") 
    };
    // Выбор версии
    let api_version = match version {
        Some(version) => {
            version
        },
        None => {
            API_VERSION_1_0
        }
    };
    /* 
        Самая важная строчка здесь - это версия vulkan, которую мы будем использовать, 
        остальное можете на свое усмотрение заполнить. Буква "с" перед строкой автоматически конвертирует 
        её в CStr строку
    */  
    let app_info = ApplicationInfo::default()
        .application_name(c"Far Cry 9")
        .engine_name(c"Cry Engine")
        .engine_version(12)
        .application_version(0)
        .api_version(api_version);
}
Layers and Extensions
В Vulkan для отлова ошибок используются слои(Layers). Layers - это опциональные компоненты Vulkan API, которые перехватывают вызовы от приложения до драйверов, они могут проверять правильно ли используется Vulkan. Подробнее можно почитать в спецификации: https://docs.vulkan.org/guide/latest/layers.html
use ash::Entry;
use ash::vk::*;
fn main() {
    let entry = unsafe { Entry::load().unwrap() };
    let version = unsafe { entry.try_enumerate_instance_version().expect("Error enumerate instance version")};
    let api_version = match version {
        Some(version) => {
            version
        },
        None => {
            API_VERSION_1_0
        }
    };
    let app_info = ApplicationInfo::default()
        .application_name(c"Far Cry 9")
        .engine_name(c"Cry Engine")
        .engine_version(12)
        .application_version(0)
        .api_version(api_version);
    let layers = unsafe { entry.enumerate_instance_layer_properties().expect("Error enumerate layers") };
    let extensions = unsafe {
         entry.enumerate_instance_extension_properties(None).expect("Error enumerate instance properties")
    };
    println!("-----------Layers--------------");
    for i in &layers {
        println!("{:?}", i.layer_name_as_c_str().unwrap_or(&c"None"));
    }
}У меня после вызова данного кода будут доступны такие слои:
"VK_LAYER_AMD_switchable_graphics"
"VK_LAYER_VALVE_steam_overlay"
"VK_LAYER_VALVE_steam_fossilize"
"VK_LAYER_OBS_HOOK"
"VK_LAYER_RENDERDOC_Capture"
"VK_LAYER_LUNARG_api_dump"
"VK_LAYER_LUNARG_gfxreconstruct"
"VK_LAYER_KHRONOS_synchronization2"
"VK_LAYER_KHRONOS_validation"
"VK_LAYER_LUNARG_monitor"
"VK_LAYER_LUNARG_screenshot"
"VK_LAYER_KHRONOS_profiles"
"VK_LAYER_KHRONOS_shader_object"
"VK_LAYER_LUNARG_crash_diagnostic"Нам потребуется поддержка лишь одного слоя - VK_LAYER_KHRONOS_validation, именно она будет отслеживать неправильное использование Vulkan
Так же нам потребуются extensions(Расширения), они добавляют новые возможности в Vulkan
use ash::Entry;
use ash::vk::*;
fn main() {
    let entry = unsafe { Entry::load().unwrap() };
    let version = unsafe { entry.try_enumerate_instance_version().expect("Error enumerate instance version")};
    let api_version = match version {
        Some(version) => {
            version
        },
        None => {
            API_VERSION_1_0
        }
    };
    let app_info = ApplicationInfo::default()
        .application_name(c"Far Cry 9")
        .engine_name(c"Cry Engine")
        .engine_version(12)
        .application_version(0)
        .api_version(api_version);
    let layers = unsafe { entry.enumerate_instance_layer_properties().expect("Error enumerate layers") };
    let extensions = unsafe {
         /// Мы передаем None, чтобы узнать о глобальных расширениях, так же можно узнать
         /// об расширений конкретного слоя, но нам это не нужно
         entry.enumerate_instance_extension_properties(None).expect("Error enumerate instance properties")
    };
    println!("-----------Layers--------------");
    for i in &layers {
        println!("{:?}", i.layer_name_as_c_str().unwrap_or(&c"None"));
    }
    println!("------------Extensions------------");
    for i in &extensions {
        println!("{:?}", i.extension_name_as_c_str().unwrap_or(&c"None"));
    }
    println!("---------------------------------");
}Я использую Window OS, поэтому буду видеть расширения связанные с этой операционной системой
"VK_KHR_device_group_creation"
"VK_KHR_external_fence_capabilities"
"VK_KHR_external_memory_capabilities"
"VK_KHR_external_semaphore_capabilities"
"VK_KHR_get_physical_device_properties2"
"VK_KHR_get_surface_capabilities2"
"VK_KHR_surface"
"VK_KHR_win32_surface"
"VK_EXT_debug_report"
"VK_EXT_debug_utils"
"VK_EXT_swapchain_colorspace"
"VK_KHR_portability_enumeration"
"VK_LUNARG_direct_driver_loading"
Из всех этих расширений я выделю только 4 нужных расширения:
- "VK_KHR_surface" - Позволит нам выводить изображение на экран. Vulkan может рендерить в свой FrameBuffre не задействую само окно ос 
- "VK_KHR_win32_surface" - Расширение для поддержки поверхности Windows 
- "VK_EXT_debug_report" - Расширение для отлова ошибок 
- "VK_EXT_debug_utils" - Еще одно расширение для отлова ошибок 
Теперь заполним InstanceCreateInfo<'_> нужными нам расширениями и слоями
use ash::Entry;
use ash::vk::*;
fn main() {
    let entry = unsafe { Entry::load().unwrap() };
    let version = unsafe { entry.try_enumerate_instance_version().expect("Error enumerate instance version") };
    let api_version = match version {
        Some(version) => {
            version
        },
        None => {
            API_VERSION_1_0
        }
    };
    let app_info = ApplicationInfo::default()
        .application_name(c"Far Cry 9")
        .engine_name(c"Cry Engine")
        .engine_version(12)
        .application_version(0)
        .api_version(api_version);
    let layers = unsafe { entry.enumerate_instance_layer_properties().expect("Error enumerate layers") };
    let extensions = unsafe {
         entry.enumerate_instance_extension_properties(None).expect("Error enumerate instance properties")
    };
     println!("------------Layers---------------");
    for i in &layers {
        println!("{:?}", i.layer_name_as_c_str().unwrap_or(&c"None"));
    }
    println!("------------Extensions------------");
    for i in &extensions {
        println!("{:?}", i.extension_name_as_c_str().unwrap_or(&c"None"));
    }
    println!("---------------------------------");
    /// Берем только нужные расширения!
    let required_extensions = [
        CStr::from_bytes_with_nul(b"VK_KHR_surface\0").unwrap().as_ptr(),
        CStr::from_bytes_with_nul(b"VK_KHR_win32_surface\0").unwrap().as_ptr(),
        CStr::from_bytes_with_nul(b"VK_EXT_debug_report\0").unwrap().as_ptr(),
        CStr::from_bytes_with_nul(b"VK_EXT_debug_utils\0").unwrap().as_ptr(),
    ];
    /// Только необходимые слои!
    let required_layers = [
        CStr::from_bytes_with_nul(b"VK_LAYER_KHRONOS_validation\0").unwrap().as_ptr(),
    ];
    let instance_info = InstanceCreateInfo::default()
        .enabled_extension_names(&required_extensions)
        .enabled_layer_names(&required_layers)
        .application_info(&app_info);
    let instance = unsafe {
        // Вместо None можем передать свою callback функцию
        entry.create_instance(&instance_info, None)
        .map_err(|e| format!("Error create insatnce with errror: {}", e))
        .unwrap()
    };
}
Device(Логическое устройство)
Device - это абстракция над физическим устройством. С помощью device уже можно будет создавать буферы, изображения, текстуры, а так же отправлять команды на GPU и ждать синхронизации. Логическое устройство создается похожим образом на Instance, так же нужно запросить слои и расширения для него, но будет чуть больше того, что мы должны передать для его создания.
Сначала просмотрим, какие вообще устройства у нас есть и выберем дискретную(встроенную) GPU
use ash::Entry;
use ash::vk::*;
fn main() {
    let entry = unsafe { Entry::load().unwrap() };
    let version = unsafe { entry.try_enumerate_instance_version().expect("Error enumerate instance version") };
    let api_version = match version {
        Some(version) => {
            version
        },
        None => {
            API_VERSION_1_0
        }
    };
    let app_info = ApplicationInfo::default()
        .application_name(c"Far Cry 9")
        .engine_name(c"Cry Engine")
        .engine_version(12)
        .application_version(0)
        .api_version(api_version);
    let layers = unsafe { entry.enumerate_instance_layer_properties().expect("Error enumerate layers") };
    let extensions = unsafe {
         entry.enumerate_instance_extension_properties(None).expect("Error enumerate instance properties")
    };
    println!("-----------Layers--------------");
    for i in &layers {
        println!("{:?}", i.layer_name_as_c_str().unwrap_or(&c"None"));
    }
    println!("------------Extensions------------");
    for i in &extensions {
        println!("{:?}", i.extension_name_as_c_str().unwrap_or(&c"None"));
    }
    println!("---------------------------------");
    let extension_names = extensions
        .iter()
        .map(|x| x.extension_name.as_ptr())
        .collect::<Vec<*const i8>>();
    let layer_names = layers
        .iter()
        .map(|x| x.layer_name.as_ptr())
        .collect::<Vec<*const i8>>();
    let instance_info = InstanceCreateInfo::default()
        .enabled_extension_names(&extension_names)
        .enabled_layer_names(&layer_names)
        .application_info(&app_info);
    let instance = unsafe {
        entry.create_instance(&instance_info, None)
        .map_err(|e| format!("Error create insatnce with errror: {}", e))
        .unwrap()
    };
    //  Получаем все доступные устройства
    let phys_devs = unsafe { instance.enumerate_physical_devices().expect("Error enumerate Physical Devices")};
    let mut phsy_dev_index = 0;
    println!("-------------Available GPU-------------------");
    for (index, i) in phys_devs.iter().enumerate() {
        // Запрашиваем свойства физического устройства
        let prop = unsafe { instance.get_physical_device_properties(*i) };
        // Выводим общую информацию
        println!("DEVICE_NAME:        {:?}", prop.device_name_as_c_str().unwrap_or(&c"Undefined"));
        println!("VULKAN_API_VERSION: {:?}", prop.api_version);
        println!("DEVICE_TYPE:        {:?}", prop.device_type);
        println!("DRIVER VERSION:     {:?}", prop.driver_version);
        if prop.device_type == PhysicalDeviceType::INTEGRATED_GPU {
            phsy_dev_index = index;
            break;
        }
    }
}
QueueFamily
Перед тем, как собрать полную информацию о нашем GPU, я бы хотел рассказать о такой вещи, как Queue Familes(Семейства очередей). У GPU есть так называемые Queue(Очереди) у которых есть свойства. Одни могут вычислять, другие передавать данные с CPU на GPU, другие могут непосредственно работать с графикой. Семейства очередей - это очереди с одними и теми же свойствами. Перед созданием логического устройства нужно будет указать какие семейства очередей будет использовать логическое устройство в будущем.
use std::ffi::CStr;
use ash::Entry;
use ash::vk::*;
fn main() {
    let entry = unsafe { Entry::load().unwrap() };
    let version = unsafe { entry.try_enumerate_instance_version().expect("Error enumerate instance version") };
    let api_version = match version {
        Some(version) => {
            version
        },
        None => {
            API_VERSION_1_0
        }
    };
    let app_info = ApplicationInfo::default()
        .application_name(c"Far Cry 9")
        .engine_name(c"Cry Engine")
        .engine_version(12)
        .application_version(0)
        .api_version(api_version);
    let layers = unsafe { entry.enumerate_instance_layer_properties().expect("Error enumerate layers") };
    let extensions = unsafe {
         entry.enumerate_instance_extension_properties(None).expect("Error enumerate instance properties")
    };
    println!("-----------Layers--------------");
    for i in &layers {
        println!("{:?}", i.layer_name_as_c_str().unwrap_or(&c"None"));
    }
    println!("------------Extensions------------");
    for i in &extensions {
        println!("{:?}", i.extension_name_as_c_str().unwrap_or(&c"None"));
    }
    println!("---------------------------------");
    let required_extensions = [
        CStr::from_bytes_with_nul(b"VK_KHR_surface\0").unwrap().as_ptr(),
        CStr::from_bytes_with_nul(b"VK_KHR_win32_surface\0").unwrap().as_ptr(),
        CStr::from_bytes_with_nul(b"VK_EXT_debug_report\0").unwrap().as_ptr(),
        CStr::from_bytes_with_nul(b"VK_EXT_debug_utils\0").unwrap().as_ptr(),
    ];
    let required_layers = [
        CStr::from_bytes_with_nul(b"VK_LAYER_KHRONOS_validation\0").unwrap().as_ptr(),
    ];
    let instance_info = InstanceCreateInfo::default()
        .enabled_extension_names(&required_extensions)
        .enabled_layer_names(&required_layers)
        .application_info(&app_info);
    let instance = unsafe {
        entry.create_instance(&instance_info, None)
        .map_err(|e| format!("Error create insatnce with errror: {}", e))
        .unwrap()
    };
    let phys_devs = unsafe { instance.enumerate_physical_devices().expect("Error enumerate Physical Devices") };
    let mut phys_dev_index = 0;
    println!("-------------Avaliable GPU-------------------");
    for (index, i) in phys_devs.iter().enumerate() {
        let prop = unsafe { instance.get_physical_device_properties(*i) };
        println!("DEVICE_NAME:        {:?}", prop.device_name_as_c_str().unwrap_or(&c"Undefined"));
        println!("VULKAN_API_VERSION: {:?}", prop.api_version);
        println!("DEVICE_TYPE:        {:?}", prop.device_type);
        println!("DRIVER VERSION:     {:?}", prop.driver_version);
        if prop.device_type == PhysicalDeviceType::INTEGRATED_GPU {
            phys_dev_index = index;
            break;
        }
    }
    let phys_dev = phys_devs[phys_dev_index];
    /// Информацию о памяти
    let memory_prop = unsafe { instance.get_physical_device_memory_properties(phys_dev) };
    /// Информация о семействах очередей
    let queue_family_prop = unsafe { instance.get_physical_device_queue_family_properties(phys_dev) };
    /// Общая информация о GPU
    let phys_prop = unsafe { instance.get_physical_device_properties(phys_dev) };
    // Сохраняем информацию в свою структуру
    struct QueueFamilyInfo {
        queue_family_index: usize,
        queue_prop: QueueFamilyProperties
    }
    let mut queue_infos = vec![];
    for (index, i) in queue_family_prop.iter().enumerate() {
        println!("Queue Family: {}: Queue Count: {:?}, Flags: {:?}", index, i.queue_count, i.queue_flags);
        queue_infos.push(QueueFamilyInfo {
            queue_family_index: index,
            queue_prop: *i
        });
    }
}
У меня вывод будет такой:
Queue Family: 0: Queue Count: 1, Flags: GRAPHICS | COMPUTE | TRANSFER | SPARSE_BINDING
Queue Family: 1: Queue Count: 2, Flags: COMPUTE | TRANSFER | SPARSE_BINDING
Queue Family: 2: Queue Count: 1, Flags: TRANSFER | SPARSE_BINDINGМожет быть как одно семейство поддерживающее вообще все операции, так и семейства заточенные под 1-2 операции, они считаются более быстрыми, чем общие.
Priority
Так же помимо информации о семействах нужно передать приоритет каждой очереди в семействе, но можно просто упростить и сделать все очереди равными. Приоритеты нужны, чтобы драйвер мог понять, какие операции предпочтительнее выполнить первыми, а какие можно позже.
use std::ffi::CStr;
use ash::Entry;
use ash::vk::*;
fn main() {
    let entry = unsafe { Entry::load().unwrap() };
    let version = unsafe { entry.try_enumerate_instance_version().expect("Error enumerate instance version") };
    let api_version = match version {
        Some(version) => {
            version
        },
        None => {
            API_VERSION_1_0
        }
    };
    let app_info = ApplicationInfo::default()
        .application_name(c"Far Cry 9")
        .engine_name(c"Cry Engine")
        .engine_version(12)
        .application_version(0)
        .api_version(api_version);
    let layers = unsafe { entry.enumerate_instance_layer_properties().expect("Error enumerate layers") };
    let extensions = unsafe {
         entry.enumerate_instance_extension_properties(None).expect("Error enumerate instance properties")
    };
    println!("-----------Layers--------------");
    for i in &layers {
        println!("{:?}", i.layer_name_as_c_str().unwrap_or(&c"None"));
    }
    println!("------------Extensions------------");
    for i in &extensions {
        println!("{:?}", i.extension_name_as_c_str().unwrap_or(&c"None"));
    }
    println!("---------------------------------");
    let required_extensions = [
        CStr::from_bytes_with_nul(b"VK_KHR_surface\0").unwrap().as_ptr(),
        CStr::from_bytes_with_nul(b"VK_KHR_win32_surface\0").unwrap().as_ptr(),
        CStr::from_bytes_with_nul(b"VK_EXT_debug_report\0").unwrap().as_ptr(),
        CStr::from_bytes_with_nul(b"VK_EXT_debug_utils\0").unwrap().as_ptr(),
    ];
    let required_layers = [
        CStr::from_bytes_with_nul(b"VK_LAYER_KHRONOS_validation\0").unwrap().as_ptr(),
    ];
    let instance_info = InstanceCreateInfo::default()
        .enabled_extension_names(&required_extensions)
        .enabled_layer_names(&required_layers)
        .application_info(&app_info);
    let instance = unsafe {
        entry.create_instance(&instance_info, None)
        .map_err(|e| format!("Error create insatnce with errror: {}", e))
        .unwrap()
    };
    let phys_devs = unsafe { instance.enumerate_physical_devices().expect("Error enumerate Physical Devices") };
    let mut phys_dev_index = 0;
    println!("-------------Avaliable GPU-------------------");
    for (index, i) in phys_devs.iter().enumerate() {
        let prop = unsafe { instance.get_physical_device_properties(*i) };
        println!("DEVICE_NAME:        {:?}", prop.device_name_as_c_str().unwrap_or(&c"Undefined"));
        println!("VULKAN_API_VERSION: {:?}", prop.api_version);
        println!("DEVICE_TYPE:        {:?}", prop.device_type);
        println!("DRIVER VERSION:     {:?}", prop.driver_version);
        if prop.device_type == PhysicalDeviceType::INTEGRATED_GPU {
            phys_dev_index = index;
            break;
        }
    }
    let phys_dev = phys_devs[phys_dev_index];
    let memory_prop = unsafe { instance.get_physical_device_memory_properties(phys_dev) };
    let queue_family_prop = unsafe { instance.get_physical_device_queue_family_properties(phys_dev) };
    let phys_prop = unsafe { instance.get_physical_device_properties(phys_dev) };
    struct QueueFamilyInfo {
        queue_family_index: usize,
        queue_prop: QueueFamilyProperties
    }
    let mut queue_infos = vec![];
    for (index, i) in queue_family_prop.iter().enumerate() {
        println!("Queue Family: {}: Queue Count: {:?}, Flags: {:?}", index, i.queue_count, i.queue_flags);
        queue_infos.push(QueueFamilyInfo {
            queue_family_index: index,
            queue_prop: *i
        });
    }
    // Устанавливаем приоритет одинаковым для всех очередей в семействе
    let priority = [1.0f32];
    let mut queue_family_infos = vec![];
    for i in queue_infos {
        let device_queue_info = DeviceQueueCreateInfo::default()
            .queue_family_index(i.queue_family_index as u32)
            .queue_priorities(&priority);
        queue_family_infos.push(device_queue_info)
    }
}
Device Extensions and Layers
У устройства нам понадобится лишь одно расширение - это поддержка Swapchain(Цепочка обмена)
use std::ffi::CStr;
use ash::Entry;
use ash::vk::*;
fn main() {
    let entry = unsafe { Entry::load().unwrap() };
    let version = unsafe { entry.try_enumerate_instance_version().expect("Error enumerate instance version") };
    let api_version = match version {
        Some(version) => {
            version
        },
        None => {
            API_VERSION_1_0
        }
    };
    let app_info = ApplicationInfo::default()
        .application_name(c"Far Cry 9")
        .engine_name(c"Cry Engine")
        .engine_version(12)
        .application_version(0)
        .api_version(api_version);
    let layers = unsafe { entry.enumerate_instance_layer_properties().expect("Error enumerate layers") };
    let extensions = unsafe {
         entry.enumerate_instance_extension_properties(None).expect("Error enumerate instance properties")
    };
    println!("-----------Layers--------------");
    for i in &layers {
        println!("{:?}", i.layer_name_as_c_str().unwrap_or(&c"None"));
    }
    println!("------------Extensions------------");
    for i in &extensions {
        println!("{:?}", i.extension_name_as_c_str().unwrap_or(&c"None"));
    }
    println!("---------------------------------");
    let required_extensions = [
        CStr::from_bytes_with_nul(b"VK_KHR_surface\0").unwrap().as_ptr(),
        CStr::from_bytes_with_nul(b"VK_KHR_win32_surface\0").unwrap().as_ptr(),
        CStr::from_bytes_with_nul(b"VK_EXT_debug_report\0").unwrap().as_ptr(),
        CStr::from_bytes_with_nul(b"VK_EXT_debug_utils\0").unwrap().as_ptr(),
    ];
    let required_layers = [
        CStr::from_bytes_with_nul(b"VK_LAYER_KHRONOS_validation\0").unwrap().as_ptr(),
    ];
    let instance_info = InstanceCreateInfo::default()
        .enabled_extension_names(&required_extensions)
        .enabled_layer_names(&required_layers)
        .application_info(&app_info);
    let instance = unsafe {
        entry.create_instance(&instance_info, None)
        .map_err(|e| format!("Error create insatnce with errror: {}", e))
        .unwrap()
    };
    let phys_devs = unsafe { instance.enumerate_physical_devices().expect("Error enumerate Physical Devices") };
    let mut phys_dev_index = 0;
    println!("-------------Avaliable GPU-------------------");
    for (index, i) in phys_devs.iter().enumerate() {
        let prop = unsafe { instance.get_physical_device_properties(*i) };
        println!("DEVICE_NAME:        {:?}", prop.device_name_as_c_str().unwrap_or(&c"Undefined"));
        println!("VULKAN_API_VERSION: {:?}", prop.api_version);
        println!("DEVICE_TYPE:        {:?}", prop.device_type);
        println!("DRIVER VERSION:     {:?}", prop.driver_version);
        if prop.device_type == PhysicalDeviceType::INTEGRATED_GPU {
            phys_dev_index = index;
            break;
        }
    }
    let phys_dev = phys_devs[phys_dev_index];
    let memory_prop = unsafe { instance.get_physical_device_memory_properties(phys_dev) };
    let queue_family_prop = unsafe { instance.get_physical_device_queue_family_properties(phys_dev) };
    let phys_prop = unsafe { instance.get_physical_device_properties(phys_dev) };
    struct QueueFamilyInfo {
        queue_family_index: usize,
        queue_prop: QueueFamilyProperties
    }
    let mut queue_infos = vec![];
    for (index, i) in queue_family_prop.iter().enumerate() {
        println!("Queue Family: {}: Queue Count: {:?}, Flags: {:?}", index, i.queue_count, i.queue_flags);
        queue_infos.push(QueueFamilyInfo {
            queue_family_index: index,
            queue_prop: *i
        });
    }
    let priority = [1.0f32];
    let mut queue_family_infos = vec![];
    for i in queue_infos {
        let device_queue_info = DeviceQueueCreateInfo::default()
            .queue_family_index(i.queue_family_index as u32)
            .queue_priorities(&priority);
        queue_family_infos.push(device_queue_info)
    }
    let extensions = unsafe { instance.enumerate_device_extension_properties(phys_dev).expect("Error enumerate device extensions") };
    println!("------------Device Extensions---------------------");
    for i in &extensions {
        println!("{:?}", i.extension_name_as_c_str().unwrap_or(&c"None"));
    }
    // Единственное расширение которое нам необходимо!
    let required_extesions = [
        CStr::from_bytes_with_nul(b"VK_KHR_swapchain\0").unwrap().as_ptr()
    ];
    let layers = unsafe { 
        instance.enumerate_device_layer_properties(phys_dev)
        .expect("Error enumerate device layers")
    };
    println!("------------Device Layers---------------------");
    for i in &layers {
        println!("{:?}", i.layer_name_as_c_str().unwrap_or(&c"None"));
    }
    println!("-------------------------------------------------");
    // Пустой features
    let features = PhysicalDeviceFeatures::default();
    // Заполняем старыми значениями
    let device_info = DeviceCreateInfo::default()
        .enabled_features(&features)
        .queue_create_infos(&queue_family_infos)
        .enabled_extension_names(&required_extesions);
    // Создаем устройство
    let device = unsafe {
        instance.create_device(phys_dev, &device_info, None).expect("Error create device")
    };
}
На этом всё! Постепенно будут выходить и новые статьи. Надеюсь, тех, кто интересуется графикой, не отпугнул этот объём - да, здесь куда больше действий, чем в старых добрых glBegin/glEnd, но зато можно прочувствовать полный контроль над GPU. Взять её за жабры и сделать ровно то, что ты хочешь.  
Полезные ссылки:
- https://github.com/adrien-ben/vulkan-tutorial-rs - Хороший туториал на Rust 
- https://vulkan-tutorial.com/ - Самый проверенный туториал, но используется язык C++ 
- https://github.com/ash-rs/ash/tree/master/ash-examples - официальные примеры от создателей - ash
- https://github.com/EmbarkStudios/kajiya - Графический движок, написан на Rust бывшим программистом Frostbite 
- https://github.com/olejaaaaaaaa/VulkanExamples - Ссылка на полный код туториал 
 
          