화면에 첫 점 하나를 그리기까지는 긴 여정이었습니다. 상당히 많은 설정이 필요했기 때문에 세부 사항에 파묻히기 쉽습니다. 세부 사항은 잠시 접어두고 한번 되돌아봅시다: 우리는 무엇을 했고, 중요한 지점은 무엇이었을까요?
우리는 그리기를 원했습니다. 어딘가에는 말 그대로 “그리기” 명령이 있습니다. 아마 여기서부터 시작해야 할 것 같습니다:
logical_device.cmd_draw(commandbuffer, 1, 1, 0, 0);
두 가지 명백한 질문이 떠오릅니다: 1. 이 명령을 어떻게 GPU로 전달할까요? 2. “그린다”는 것은 무엇을 의미할까요? 더 자세히 나눠보면: 2a. 무엇을 그리고 싶은가? 2b. 그릴 대상에 대한 설명 외에, 화면에 보이게 하려면 무엇이 필요한가?
2b. 우선, 화면, 즉 그릴 대상이 있어야 합니다. 우리는 winit으로 창을 설정하기 위해 Vulkan에서 잠시 벗어나, 이 창을 “서피스(surface)”로 Vulkan이 사용할 수 있게 만들었습니다. 이 서피스와 연관지어 — 현재 보이는 화면에 직접 그리는 대신, 그림을 미리 준비한 다음 보여주기 위해 — 스왑체인(swapchain)을 생성했습니다. 이 스왑체인은 여러 개의 이미지(image)로 구성되어 있으며, 이 이미지들을 그리기 명령의 영향을 받는 대상과 연결하기 위해 “이미지 뷰(imageview)”(데이터 해석 방법에 대한 정보)와 프레임버퍼(framebuffer)가 필요했습니다 (자세한 내용은 2a에서 다룹니다). 스왑체인을 사용하는 과정은 “다음 이미지 획득”, “그리기 명령 제출”, “이미지 제시”의 반복이었습니다. 이 단계들은 올바른 순서로 발생해야 하므로, 처리해야 할 약간의 동기화(synchronisation) 문제가 있었습니다.
그 밖에 무엇이 있었을까요? 물론, 추가적인 설정 작업이 더 있었습니다. 큐를 비롯해 우리가 필요했던 다른 모든 것들이 존재하고 접근 가능해지기 전에, 그것들을 생성해야만 했습니다. 생성 과정은 Vulkan 인스턴스(instance)를 찾고, 물리 디바이스(physical device)를 선택한 다음, 논리 디바이스(logical device)(이후의 거의 모든 Vulkan 호출이 통과하게 될 추상화 계층)를 설정하는 것으로 시작됩니다.
우리의 목표가 상당히 소박했음에도 불구하고, 우리는 이미 Vulkan의 핵심 기능을 넘어서는 영역까지 모험을 해야 했습니다. 이를 위한 다른 방법들은 a) 추가적인 “레이어(layer)”(애플리케이션과 드라이버 “사이”에 위치)를 사용하는 것과, b) “확장(extension)”을 사용하는 것이었습니다. 우리는 a)를 검증 레이어(validation layer)에서, 그리고 b)를 디버그와 서피스(플랫폼에 따라 다르기 때문), 그리고 스왑체인(이 확장은 단일 디바이스 수준의 확장이지만요 — 우리가 여러 디바이스를 사용할 것도 아닌데 말이죠)의 맥락에서 마주쳤습니다. 각 확장은 모든 관련 함수를 메소드로 가지고 있는 로더(loader)와 함께 제공됩니다.