좋습니다, 우리는 표면(surface)을 만들었고 (OS 특정 부분은 이제 확실히 끝났습니다), 이제 그 위에 어떻게 그림을 그릴까요? 음, 보통 우리는 화면에 보이는 것을 직접 그리고 싶어하지 않습니다. 고전적인 방법은 프론트버퍼(frontbuffer)와 백버퍼(backbuffer)를 두는 것입니다. 우리는 프론트버퍼를 화면에 보여주고 백버퍼에 그림을 그립니다. 그리기가 끝나면, 둘을 바꿉니다. 방금 그린 그림을 보여주고, 다른 버퍼(현재 화면에 보이지 않는)에는 새로운 것을 그립니다. 이 {프론트버퍼, 백버퍼} 집합이 스왑체인의 간단한 예입니다. 이미지를 세 개 사용할 수도 있습니다…
스왑체인은 또 다른 확장 기능이며, 이번에는 인스턴스 확장이 아닌 장치 확장입니다. 따라서 우리가 수정해야 할 것은 DeviceCreateInfo입니다:
let device_extension_name_pointers: Vec<*const i8> =
vec![ash::extensions::khr::Swapchain::name().as_ptr()];
let device_create_info = vk::DeviceCreateInfo::builder()
.queue_create_infos(&queue_infos)
.enabled_extension_names(&device_extension_name_pointers)
.enabled_layer_names(&layer_name_pointers);
실제로 스왑체인을 만들기 전에, 우리가 다루고 있는 표면에 대한 정보를 좀 조회해 봅시다. 주로 필요한 구조체를 어떻게 채워야 할지 더 잘 파악하기 위함입니다.
let surface_capabilities = unsafe {
surface_loader.get_physical_device_surface_capabilities(physical_device, surface)
};
let surface_present_modes = unsafe {
surface_loader.get_physical_device_surface_present_modes(physical_device, surface)
};
let surface_formats =
unsafe { surface_loader.get_physical_device_surface_formats(physical_device, surface) };
dbg!(&surface_capabilities);
dbg!(&surface_present_modes);
dbg!(&surface_formats);
이제 실제로 스왑체인을 만들어 봅시다:
let queuefamilies = [qfamindices.0];
let swapchain_create_info = vk::SwapchainCreateInfoKHR::builder()
.surface(surface)
.min_image_count(
3.max(surface_capabilities.min_image_count)
.min(surface_capabilities.max_image_count),
)
.image_format(surface_formats.first().unwrap().format)
.image_color_space(surface_formats.first().unwrap().color_space)
.image_extent(surface_capabilities.current_extent)
.image_array_layers(1)
.image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT)
.image_sharing_mode(vk::SharingMode::EXCLUSIVE)
.queue_family_indices(&queuefamilies)
.pre_transform(surface_capabilities.current_transform)
.composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE)
.present_mode(vk::PresentModeKHR::FIFO);
let swapchain_loader = ash::extensions::khr::Swapchain::new(&instance, &logical_device);
let swapchain = unsafe { swapchain_loader.create_swapchain(&swapchain_create_info, None)? };
즉, 우리가 만든 표면을 위한 스왑체인을 만듭니다 (타당하죠). 이미지는 3개(프론트버퍼, 백버퍼, 백-백버퍼?)로 하되, 최소 요구량 이상, 최대 가능량 이하로 설정합니다. 특정 이미지 포맷과 색 공간을 사용하는데, 제 경우에는 다음과 같을 것입니다.
SurfaceFormatKHR {
format: B8G8R8A8_UNORM,
color_space: SRGB_NONLINEAR,
},
(이전 디버그 출력에서 그냥 복사해 온다면 말이죠.) 여기서 포맷은 파란색, 초록색, 빨간색, 알파 채널 각각에 8비트씩 (그 순서대로) 할당되며, 부호 없는 정규화 포맷, 즉 [0,1] 범위에 있다는 것을 의미합니다. 그리고 (화면에 그리고자 하는 것의) 색상은 sRGB로 인코딩되는데, 이는 어떤 회색을 나타내는 값을 두 배로 하면 인지되는 밝기가 두 배가 되는 것과는 잘 맞지만, 색상을 선형 보간하는 것은 잘 동작하지 않는다는 의미입니다. (sRGB는 단순한 선형 RGB에 감마 보정을 더한 것입니다.)
계속해서: 프레임의 크기가 필요합니다 (여기서는 800x600 픽셀인데, winit에 아무것도 알려주지 않으면 이게 기본값인 것 같습니다 — 하지만 별로 중요하지 않습니다, 왜냐하면 그냥 현재 사용 중인 크기(“extent”)를 가져다 쓸 것이기 때문입니다 (surface_capabilities 참조)).
저는 화면이 하나이고, 멋진 VR 안경 세트 같은 건 없으므로 한 번에 하나의 이미지만 필요합니다 (그리고 이것이 image_array_layers가 하는 일이라고 생각합니다). 우리는 이 스왑체인의 결과를 COLOR_ATTACHMENT로 사용하고 싶습니다. 한 번에 하나의 큐 패밀리에서만 이미지에 접근할 것이므로, 접근을 공유할 필요가 없습니다 (SharingMode::EXCLUSIVE). 그리고 그 큐 패밀리는 그래픽스용입니다.
변환(transform)으로는 “시계 방향으로 90도 회전”이나 “수평으로 뒤집기” 같은 것을 지정할 수 있지만, 제 경우 표면 기능(surface capabilities)을 보면 어차피 항등 변환 외에는 아무것도 작동하지 않을 것이라고 나옵니다. 그냥 현재 것을 사용합시다.
복합 알파(Composite alpha)는 애플리케이션 창이 다른 창과 어떻게 상호 작용해야 하는지와 관련이 있습니다. 우리는 어떤 투명도 효과도 만들지 않을 것이므로, 그냥 OPAQUE로 설정합시다. 참고: 여기서의 투명도는 창 자체의 투명도에만 해당하며, 나중에 창 안에 그리게 될 객체의 투명도와는 전혀 관련이 없습니다. (만약 우리가 실제로 무언가를 그릴 만큼 충분히 진행한다면 말이죠.)
마지막으로, 표현 모드(presentation mode)입니다. 프론트버퍼와 백버퍼만 있다면 이건 흥미로운 질문이 아닙니다. 어느 시점에 그 내용을 바꾸면 되니까요. 이제 우리는 그릴 것이 세 개나 있습니다. 화면이 1번을 보여주느라 바쁜 동안, 우리가 빠르게 2번과 3번을 준비했다고 가정해 봅시다. 새 그림을 사용해야 할 때, 어떤 것을 선택해야 할까요? 우리는 FIFO 모드를 선택합니다: 준비된 모든 이미지를 순서대로 보여줍니다. 다른 것은 무엇이 있을까요? 관심이 있다면, 명세서(vkPresentModeKHR 항목)를 읽어보세요. 제가 설명하는 것보다 더 나은 설명이 있습니다.
CreateInfo 구조체를 채운 후 (어떤 필드를 채워야 할지 어떻게 알 수 있을까요? — 평소처럼 vk::SwapchainCreateInfoKHR{..Default::default()}로 시작해서 dbg! 출력을 엿보거나, ash 문서나 벌칸 명세서의 메소드 목록을 따라가면 됩니다), 먼저 확장을 로드하고 특정 생성 함수를 호출합니다. 그리고, create_... 함수로 만든 것은 나중에 (프로그램 끝에서) 다시 파괴(destroy)해야 합니다:
swapchain_loader.destroy_swapchain(swapchain, None);
이제 스왑체인이 생겼습니다. 스왑체인이 있다는 것은 몇 개의 이미지(우리가 설정한 방식대로라면 아마도 3개)를 가지고 있다는 뜻입니다. 그것들을 가져옵시다:
let swapchain_images = unsafe { swapchain_loader.get_swapchain_images(swapchain)? };
이것은 vkImage의 Vec입니다.
이미지는 아직 그다지 유용한 상태가 아닙니다. 특히, 이미지 데이터를 읽고 쓰기 위해 직접 접근하지 않습니다. 대신, 이미지 뷰(ImageView)가 필요합니다.
let mut swapchain_imageviews = Vec::with_capacity(swapchain_images.len());
for image in &swapchain_images {
let subresource_range = vk::ImageSubresourceRange::builder()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.base_mip_level(0)
.level_count(1)
.base_array_layer(0)
.layer_count(1);
let imageview_create_info = vk::ImageViewCreateInfo::builder()
.image(*image)
.view_type(vk::ImageViewType::TYPE_2D)
.format(vk::Format::B8G8R8A8_UNORM)
.subresource_range(*subresource_range);
let imageview = unsafe { logical_device.create_image_view(&imageview_create_info, None) }?;
swapchain_imageviews.push(imageview);
}
여기서 우리는 각 이미지에 대한 이미지 뷰를 만듭니다. 우리는 다음을 명시해야 합니다: 어떤 이미지에 대한 것인지, 어떤 타입인지(2D 이미지, 또는 1D, 3D, 큐브 맵?), 포맷(이것을 바꿔보세요; 스왑체인 생성 정보에 주어진 포맷과 다른 것을 사용하면 유효성 검사 레이어가 불평할 것입니다), 그리고 하위 리소스 범위(subresource range)입니다 (이 이미지의 색상(Color) 정보에 관심이 있고, 깊이(depth) 정보는 아니며, mip_levels과 array_layers 각각에 대해 0번부터 시작하는 레벨이 하나씩 있다는 것을 의미합니다. 지금은 이것들을 무시할 수 있습니다).
그리고 다시: 명시적인 생성은 끝에서 명시적인 파괴를 의미합니다:
for iv in &swapchain_imageviews {
logical_device.destroy_image_view(*iv, None);
}
현재 프로그램:
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(),
ash::extensions::khr::Surface::name().as_ptr(),
ash::extensions::khr::XlibSurface::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)? };
use winit::platform::unix::WindowExtUnix;
let x11_display = window.xlib_display().unwrap();
let x11_window = window.xlib_window().unwrap();
let x11_create_info = vk::XlibSurfaceCreateInfoKHR::builder()
.window(x11_window)
.dpy(x11_display as *mut vk::Display);
let xlib_surface_loader = ash::extensions::khr::XlibSurface::new(&entry, &instance);
let surface = unsafe { xlib_surface_loader.create_xlib_surface(&x11_create_info, None) }?;
let surface_loader = ash::extensions::khr::Surface::new(&entry, &instance);
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)
&& unsafe {
surface_loader
.get_physical_device_surface_support(physical_device, index as u32, surface)?
}
{
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(),
];
let device_extension_name_pointers: Vec<*const i8> =
vec![ash::extensions::khr::Swapchain::name().as_ptr()];
let device_create_info = vk::DeviceCreateInfo::builder()
.queue_create_infos(&queue_infos)
.enabled_extension_names(&device_extension_name_pointers)
.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) };
let surface_capabilities = unsafe {
surface_loader.get_physical_device_surface_capabilities(physical_device, surface)?
};
let surface_present_modes = unsafe {
surface_loader.get_physical_device_surface_present_modes(physical_device, surface)?
};
let surface_formats =
unsafe { surface_loader.get_physical_device_surface_formats(physical_device, surface)? };
let queuefamilies = [qfamindices.0];
let swapchain_create_info = vk::SwapchainCreateInfoKHR::builder()
.surface(surface)
.min_image_count(
3.max(surface_capabilities.min_image_count)
.min(surface_capabilities.max_image_count),
)
.image_format(surface_formats.first().unwrap().format)
.image_color_space(surface_formats.first().unwrap().color_space)
.image_extent(surface_capabilities.current_extent)
.image_array_layers(1)
.image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT)
.image_sharing_mode(vk::SharingMode::EXCLUSIVE)
.queue_family_indices(&queuefamilies)
.pre_transform(surface_capabilities.current_transform)
.composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE)
.present_mode(vk::PresentModeKHR::FIFO);
let swapchain_loader = ash::extensions::khr::Swapchain::new(&instance, &logical_device);
let swapchain = unsafe { swapchain_loader.create_swapchain(&swapchain_create_info, None)? };
let swapchain_images = unsafe { swapchain_loader.get_swapchain_images(swapchain)? };
let mut swapchain_imageviews = Vec::with_capacity(swapchain_images.len());
for image in &swapchain_images {
let subresource_range = vk::ImageSubresourceRange::builder()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.base_mip_level(0)
.level_count(1)
.base_array_layer(0)
.layer_count(1);
let imageview_create_info = vk::ImageViewCreateInfo::builder()
.image(*image)
.view_type(vk::ImageViewType::TYPE_2D)
.format(vk::Format::B8G8R8A8_UNORM)
.subresource_range(*subresource_range);
let imageview = unsafe { logical_device.create_image_view(&imageview_create_info, None) }?;
swapchain_imageviews.push(imageview);
}
unsafe {
for iv in &swapchain_imageviews {
logical_device.destroy_image_view(*iv, None);
}
swapchain_loader.destroy_swapchain(swapchain, None);
logical_device.destroy_device(None);
surface_loader.destroy_surface(surface, 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
}