우리는 이미 준비 과정의 상당 부분을 완료했으며, (제가 순진하게 생각하는) “그래픽스”의 핵심인 셰이더(Shaders)와 파이프라인(pipelines)에 가까워지고 있습니다. 하지만 그곳으로 가기 전에, 잠시 Vulkan에서 벗어나 우리 프로그램을 생각해 봅시다. 세 가지 문제가 있습니다: main 함수가 점점 커지고 있으므로, 몇 개의 개별 단위로 분리해야 합니다. 우리는 본질적으로 전역 변수인 많은 것들을 (우리가 가진 유일한 함수에 흩어져) 수집하고 있는데, 이것들을 구조체로 정리할 필요가 있습니다. 셋째로, 우리는 창을 유지하지 않고 있습니다 (무언가를 보려면 창을 즉시 닫는 것은 좋지 않습니다).
임의의 첫 번째 정리 단계: 인스턴스 생성을 별도의 함수로 옮길 수 있습니다. 이 작업은
let enginename = std::ffi::CString::new("UnknownGameEngine").unwrap();
(이전 줄인 entry는 따로 둡시다; 계속 가지고 있어야 합니다)부터
let instance = unsafe { entry.create_instance(&instance_create_info, None)? };
까지의 모든 것을 포함해야 합니다.
첫 번째 단계: 이 모든 것을 가져와서 하나의 블록 안에 넣고, “let instance=“와 마지막 세미콜론은 중괄호 밖으로 옮깁니다. 이렇게요:
let instance = {
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);
unsafe { entry.create_instance(&instance_create_info, None)}
}?;
그런 다음 컴파일러의 말을 들어봅시다. 컴파일러는 (이 정의 이후의 줄에서) 나중에 사용한 debugcreateinfo와 layer_name_pointers를 찾을 수 없다고 불평합니다. 결정을 내려야 합니다: 정의를 복사하고 싶나요, 아니면 이 스코프 밖으로 옮겨서 (생성될) 함수에 인자로 전달하고 싶나요?
debugcreateinfo의 경우, 저는 복사하는 편을 선호합니다. 음, 복사하고 여기에 남겨둘 부분을 수정하는 거죠: 인스턴스 생성과 관련된 부분에서 메시지가 더 적어도 괜찮을 것 같습니다.
layer_name_pointers는: 포인터의 Vec을 여기저기 전달하는 것이 올바른 방법이라고 생각되지 않습니다. 그래서, 좋지 않은 타협안: 실제 데이터는 바깥에 두고 (let layer_names = vec!["VK_LAYER_KHRONOS_validation"];), CString과 포인터 생성은 복제합니다:
let layer_names = vec!["VK_LAYER_KHRONOS_validation"];
let instance = {
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_c: Vec<std::ffi::CString> = layer_names
.iter()
.map(|&ln| std::ffi::CString::new(ln).unwrap())
.collect();
let layer_name_pointers: Vec<*const i8> = layer_names_c
.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::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);
unsafe { entry.create_instance(&instance_create_info, None) }
}?;
let layer_names_c: Vec<std::ffi::CString> = layer_names
.iter()
.map(|&ln| std::ffi::CString::new(ln).unwrap())
.collect();
let layer_name_pointers: Vec<*const i8> = layer_names_c
.iter()
.map(|layer_name| layer_name.as_ptr())
.collect();
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 = init_instance(&entry, &layer_names)?;
함수는 이렇게 생겼습니다:
fn init_instance(
entry: &ash::Entry,
layer_names: &[&str],
) -> Result<ash::Instance, ash::InstanceError> {
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_c: Vec<std::ffi::CString> = layer_names
.iter()
.map(|&ln| std::ffi::CString::new(ln).unwrap())
.collect();
let layer_name_pointers: Vec<*const i8> = layer_names_c
.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::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);
unsafe { entry.create_instance(&instance_create_info, None) }
}
컴파일러가 올바른 반환 타입을 알아내는 데 도움을 주었을 수 있습니다. (“반환 타입이 InstanceError여야 한다는 걸 어떻게 알았을까?” — 음, -> Result<ash::Instance, ()>로 시도해보고 메시지를 읽어보세요.)
좋습니다, 다음 임의의 정리 작업입니다. 디버그 관련 부분을 분리합시다. 별도의 구조체에 넣습니다:
struct DebugDongXi {
loader: ash::extensions::ext::DebugUtils,
messenger: vk::DebugUtilsMessengerEXT,
}
impl DebugDongXi {
fn init(entry: &ash::Entry, instance: &ash::Instance) -> Result<DebugDongXi, vk::Result> {
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 loader = ash::extensions::ext::DebugUtils::new(entry, instance);
let messenger = unsafe { loader.create_debug_utils_messenger(&debugcreateinfo, None)? };
Ok(DebugDongXi { loader, messenger })
}
}
(역주: DongXi(东西)는 중국어로 “물건”, “것”이라는 뜻입니다. 저자가 재미로 붙인 이름입니다.)
그리고
let debug = DebugDongXi::init(&entry, &instance)?;
와 함께 정리 코드에 약간의 변경을 가합니다:
debug
.loader
.destroy_debug_utils_messenger(debug.messenger, None);
사실, 이것은 DebugDongXi 객체가 사라질 때 (자동으로) 항상 하고 싶은 일입니다. Drop 구현을 위한 완벽한 장소처럼 들립니다:
impl Drop for DebugDongXi {
fn drop(&mut self) {
unsafe {
self.loader
.destroy_debug_utils_messenger(self.messenger, None)
};
}
}
그리고 그 대가로 이전의
debug
.loader
.destroy_debug_utils_messenger(debug.messenger, None);
코드를 제거합니다.
오!
[Debug][error][validation] "Debug messengers not removed before DestroyInstance"
물론, 이제 정리 작업이 잘못된 순서로 일어납니다: 우리의 DebugDongXi는 함수가 끝날 때까지 살아있는데, 이때는 이미 인스턴스가 파괴된 후입니다. 음, 우리는 여전히 “debug”의 생명주기를 조기에 (또는: 완벽한 시점에) 끝낼 수 있습니다:
std::mem::drop(debug);
모든 표면(surface) 관련 사항들도 비슷하게 처리합니다:
struct SurfaceDongXi {
xlib_surface_loader: ash::extensions::khr::XlibSurface,
surface: vk::SurfaceKHR,
surface_loader: ash::extensions::khr::Surface,
}
impl SurfaceDongXi {
fn init(
window: &winit::window::Window,
entry: &ash::Entry,
instance: &ash::Instance,
) -> Result<SurfaceDongXi, vk::Result> {
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);
Ok(SurfaceDongXi {
xlib_surface_loader,
surface,
surface_loader,
})
}
}
impl Drop for SurfaceDongXi {
fn drop(&mut self) {
unsafe {
self.surface_loader.destroy_surface(self.surface, None);
}
}
}
그리고:
let surfaces = SurfaceDongXi::init(&window, &entry, &instance)?;
이제 일부 함수 호출이 다소 성가셔집니다:
let surface_capabilities = unsafe {
surfaces
.surface_loader
.get_physical_device_surface_capabilities(physical_device, surfaces.surface)?
};
이 코드에는 “surfaces”가 두 번 포함되어 있는데, SurfaceDongXi의 메서드로 두는 것이 더 나을 수 있습니다. 그래서:
let surface_capabilities = surfaces.get_capabilities(physical_device)?;
와 함께
impl SurfaceDongXi{
fn get_capabilities(
&self,
physical_device: vk::PhysicalDevice,
) -> Result<vk::SurfaceCapabilitiesKHR, vk::Result> {
unsafe {
self.surface_loader
.get_physical_device_surface_capabilities(physical_device, self.surface)
}
}
}
… 그리고 다른 함수들도 비슷하게 처리합니다:
fn get_present_modes(
&self,
physical_device: vk::PhysicalDevice,
) -> Result<Vec<vk::PresentModeKHR>, vk::Result> {
unsafe {
self.surface_loader
.get_physical_device_surface_present_modes(physical_device, self.surface)
}
}
fn get_formats(
&self,
physical_device: vk::PhysicalDevice,
) -> Result<Vec<vk::SurfaceFormatKHR>, vk::Result> {
unsafe {
self.surface_loader
.get_physical_device_surface_formats(physical_device, self.surface)
}
}
fn get_physical_device_surface_support(
&self,
physical_device: vk::PhysicalDevice,
queuefamilyindex: usize,
) -> Result<bool, vk::Result> {
unsafe {
self.surface_loader.get_physical_device_surface_support(
physical_device,
queuefamilyindex as u32,
self.surface,
)
}
}
이렇게 다른 구조체와 함수로 분리하고 새로운 편의 메서드를 도입하는 것은 양날의 검과 같습니다. 물론 함수를 더 보기 좋고 접근하기 쉽게 만들지만, 동시에 기존 문서와는 더 멀어지게 만듭니다. 이는 다른 사람들이 코드를 사용하는 것을 더 어렵게 만들 수 있습니다. 그리고 “다른 사람들”에는 미래의 저 자신도 포함됩니다. 음, 적어도 이번에는 코드가 어떻게 보일지는 제 책임입니다. (일반적으로, 이러한 결정을 내리는 것은 특정 라이브러리 작성자들이 무슨 생각을 했는지 이해하게 해주며, 이는 분명히 감사할 일입니다 — 하지만 이런 매우 사소한 곳에는 그다지 적용되지 않습니다.)
큐 패밀리(QueueFamilies)도 분리할 수 있습니다:
struct QueueFamilies {
graphics_q_index: Option<u32>,
transfer_q_index: Option<u32>,
}
impl QueueFamilies {
fn init(
instance: &ash::Instance,
physical_device: vk::PhysicalDevice,
surfaces: &SurfaceDongXi,
) -> Result<QueueFamilies, vk::Result> {
let queuefamilyproperties =
unsafe { instance.get_physical_device_queue_family_properties(physical_device) };
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)
&& surfaces.get_physical_device_surface_support(physical_device, index)?
{
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);
}
}
}
Ok(QueueFamilies {
graphics_q_index: found_graphics_q_index,
transfer_q_index: found_transfer_q_index,
})
}
}
그리고
let queue_families = QueueFamilies::init(&instance, physical_device, &surfaces)?;
비슷한 성격의 몇 가지 수정 후에, 코드는 다음과 같이 보입니다:
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 layer_names = vec!["VK_LAYER_KHRONOS_validation"];
let instance = init_instance(&entry, &layer_names)?;
let debug = DebugDongXi::init(&entry, &instance)?;
let surfaces = SurfaceDongXi::init(&window, &entry, &instance)?;
let (physical_device, physical_device_properties) =
init_physical_device_and_properties(&instance)?;
let queue_families = QueueFamilies::init(&instance, physical_device, &surfaces)?;
let (logical_device, queues) =
init_device_and_queues(&instance, physical_device, &queue_families, &layer_names)?;
let swapchain = SwapchainDongXi::init(
&instance,
physical_device,
&logical_device,
&surfaces,
&queue_families,
&queues,
)?;
unsafe {
swapchain.cleanup(&logical_device);
logical_device.destroy_device(None);
std::mem::drop(surfaces);
std::mem::drop(debug);
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
}
fn init_instance(
entry: &ash::Entry,
layer_names: &[&str],
) -> Result<ash::Instance, ash::InstanceError> {
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_c: Vec<std::ffi::CString> = layer_names
.iter()
.map(|&ln| std::ffi::CString::new(ln).unwrap())
.collect();
let layer_name_pointers: Vec<*const i8> = layer_names_c
.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::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);
unsafe { entry.create_instance(&instance_create_info, None) }
}
struct DebugDongXi {
loader: ash::extensions::ext::DebugUtils,
messenger: vk::DebugUtilsMessengerEXT,
}
impl DebugDongXi {
fn init(entry: &ash::Entry, instance: &ash::Instance) -> Result<DebugDongXi, vk::Result> {
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 loader = ash::extensions::ext::DebugUtils::new(entry, instance);
let messenger = unsafe { loader.create_debug_utils_messenger(&debugcreateinfo, None)? };
Ok(DebugDongXi { loader, messenger })
}
}
impl Drop for DebugDongXi {
fn drop(&mut self) {
unsafe {
self.loader
.destroy_debug_utils_messenger(self.messenger, None)
};
}
}
struct SurfaceDongXi {
xlib_surface_loader: ash::extensions::khr::XlibSurface,
surface: vk::SurfaceKHR,
surface_loader: ash::extensions::khr::Surface,
}
impl SurfaceDongXi {
fn init(
window: &winit::window::Window,
entry: &ash::Entry,
instance: &ash::Instance,
) -> Result<SurfaceDongXi, vk::Result> {
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);
Ok(SurfaceDongXi {
xlib_surface_loader,
surface,
surface_loader,
})
}
fn get_capabilities(
&self,
physical_device: vk::PhysicalDevice,
) -> Result<vk::SurfaceCapabilitiesKHR, vk::Result> {
unsafe {
self.surface_loader
.get_physical_device_surface_capabilities(physical_device, self.surface)
}
}
fn get_present_modes(
&self,
physical_device: vk::PhysicalDevice,
) -> Result<Vec<vk::PresentModeKHR>, vk::Result> {
unsafe {
self.surface_loader
.get_physical_device_surface_present_modes(physical_device, self.surface)
}
}
fn get_formats(
&self,
physical_device: vk::PhysicalDevice,
) -> Result<Vec<vk::SurfaceFormatKHR>, vk::Result> {
unsafe {
self.surface_loader
.get_physical_device_surface_formats(physical_device, self.surface)
}
}
fn get_physical_device_surface_support(
&self,
physical_device: vk::PhysicalDevice,
queuefamilyindex: usize,
) -> Result<bool, vk::Result> {
unsafe {
self.surface_loader.get_physical_device_surface_support(
physical_device,
queuefamilyindex as u32,
self.surface,
)
}
}
}
impl Drop for SurfaceDongXi {
fn drop(&mut self) {
unsafe {
self.surface_loader.destroy_surface(self.surface, None);
}
}
}
fn init_physical_device_and_properties(
instance: &ash::Instance,
) -> Result<(vk::PhysicalDevice, vk::PhysicalDeviceProperties), vk::Result> {
let phys_devs = unsafe { instance.enumerate_physical_devices()? };
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));
}
}
Ok(chosen.unwrap())
}
struct QueueFamilies {
graphics_q_index: Option<u32>,
transfer_q_index: Option<u32>,
}
impl QueueFamilies {
fn init(
instance: &ash::Instance,
physical_device: vk::PhysicalDevice,
surfaces: &SurfaceDongXi,
) -> Result<QueueFamilies, vk::Result> {
let queuefamilyproperties =
unsafe { instance.get_physical_device_queue_family_properties(physical_device) };
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)
&& surfaces.get_physical_device_surface_support(physical_device, index)?
{
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);
}
}
}
Ok(QueueFamilies {
graphics_q_index: found_graphics_q_index,
transfer_q_index: found_transfer_q_index,
})
}
}
struct Queues {
graphics_queue: vk::Queue,
transfer_queue: vk::Queue,
}
fn init_device_and_queues(
instance: &ash::Instance,
physical_device: vk::PhysicalDevice,
queue_families: &QueueFamilies,
layer_names: &[&str],
) -> Result<(ash::Device, Queues), vk::Result> {
let layer_names_c: Vec<std::ffi::CString> = layer_names
.iter()
.map(|&ln| std::ffi::CString::new(ln).unwrap())
.collect();
let layer_name_pointers: Vec<*const i8> = layer_names_c
.iter()
.map(|layer_name| layer_name.as_ptr())
.collect();
let priorities = [1.0f32];
let queue_infos = [
vk::DeviceQueueCreateInfo::builder()
.queue_family_index(queue_families.graphics_q_index.unwrap())
.queue_priorities(&priorities)
.build(),
vk::DeviceQueueCreateInfo::builder()
.queue_family_index(queue_families.transfer_q_index.unwrap())
.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(queue_families.graphics_q_index.unwrap(), 0) };
let transfer_queue =
unsafe { logical_device.get_device_queue(queue_families.transfer_q_index.unwrap(), 0) };
Ok((
logical_device,
Queues {
graphics_queue,
transfer_queue,
},
))
}
struct SwapchainDongXi {
swapchain_loader: ash::extensions::khr::Swapchain,
swapchain: vk::SwapchainKHR,
images: Vec<vk::Image>,
imageviews: Vec<vk::ImageView>,
}
impl SwapchainDongXi {
fn init(
instance: &ash::Instance,
physical_device: vk::PhysicalDevice,
logical_device: &ash::Device,
surfaces: &SurfaceDongXi,
queue_families: &QueueFamilies,
queues: &Queues,
) -> Result<SwapchainDongXi, vk::Result> {
let surface_capabilities = surfaces.get_capabilities(physical_device)?;
let surface_present_modes = surfaces.get_present_modes(physical_device)?;
let surface_formats = surfaces.get_formats(physical_device)?;
let queuefamilies = [queue_families.graphics_q_index.unwrap()];
let swapchain_create_info = vk::SwapchainCreateInfoKHR::builder()
.surface(surfaces.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);
}
Ok(SwapchainDongXi {
swapchain_loader,
swapchain,
images: swapchain_images,
imageviews: swapchain_imageviews,
})
}
fn cleanup(self, logical_device: &ash::Device) {
for iv in &self.imageviews {
unsafe { logical_device.destroy_image_view(*iv, None) };
}
}
}
impl Drop for SwapchainDongXi {
fn drop(&mut self) {
unsafe {
self.swapchain_loader
.destroy_swapchain(self.swapchain, None)
};
}
}
주목할 만한 점은 SwapChainDongXi의 “Drop”입니다. 우리는 여기서 스왑체인 이미지뷰들도 파괴해야 합니다. 하지만, 그러려면 logical_device가 필요한데, drop()에 전달할 수 없습니다. 따라서 별도의 함수가 있어야 합니다.
다음 단계로 넘어갑시다: 남은 몇 가지를 하나의 구조체에 모두 넣겠습니다.
struct Aetna {
window: winit::window::Window,
entry: ash::Entry,
instance: ash::Instance,
debug: DebugDongXi,
surfaces: SurfaceDongXi,
physical_device: vk::PhysicalDevice,
physical_device_properties: vk::PhysicalDeviceProperties,
queue_families: QueueFamilies,
queues: Queues,
device: ash::Device,
swapchain: SwapchainDongXi,
}
impl Aetna {
fn init(window: winit::window::Window) -> Result<Aetna, Box<dyn std::error::Error>> {
let entry = ash::Entry::new()?;
let layer_names = vec!["VK_LAYER_KHRONOS_validation"];
let instance = init_instance(&entry, &layer_names)?;
let debug = DebugDongXi::init(&entry, &instance)?;
let surfaces = SurfaceDongXi::init(&window, &entry, &instance)?;
let (physical_device, physical_device_properties) =
init_physical_device_and_properties(&instance)?;
let queue_families = QueueFamilies::init(&instance, physical_device, &surfaces)?;
let (logical_device, queues) =
init_device_and_queues(&instance, physical_device, &queue_families, &layer_names)?;
let swapchain = SwapchainDongXi::init(
&instance,
physical_device,
&logical_device,
&surfaces,
&queue_families,
&queues,
)?;
Ok(Aetna {
window,
entry,
instance,
debug,
surfaces,
physical_device,
physical_device_properties,
queue_families,
queues,
device: logical_device,
swapchain,
})
}
}
impl Drop for Aetna {
fn drop(&mut self) {
unsafe {
self.swapchain.cleanup(&self.device);
self.device.destroy_device(None);
std::mem::drop(self.surfaces);
std::mem::drop(self.debug);
self.instance.destroy_instance(None)
};
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let eventloop = winit::event_loop::EventLoop::new();
let window = winit::window::Window::new(&eventloop)?;
let aetna = Aetna::init(window)?;
Ok(())
}
이것은 나쁘지 않아 보입니다 (그리고 main 함수가 얼마나 짧아졌는지 마음에 듭니다). 하지만 작동하지 않습니다. 큰 구조체의 개별 필드를 드롭(drop)할 수 없습니다. 왜냐하면 그것은 drop() 함수가 끝날 때만 소멸되어야 하기 때문이며, 같은 이유로 swapchain.cleanup()을 호출할 수 없습니다. 가능한 해결책은 surfaces와 debug를 ManuallyDrop 래퍼로 감싸는 것입니다. 스왑체인 정리를 위해, 저는 drop() 함수를 제거하고 모든 것을 cleanup()에 넣되, self 대신 &mut self만 받도록 만들겠습니다. “unsafe” 마커는 밖으로 옮깁니다. 왜냐하면 이제 이 함수는 위험하기 때문입니다: 호출된 후에도 SwapchainDongXi 객체는 여전히 존재하지만 더 이상 사용할 수 없습니다. 이미지뷰, 이미지, 스왑체인이 이미 사라졌기 때문입니다.
다시 프로그램을 살펴볼 시간입니다:
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 aetna = Aetna::init(window)?;
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
}
fn init_instance(
entry: &ash::Entry,
layer_names: &[&str],
) -> Result<ash::Instance, ash::InstanceError> {
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_c: Vec<std::ffi::CString> = layer_names
.iter()
.map(|&ln| std::ffi::CString::new(ln).unwrap())
.collect();
let layer_name_pointers: Vec<*const i8> = layer_names_c
.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::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);
unsafe { entry.create_instance(&instance_create_info, None) }
}
struct DebugDongXi {
loader: ash::extensions::ext::DebugUtils,
messenger: vk::DebugUtilsMessengerEXT,
}
impl DebugDongXi {
fn init(entry: &ash::Entry, instance: &ash::Instance) -> Result<DebugDongXi, vk::Result> {
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 loader = ash::extensions::ext::DebugUtils::new(entry, instance);
let messenger = unsafe { loader.create_debug_utils_messenger(&debugcreateinfo, None)? };
Ok(DebugDongXi { loader, messenger })
}
}
impl Drop for DebugDongXi {
fn drop(&mut self) {
unsafe {
self.loader
.destroy_debug_utils_messenger(self.messenger, None)
};
}
}
struct SurfaceDongXi {
xlib_surface_loader: ash::extensions::khr::XlibSurface,
surface: vk::SurfaceKHR,
surface_loader: ash::extensions::khr::Surface,
}
impl SurfaceDongXi {
fn init(
window: &winit::window::Window,
entry: &ash::Entry,
instance: &ash::Instance,
) -> Result<SurfaceDongXi, vk::Result> {
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);
Ok(SurfaceDongXi {
xlib_surface_loader,
surface,
surface_loader,
})
}
fn get_capabilities(
&self,
physical_device: vk::PhysicalDevice,
) -> Result<vk::SurfaceCapabilitiesKHR, vk::Result> {
unsafe {
self.surface_loader
.get_physical_device_surface_capabilities(physical_device, self.surface)
}
}
fn get_present_modes(
&self,
physical_device: vk::PhysicalDevice,
) -> Result<Vec<vk::PresentModeKHR>, vk::Result> {
unsafe {
self.surface_loader
.get_physical_device_surface_present_modes(physical_device, self.surface)
}
}
fn get_formats(
&self,
physical_device: vk::PhysicalDevice,
) -> Result<Vec<vk::SurfaceFormatKHR>, vk::Result> {
unsafe {
self.surface_loader
.get_physical_device_surface_formats(physical_device, self.surface)
}
}
fn get_physical_device_surface_support(
&self,
physical_device: vk::PhysicalDevice,
queuefamilyindex: usize,
) -> Result<bool, vk::Result> {
unsafe {
self.surface_loader.get_physical_device_surface_support(
physical_device,
queuefamilyindex as u32,
self.surface,
)
}
}
}
impl Drop for SurfaceDongXi {
fn drop(&mut self) {
unsafe {
self.surface_loader.destroy_surface(self.surface, None);
}
}
}
fn init_physical_device_and_properties(
instance: &ash::Instance,
) -> Result<(vk::PhysicalDevice, vk::PhysicalDeviceProperties), vk::Result> {
let phys_devs = unsafe { instance.enumerate_physical_devices()? };
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));
}
}
Ok(chosen.unwrap())
}
struct QueueFamilies {
graphics_q_index: Option<u32>,
transfer_q_index: Option<u32>,
}
impl QueueFamilies {
fn init(
instance: &ash::Instance,
physical_device: vk::PhysicalDevice,
surfaces: &SurfaceDongXi,
) -> Result<QueueFamilies, vk::Result> {
let queuefamilyproperties =
unsafe { instance.get_physical_device_queue_family_properties(physical_device) };
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)
&& surfaces.get_physical_device_surface_support(physical_device, index)?
{
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);
}
}
}
Ok(QueueFamilies {
graphics_q_index: found_graphics_q_index,
transfer_q_index: found_transfer_q_index,
})
}
}
struct Queues {
graphics_queue: vk::Queue,
transfer_queue: vk::Queue,
}
fn init_device_and_queues(
instance: &ash::Instance,
physical_device: vk::PhysicalDevice,
queue_families: &QueueFamilies,
layer_names: &[&str],
) -> Result<(ash::Device, Queues), vk::Result> {
let layer_names_c: Vec<std::ffi::CString> = layer_names
.iter()
.map(|&ln| std::ffi::CString::new(ln).unwrap())
.collect();
let layer_name_pointers: Vec<*const i8> = layer_names_c
.iter()
.map(|layer_name| layer_name.as_ptr())
.collect();
let priorities = [1.0f32];
let queue_infos = [
vk::DeviceQueueCreateInfo::builder()
.queue_family_index(queue_families.graphics_q_index.unwrap())
.queue_priorities(&priorities)
.build(),
vk::DeviceQueueCreateInfo::builder()
.queue_family_index(queue_families.transfer_q_index.unwrap())
.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(queue_families.graphics_q_index.unwrap(), 0) };
let transfer_queue =
unsafe { logical_device.get_device_queue(queue_families.transfer_q_index.unwrap(), 0) };
Ok((
logical_device,
Queues {
graphics_queue,
transfer_queue,
},
))
}
struct SwapchainDongXi {
swapchain_loader: ash::extensions::khr::Swapchain,
swapchain: vk::SwapchainKHR,
images: Vec<vk::Image>,
imageviews: Vec<vk::ImageView>,
}
impl SwapchainDongXi {
fn init(
instance: &ash::Instance,
physical_device: vk::PhysicalDevice,
logical_device: &ash::Device,
surfaces: &SurfaceDongXi,
queue_families: &QueueFamilies,
queues: &Queues,
) -> Result<SwapchainDongXi, vk::Result> {
let surface_capabilities = surfaces.get_capabilities(physical_device)?;
let surface_present_modes = surfaces.get_present_modes(physical_device)?;
let surface_formats = surfaces.get_formats(physical_device)?;
let queuefamilies = [queue_families.graphics_q_index.unwrap()];
let swapchain_create_info = vk::SwapchainCreateInfoKHR::builder()
.surface(surfaces.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);
}
Ok(SwapchainDongXi {
swapchain_loader,
swapchain,
images: swapchain_images,
imageviews: swapchain_imageviews,
})
}
unsafe fn cleanup(&mut self, logical_device: &ash::Device) {
for iv in &self.imageviews {
logical_device.destroy_image_view(*iv, None);
}
self.swapchain_loader
.destroy_swapchain(self.swapchain, None)
}
}
struct Aetna {
window: winit::window::Window,
entry: ash::Entry,
instance: ash::Instance,
debug: std::mem::ManuallyDrop<DebugDongXi>,
surfaces: std::mem::ManuallyDrop<SurfaceDongXi>,
physical_device: vk::PhysicalDevice,
physical_device_properties: vk::PhysicalDeviceProperties,
queue_families: QueueFamilies,
queues: Queues,
device: ash::Device,
swapchain: SwapchainDongXi,
}
impl Aetna {
fn init(window: winit::window::Window) -> Result<Aetna, Box<dyn std::error::Error>> {
let entry = ash::Entry::new()?;
let layer_names = vec!["VK_LAYER_KHRONOS_validation"];
let instance = init_instance(&entry, &layer_names)?;
let debug = DebugDongXi::init(&entry, &instance)?;
let surfaces = SurfaceDongXi::init(&window, &entry, &instance)?;
let (physical_device, physical_device_properties) =
init_physical_device_and_properties(&instance)?;
let queue_families = QueueFamilies::init(&instance, physical_device, &surfaces)?;
let (logical_device, queues) =
init_device_and_queues(&instance, physical_device, &queue_families, &layer_names)?;
let swapchain = SwapchainDongXi::init(
&instance,
physical_device,
&logical_device,
&surfaces,
&queue_families,
&queues,
)?;
Ok(Aetna {
window,
entry,
instance,
debug: std::mem::ManuallyDrop::new(debug),
surfaces: std::mem::ManuallyDrop::new(surfaces),
physical_device,
physical_device_properties,
queue_families,
queues,
device: logical_device,
swapchain,
})
}
}
impl Drop for Aetna {
fn drop(&mut self) {
unsafe {
self.swapchain.cleanup(&self.device);
self.device.destroy_device(None);
std::mem::ManuallyDrop::drop(&mut self.surfaces);
std::mem::ManuallyDrop::drop(&mut self.debug);
self.instance.destroy_instance(None)
};
}
}
이제, main()은 좋아 보입니다. “코드가 너무 많다…“는 측면은 이제 “…파일 안에”로 옮겨갔지만, 이 정리 작업은 나중으로 미루겠습니다. 대신, 우리는 여전히 프로그램이 즉시 종료되는 문제를 해결해야 합니다. 즉, 우리가 만든 이벤트 루프를 실제로 실행해야 합니다. 그것을 실행하는 절대적으로 최소한의 (비정상적으로 최소한의) 버전은 아마도 다음과 같을 것입니다:
use winit::event::{Event, WindowEvent};
eventloop.run(move |event, _, controlflow| match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => {
*controlflow = winit::event_loop::ControlFlow::Exit;
}
_ => {}
});
이 루프는 우리가 창을 닫을 때까지 실행됩니다. 하지만 모든 것이 잘된 것은 아닙니다: 유효성 검사 레이어의 메시지를 따라가 보면, 마지막에 “destroy...” 함수들이 호출되지 않는 것을 알 수 있습니다. 우리가 Aetna를 드롭하지 않고 있는 걸까요? 그렇습니다. 이벤트 루프의 run() 메서드는 다소 특별해서 반환되지 않습니다 (프로그램에 “이벤트 루프 이후”는 없습니다). 그리고 이것이 winit 문서가 우리에게 말해주는 것입니다: “이 함수에 전달되지 않은 모든 값은 드롭되지 않습니다.” 그러니, 우리는 놀라지 말고 우리 구조체를 클로저 안으로 전달해서 드롭되도록 해야 합니다.
예를 들어, 이렇게 만들 수 있습니다:
eventloop.run(move |event, _, controlflow| match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => {
let a = &aetna;
*controlflow = winit::event_loop::ControlFlow::Exit;
}
_ => {}
});
그러면 구조체는 이벤트 루프로 들어가고, 우리는 그것이 드롭되는 것을 관찰할 수 있습니다. 여기서 aetna에 접근하는 다른 어떤 방법도 작동했을 것입니다 — 제 생각에, 이것은 프로그램이 실제로 무언가를 하기 시작하면 자동으로 사라졌을 문제입니다. “match event“는 키보드 입력, 마우스 움직임 등과 같은 모든 종류의 이벤트를 처리하도록 커질 것입니다. 우리가 그래픽 애플리케이션으로 나아가고 있다는 것을 볼 때, 우리는 확실히 그리기 이벤트, 즉 창을 채워달라는 요청을 처리하는 데 관심이 있을 것입니다. RedrawRequested 이벤트를 처리해야 합니다:
Event::RedrawRequested(_) => {
// 여기서 렌더링 (나중에)
}
언제 다시 그려야 할까요? 다른 모든 작업이 완료되었을 때 (모든 키 입력이 처리되는 등, 즉 MainEvents가 Cleared될 때) 그리고 업데이트 로직이 실행된 후에 (아직 아무것도 없지만) 입니다. 우리는 창의 다시 그리기를 요청합니다:
Event::MainEventsCleared => {
// 여기서 작업 수행 (나중에)
aetna.window.request_redraw();
}
그리고 사실 이것은 우리가 이미 가짜 “let a=&aetna“를 제거하고도 마지막에 파괴를 호출할 수 있다는 것을 의미합니다. 지금으로서는 이것으로 충분합니다.
마지막으로 현재의 전체 main()을 살펴본 후, 우리는 그래픽 주제로 돌아갈 것입니다.
fn main() -> Result<(), Box<dyn std::error::Error>> {
let eventloop = winit::event_loop::EventLoop::new();
let window = winit::window::Window::new(&eventloop)?;
let aetna = Aetna::init(window)?;
use winit::event::{Event, WindowEvent};
eventloop.run(move |event, _, controlflow| match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => {
*controlflow = winit::event_loop::ControlFlow::Exit;
}
Event::MainEventsCleared => {
// 여기서 작업 수행 (나중에)
aetna.window.request_redraw();
}
Event::RedrawRequested(_) => {
// 여기서 렌더링 (나중에)
}
_ => {}
});
// 이 코드는 eventloop.run()이 반환되지 않으므로 실제로 실행되지 않습니다.
// 하지만, main 함수 시그니처를 만족시키기 위해 Ok(())를 남겨둡니다.
// winit 0.29부터 eventloop.run()은 Never 타입 (`!`)을 반환하지 않으므로
// 기술적으로는 도달 가능하지만, 일반적인 데스크톱 타겟에서는 그렇지 않습니다.
Ok(())
}