ashen-aetna-ko

잿빛 에트나 (Ashen Aetna)

— 재 덮인 화산 위에서 녹슨 듯 비틀거리며

(3D 그래픽스, Rust, Vulkan, ash에 대한/속의/관한/함께하는 튜토리얼)

큐 (Queues)

큐는 우리가 명령(command)을 제출하는 곳입니다. 어떤 의미에서는 우리의 그래픽스 명령들이 줄을 서서 기다리는 장소라고 할 수 있습니다. 큐 패밀리(Queue families)는 이러한 큐들의 모음이며, “특화된 기능”에 따라 그룹화됩니다. 그래픽스 명령을 위한 큐, 전송(transfer) 작업을 위한 큐, 또는 컴퓨팅(computing)을 위한 큐가 있을 수 있습니다. 어떤 큐 패밀리를 사용할 수 있는지는 물리 장치(physical device)의 속성이며, 이는 해당 장치를 선택하는 데에도 영향을 미칠 수 있습니다. 우리는 그래픽스 기능이 있는 큐와 전송 작업에 적합한 큐를 하나씩 선택할 것입니다. 전송용으로는 전송 전용 큐 패밀리를 선호하는데, 이를 통해 더 빠른 특화된 하드웨어를 사용할 수 있기를 기대하기 때문입니다. 하지만 그냥 같은 큐를 사용해도 무방합니다. 물론, 우리는 실제로 큐를 포함하는 큐 패밀리만 고려할 것입니다…

    let queuefamilyproperties =
        unsafe { instance.get_physical_device_queue_family_properties(physical_device) };
    dbg!(&queuefamilyproperties);
    let qfamindices = {
        let mut found_graphics_q_index = None;
        let mut found_transfer_q_index = None;
        for (index, qfam) in queuefamilyproperties.iter().enumerate() {
            if qfam.queue_count > 0 && qfam.queue_flags.contains(vk::QueueFlags::GRAPHICS)
            {
                found_graphics_q_index = Some(index as u32);
            }
            if qfam.queue_count > 0 && qfam.queue_flags.contains(vk::QueueFlags::TRANSFER) {
                // 전송 큐 인덱스가 아직 없거나,
                // 현재 큐가 그래픽스 기능을 포함하지 않는 경우 (전송 전용일 가능성이 높음)
                if found_transfer_q_index.is_none()
                    || !qfam.queue_flags.contains(vk::QueueFlags::GRAPHICS)
                {
                    found_transfer_q_index = Some(index as u32);
                }
            }
        }
        (
            found_graphics_q_index.unwrap(),
            found_transfer_q_index.unwrap(),
        )
    };

이 큐 패밀리 인덱스들을 찾았으니, 다음으로 물리 장치의 주 인터페이스 역할을 할 논리 장치(logical device)를 생성합니다.

    let priorities = [1.0f32];
    let queue_infos = [
        vk::DeviceQueueCreateInfo::builder()
            .queue_family_index(qfamindices.0)
            .queue_priorities(&priorities)
            .build(),
        vk::DeviceQueueCreateInfo::builder()
            .queue_family_index(qfamindices.1)
            .queue_priorities(&priorities)
            .build(),
    ];
    let device_create_info = vk::DeviceCreateInfo::builder()
        .queue_create_infos(&queue_infos)
        .enabled_layer_names(&layer_name_pointers);
    let logical_device =
        unsafe { instance.create_device(physical_device, &device_create_info, None)? };
    let graphics_queue = unsafe { logical_device.get_device_queue(qfamindices.0, 0) };
    let transfer_queue = unsafe { logical_device.get_device_queue(qfamindices.1, 0) };

    unsafe {
        logical_device.destroy_device(None);
        debug_utils.destroy_debug_utils_messenger(utils_messenger, None);
        instance.destroy_instance(None)
    };
    Ok(())

잊어버리기 전에 말씀드리자면, 우리는 마지막에 이 장치도 파괴합니다.

