큐는 우리가 명령(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
}