ashen-aetna-ko

잿빛 에트나

— 잿더미 화산 위에서 어설프게 비틀거리기

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

우리는 장치를 생성했고, 명령어를 제출할 수 있는 무언가(큐)도 가지고 있습니다. (물론, 커맨드 버퍼를 미리 준비했다면 말이죠. 그리고 대부분의 명령어는 아마 추가적인 정보가 필요할 겁니다. 파이프라인이 생성되지 않았다면, 그게 무엇이든 간에, ‘파이프라인을 바인딩하는’ 것은 어렵습니다…) — 하지만, 분명히 빠진 것이 있습니다. 바로 창, 무언가를 그릴 대상입니다. 사실, 창은 선택 사항입니다. 우리는 Vulkan과 GPU를 오직 연산 능력 때문에 사용할 수도 있습니다. 하지만 보통은 그렇지 않죠. 이 튜토리얼은 결국 그래픽스에 관한 것이니까요. 그러므로, 창을 하나 만들고 그 안에 그리는 과정에 더 가까이 다가가 봅시다.

우선 창부터 만들어 봅시다. 이것은 Vulkan의 역할이 아닙니다. 대신 winit 크레이트를 사용할 것입니다. Cargo.tomlwinit="0.22.0" 한 줄을 추가하고, main 함수의 시작 부분에 다음 코드를 삽입합니다:

    let eventloop = winit::event_loop::EventLoop::new();
    let window = winit::window::Window::new(&eventloop)?;

이제 프로그램을 실행하면 창이 열렸다가 바로 닫힐 것입니다. 이 창에 렌더링하려면, 방금 만든 창을 우리 프로그램의 Vulkan 부분에 알려주어야 합니다. 즉, 이 창 ‘표면(surface)’이 존재하고 우리가 그것을 사용하고 싶다는 것을 어떻게든 알려야 합니다. 이는 운영체제와 좀 더 밀접한 상호작용이며, 이는 여러분이 사용하는 운영체제에 따라 세부 사항이 달라질 수 있음을 의미합니다. 만약 리눅스를 사용한다면 다음 코드가 동작할 확률이 높습니다. 다른 운영체제를 사용한다면, 다른 예제나 튜토리얼을 잠시 살펴보고 이어지는 몇 개의 단락을 그에 맞게 수정해야 합니다. (저는 다른 플랫폼을 테스트할 방법이 없으므로, 제 컴퓨터에서 실행되는 한 가지 버전에 대해서만 다루겠습니다.)

Vulkan 쪽에서는, 이 모든 표면 관련 작업을 처리하기 위해 또 다른 확장이 필요합니다. winit 쪽에서는, 창의 내부 정보 중 일부를 추출해야 합니다. 디버그 메신저를 생성하는 부분과 물리 장치를 다루는 코드 사이에 다음 코드를 작성해 봅시다:

    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);

처음 두 할당문은 창의 내부 정보를 다룹니다. 이를 위해서는 특정 트레이트(trait)가 스코프 내에 있어야 하므로 use 구문이 필요합니다. 그런 다음 CreateInfo 구조체를 채우고, xlib_surface_loader를 생성하여 표면(surface)을 얻습니다. 그리고 표면 관련 함수들을 사용하기 위한 surface_loader도 함께 만듭니다.

지금 당장 프로그램을 실행하면 Unable to load create_xlib_surface_khr라는 패닉(panic)이 발생합니다. 이 확장들도 로드해야 합니다! 이는 이전에 작성했던 코드의 일부를 약간 수정해야 한다는 의미입니다:

    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(),
    ];

그리고 마지막에는 다음 코드도 추가해야 합니다.

        surface_loader.destroy_surface(surface, None);

좋습니다. 이제 우리가 선택한 큐(패밀리)가 이 표면을 다루고 그 위에 그릴 수도 있다면 좋겠죠. 그래픽스 큐를 찾는 부분에서 이 내용을 확인하도록 하겠습니다.

	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);
            }

여기에는 (이론적으로) 표면에 제시(presenting)하는 데 사용할 수 있는 (유일한) 큐와 “GRAPHICS” 그리기가 가능한 (유일한) 큐가 서로 다를 수 있다는 경고가 따릅니다. 여기서는 그렇지 않고, 이 코드는 튜토리얼용 코드일 뿐이므로, 이 잠재적인 문제는 무시하겠습니다.

계속