DeviceCreateInfo를 살펴봅시다: “enabled_layer_names” 필드에는 우리가 검증 레이어(validation layers)를 사용한다는 정보가 주어집니다. 이건 중요하지 않으며, 이 필드는 (최신 구현에서는) 무시될 것입니다. 주로 호환성을 위해 설정하는 것입니다. 그 외에는 큐에 대한 정보를 전달합니다. 어떤 큐 패밀리를 사용할지(앞서 찾은 것들), 해당 패밀리에서 몇 개의 큐가 필요한지, 그리고 그들의 우선순위는 어떠해야 하는지를 명시합니다. 우선순위(Priorities)는 0.0에서 1.0 사이의 값을 가지며, 값이 높을수록 우선순위가 높다는 것, 즉 우선적으로 처리된다는 의미입니다. 큐의 개수는 명시적으로 지정되지는 않습니다. 만약 우리가 이 패밀리 중 하나에 대해 큐 세 개를 원했다면, 우선순위를 더 많이 설정했을 것입니다: let priorities = [1.0f32,1.0f32,0.5f32] 와 같이 말이죠. 또한, DeviceCreationInfoBuilder에서 .build()를 사용하는데, 제가 그렇게 하지 말라고 경고했음에도 불구하고 말입니다. 이 정보들이 배열에 수집되기 때문에 필요한 것 같습니다. Deref가 배열 내부까지 도달하지 못해 타입이 맞지 않게 되는 것 같습니다. 제가 뭘 하는지 잘 알지 못하는 상태에서 하지 말라고 경고했었죠. 저도 잘 모르지만, ash 저장소의 예제에서도 비슷하게 사용되므로 괜찮을 것입니다.

정말로 괜찮지 않은 경우는 두 DeviceQueueCreateInfo가 모두 동일한 큐 패밀리를 참조하는 경우입니다 (그래픽스 큐와 전송 큐의 인덱스가 같은 경우). 이럴 때는 하나의 DeviceQueueCreateInfo만 사용해야 합니다 (그리고 거기서 큐 두 개를 요청해야 할 수도 있습니다). 연습 문제: 이 경우를 처리해 보세요. 저는 하고 싶지 않네요.

계속 진행해서: 장치를 생성하면 큐도 함께 생성되지만, 나중에 참조할 수 있도록 큐의 핸들(handle)을 여전히 요청해야 합니다. 큐 패밀리의 인덱스와 해당 패밀리 내 큐의 인덱스를 제공합니다.

use ash::version::DeviceV1_0;
use ash::version::EntryV1_0;
use ash::version::InstanceV1_0;
use ash::vk;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let eventloop = winit::event_loop::EventLoop::new();
    let window = winit::window::Window::new(&eventloop)?;

    let entry = ash::Entry::new()?;
    let enginename = std::ffi::CString::new("UnknownGameEngine").unwrap();
    let appname = std::ffi::CString::new("The Black Window").unwrap();
    let app_info = vk::ApplicationInfo::builder()
        .application_name(&appname)
        .application_version(vk::make_version(0, 0, 1))
        .engine_name(&enginename)
        .engine_version(vk::make_version(0, 42, 0))
        .api_version(vk::make_version(1, 0, 106));
    let layer_names: Vec<std::ffi::CString> =
        vec![std::ffi::CString::new("VK_LAYER_KHRONOS_validation").unwrap()];
    let layer_name_pointers: Vec<*const i8> = layer_names
        .iter()
        .map(|layer_name| layer_name.as_ptr())
        .collect();
    let extension_name_pointers: Vec<*const i8> =
        vec![ash::extensions::ext::DebugUtils::name().as_ptr()];
    let mut debugcreateinfo = vk::DebugUtilsMessengerCreateInfoEXT::builder()
        .message_severity(
            vk::DebugUtilsMessageSeverityFlagsEXT::WARNING
                | vk::DebugUtilsMessageSeverityFlagsEXT::VERBOSE
                | vk::DebugUtilsMessageSeverityFlagsEXT::INFO
                | vk::DebugUtilsMessageSeverityFlagsEXT::ERROR,
        )
        .message_type(
            vk::DebugUtilsMessageTypeFlagsEXT::GENERAL
                | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE
                | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION,
        )
        .pfn_user_callback(Some(vulkan_debug_utils_callback));

    let instance_create_info = vk::InstanceCreateInfo::builder()
        .push_next(&mut debugcreateinfo)
        .application_info(&app_info)
        .enabled_layer_names(&layer_name_pointers)
        .enabled_extension_names(&extension_name_pointers);
    let instance = unsafe { entry.create_instance(&instance_create_info, None)? };
    let debug_utils = ash::extensions::ext::DebugUtils::new(&entry, &instance);
    let utils_messenger =
        unsafe { debug_utils.create_debug_utils_messenger(&debugcreateinfo, None)? };

    let phys_devs = unsafe { instance.enumerate_physical_devices()? };
    let (physical_device, physical_device_properties) = {
        let mut chosen = None;
        for p in phys_devs {
            let properties = unsafe { instance.get_physical_device_properties(p) };
            if properties.device_type == vk::PhysicalDeviceType::DISCRETE_GPU {
                chosen = Some((p, properties));
            }
        }
        chosen.unwrap()
    };
    let queuefamilyproperties =
        unsafe { instance.get_physical_device_queue_family_properties(physical_device) };
    let qfamindices = {
        let mut found_graphics_q_index = None;
        let mut found_transfer_q_index = None;
        for (index, qfam) in queuefamilyproperties.iter().enumerate() {
            if qfam.queue_count > 0 && qfam.queue_flags.contains(vk::QueueFlags::GRAPHICS)
            {
                found_graphics_q_index = Some(index as u32);
            }
            if qfam.queue_count > 0 && qfam.queue_flags.contains(vk::QueueFlags::TRANSFER) {
                if found_transfer_q_index.is_none()
                    || !qfam.queue_flags.contains(vk::QueueFlags::GRAPHICS)
                {
                    found_transfer_q_index = Some(index as u32);
                }
            }
        }
        (
            found_graphics_q_index.unwrap(),
            found_transfer_q_index.unwrap(),
        )
    };
    let priorities = [1.0f32];
    let queue_infos = [
        vk::DeviceQueueCreateInfo::builder()
            .queue_family_index(qfamindices.0)
            .queue_priorities(&priorities)
            .build(),
        vk::DeviceQueueCreateInfo::builder()
            .queue_family_index(qfamindices.1)
            .queue_priorities(&priorities)
            .build(),
    ];
    dbg!(&queue_infos);
    let device_create_info = vk::DeviceCreateInfo::builder()
        .queue_create_infos(&queue_infos)
        .enabled_layer_names(&layer_name_pointers);
    let logical_device =
        unsafe { instance.create_device(physical_device, &device_create_info, None)? };
    let graphics_queue = unsafe { logical_device.get_device_queue(qfamindices.0, 0) };
    let transfer_queue = unsafe { logical_device.get_device_queue(qfamindices.1, 0) };

    unsafe {
        logical_device.destroy_device(None);
        debug_utils.destroy_debug_utils_messenger(utils_messenger, None);
        instance.destroy_instance(None)
    };
    Ok(())
}

unsafe extern "system" fn vulkan_debug_utils_callback(
    message_severity: vk::DebugUtilsMessageSeverityFlagsEXT,
    message_type: vk::DebugUtilsMessageTypeFlagsEXT,
    p_callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT,
    _p_user_data: *mut std::ffi::c_void,
) -> vk::Bool32 {
    let message = std::ffi::CStr::from_ptr((*p_callback_data).p_message);
    let severity = format!("{:?}", message_severity).to_lowercase();
    let ty = format!("{:?}", message_type).to_lowercase();
    println!("[Debug][{}][{}] {:?}", severity, ty, message);
    vk::FALSE
}

계속