우리는 삼각형을 그려보았습니다. 하지만 삼각형은 거의 3D 객체라고 할 수 없죠. 가장 간단한 진짜 3D 객체는 아마 정육면체일 것입니다. (물론, 어떤 면에서는 사면체가 더 간단할 수도 있습니다. 하지만 사면체로 가진 맙시다.) 사실 저는 구(sphere)를 더 기대하고 있습니다.
상자를 그리는 것은 그리 어렵지 않을 겁니다. 정점 버퍼에 점들을 추가하는 방법을 알고 있으니, 삼각형 몇 개를 더 넣으면 쉽게 끝낼 수 있습니다.
저는 이번 기회를 통해 정육면체나, 더 일반적으로 3D 모델이 코드에서 어떻게 표현되는지 정리하기 시작하고 싶습니다. (이번에는 그래픽과 Vulkan 이야기는 조금 줄어듭니다.)
그리고 씬(scene)에 새로운 정육면체를 추가할 때마다 정점 버퍼를 일일이 손으로 채워 넣고 싶지는 않습니다.
모델을 생각할 때, 저는 그 자체로 다소 복잡할 수 있고(분명 상자보다는 더), 아마도 매우 많이 존재할 수 있는(독특한 몬스터보다는 RTS 군대의 나무나 기본 유닛을 생각해보세요) 어떤 것을 상상합니다. 이들 모두가 동시에 보이는 것은 아니며(카메라/자신 뒤에 멀리 있는 것을 그리는 데 시간을 낭비하고 싶지 않습니다 — 더 나아가: 그냥 “그리지 않는” 것뿐만 아니라, GPU에게 그것이 존재한다는 사실조차 알리고 싶지 않습니다 — 우리가 가진 모델이 거의 없으니 아직은 이런 생각을 하기엔 너무 이를 수도 있지만, 뭐, 문제 될 건 없잖아요?).
정점(vertex) 데이터와 인스턴스(instance) 데이터(이전처럼; 인스턴스당 [f32;4] 위치와 정점당 [f32;5] 크기 및 색상)로 분리한 것은 이미 이 문제에 많은 도움이 됩니다. 우리는 인스턴스를 보이는 부분과 보이지 않는 부분으로 나누기만 하면 됩니다.
아마 이런 식이겠죠?
struct Model {
vertexdata: Vec<[f32; 5]>,
visible_instances: Vec<[f32; 4]>,
invisible_instances: Vec<[f32; 4]>,
vertexbuffer: Buffer,
instancebuffer: Buffer,
}
(모델과 밀접하게 연결된 버퍼들도 포함시켰습니다.)
계속하기 전에: 저는 저 [f32;4]와 [f32;5]를 계속 유지할 자신이 별로 없습니다. 사실, 점 크기(point size) 변수는 이미 꽤 쓸모가 없어졌습니다. 이 구조체를 좀 더 일반적인 형태로 유지합시다.
struct Model<V, I> {
vertexdata: Vec<V>,
visible_instances: Vec<I>,
invisible_instances: Vec<I>,
vertexbuffer: Buffer,
instancebuffer: Buffer,
}
이 구조는 외부에서 인스턴스를 참조하기 어렵게 만듭니다. 그리고 어느 시점에는 분명 이를 할 수 있어야 할 겁니다(“이 유닛이 죽었으니 모델을 제거해야 한다.”). “보이는 것, 5번”과 같은 레이블을 사용하는 것은 “보이는 것 3번”을 “보이지 않는 것”으로 바꾸고 visible_instances의 요소들이 이동하는 순간 깨져버립니다.
새로운 아이디어: 외부에서 사용할 수 있는 “핸들(handle)”을 저장하고 나눠주며 추적하여 참조가 가능하게 합시다. 그러면 보이는 인스턴스와 보이지 않는 인스턴스를 같은 Vec에 보관할 수도 있습니다. (예를 들어, 보이는 것을 먼저, 보이지 않는 것을 그 뒤에 두는 식으로요.)
저는 ‘핸들’을 외부에서 접근 가능한 번호로, ‘인덱스’를 내부적으로 instances 벡터에 접근하는 데 필요한 번호로 부르겠습니다.
struct Model<V, I> {
vertexdata: Vec<V>,
handle_to_index: std::collections::HashMap<usize, usize>,
instances: Vec<I>,
first_invisible: usize,
vertexbuffer: Buffer,
instancebuffer: Buffer,
}
여기에 다음에 사용할 핸들과 이미 사용된 핸들을 추적하는 기능도 추가합시다:
struct Model<V, I> {
vertexdata: Vec<V>,
handle_to_index: std::collections::HashMap<usize, usize>,
handles: Vec<usize>,
instances: Vec<I>,
first_invisible: usize,
next_handle: usize,
vertexbuffer: Buffer,
instancebuffer: Buffer,
}
우리는 핸들을 통해 인스턴스에 접근합니다. 이 간접성은 단일 요소에 접근할 때 약간의 오버헤드가 있지만, 모든 (관련) 데이터를 바로 옆에 두는 것은 그럴 만한 가치가 있을 겁니다 (프로파일링은 안 해봤지만, 그러길 바랍니다).
자, 요소에 접근하는 방법입니다:
impl<V, I> Model<V, I> {
fn get(&self, handle: usize) -> Option<&I> {
if let Some(&index) = self.handle_to_index.get(&handle) {
self.instances.get(index)
} else {
None
}
}
fn get_mut(&mut self, handle: usize) -> Option<&mut I> {
if let Some(&index) = self.handle_to_index.get(&handle) {
self.instances.get_mut(index)
} else {
None
}
}
}
벡터 내의 두 요소 위치를 바꿀 수 있도록 해봅시다 (예를 들어 “보이는” 부분과 “보이지 않는” 부분 사이를 전환하기 위해). 이를 위해서는 약간의 장부 정리가 필요합니다. (그리고 이 목적을 위해 두 개의 함수를 도입할 건데, 하나는 핸들을 받고 다른 하나는 인덱스를 받습니다 — 후자는 내부 헬퍼 함수에 가깝습니다.)
fn swap_by_handle(&mut self, handle1: usize, handle2: usize) -> Result<(), InvalidHandle> {
if handle1 == handle2 {
return Ok(());
}
if let (Some(&index1), Some(&index2)) = (
self.handle_to_index.get(&handle1),
self.handle_to_index.get(&handle2),
) {
self.handles.swap(index1, index2);
self.instances.swap(index1, index2);
self.handle_to_index.insert(index1, handle2);
self.handle_to_index.insert(index2, handle1);
Ok(())
} else {
Err(InvalidHandle)
}
}
fn swap_by_index(&mut self, index1: usize, index2: usize) {
if index1 == index2 {
return;
}
let handle1 = self.handles[index1];
let handle2 = self.handles[index2];
self.handles.swap(index1, index2);
self.instances.swap(index1, index2);
self.handle_to_index.insert(index1, handle2);
self.handle_to_index.insert(index2, handle1);
}
잘못될 수 있는 상황: 존재하지 않는 핸들로 요소를 요청하는 경우. 저는 이것을 InvalidHandle 오류로 표현하기로 했습니다.
#[derive(Debug, Clone)]
struct InvalidHandle;
impl std::fmt::Display for InvalidHandle {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "invalid handle")
}
}
impl std::error::Error for InvalidHandle {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
가시성(visibility)은 요소가 벡터의 첫 부분에 있는지 확인하여 검사합니다. 무언가를 보이게 하거나 보이지 않게 하는 것은 해당 요소를 벡터의 해당 부분으로 정렬하는 것입니다.
fn is_visible(&self, handle: usize) -> Result<bool, InvalidHandle> {
if let Some(index) = self.handle_to_index.get(&handle) {
Ok(index < &self.first_invisible)
} else {
Err(InvalidHandle)
}
}
fn make_visible(&mut self, handle: usize) -> Result<(), InvalidHandle> {
// 이미 보이면: 아무것도 하지 않음
if let Some(&index) = self.handle_to_index.get(&handle) {
if index < self.first_invisible {
return Ok(());
}
// 아니면: first_invisible 위치로 이동하고 first_invisible 값을 증가시킴
self.swap_by_index(index, self.first_invisible);
self.first_invisible += 1;
Ok(())
} else {
Err(InvalidHandle)
}
}
fn make_invisible(&mut self, handle: usize) -> Result<(), InvalidHandle> {
// 이미 보이지 않으면: 아무것도 하지 않음
if let Some(&index) = self.handle_to_index.get(&handle) {
if index >= self.first_invisible {
return Ok(());
}
// 아니면: first_invisible 바로 앞 위치로 이동하고 first_invisible 값을 감소시킴
self.swap_by_index(index, self.first_invisible - 1);
self.first_invisible -= 1;
Ok(())
} else {
Err(InvalidHandle)
}
}
삽입(새 핸들을 반환)과 제거(요소를 반환)는 약간의 장부 정리를 해야 합니다.
fn insert(&mut self, element: I) -> usize {
let handle = self.next_handle;
self.next_handle += 1;
let index = self.instances.len();
self.instances.push(element);
self.handles.push(handle);
self.handle_to_index.insert(handle, index);
handle
}
fn insert_visibly(&mut self, element: I) -> usize {
let new_handle = self.insert(element);
self.make_visible(new_handle).ok(); // 바로 앞 줄에서 삽입했으므로 실패할 수 없음
new_handle
}
fn remove(&mut self, handle: usize) -> Result<I, InvalidHandle> {
if let Some(&index) = self.handle_to_index.get(&handle) {
if index < self.first_invisible {
self.swap_by_index(index, self.first_invisible - 1);
self.first_invisible -= 1;
}
self.swap_by_index(self.first_invisible, self.instances.len() - 1);
self.handles.pop();
self.handle_to_index.remove(&handle);
// 인덱스를 찾을 수 있었으므로 반드시 Some()이어야 함
Ok(self.instances.pop().unwrap())
} else {
Err(InvalidHandle)
}
}
좋습니다. 다음은 버퍼입니다. 처음부터 존재하지 않고, 데이터로부터 명령에 따라 생성하게 하는 것이 좋은 생각일 수 있습니다.
Option으로 만듭시다.
struct Model {
vertexdata: Vec<V>,
handle_to_index: std::collections::HashMap<usize, usize>,
handles: Vec<usize>,
instances: Vec<I>,
first_invisible: usize,
next_handle: usize,
vertexbuffer: Option<Buffer>,
instancebuffer: Option<Buffer>,
}
그리고 정점 버퍼 생성 (더 이상 바이트를 손으로 세지 않아도 됩니다, 야호!):
fn update_vertexbuffer(
&mut self,
allocator: &vk_mem::Allocator,
) -> Result<(), vk_mem::error::Error> {
if let Some(buffer) = &self.vertexbuffer {
buffer.fill(allocator, &self.vertexdata)?;
Ok(())
} else {
let bytes = (self.vertexdata.len() * std::mem::size_of::<V>()) as u64;
let buffer = Buffer::new(
&allocator,
bytes,
vk::BufferUsageFlags::VERTEX_BUFFER,
vk_mem::MemoryUsage::CpuToGpu,
)?;
buffer.fill(allocator, &self.vertexdata)?;
self.vertexbuffer = Some(buffer);
Ok(())
}
}
아직 버퍼가 없다면, 올바른 크기로 하나 생성합니다. 버퍼가 이미 존재한다면, 새 데이터를 채워 넣습니다.
“데이터 채워 넣기”에 대해 말하자면, 이것은 우리가 바로 이전에 생성되지 않은 버퍼에 데이터를 채워 넣는 첫 번째 경우입니다. Buffer::fill을 다시 한번 살펴봅시다:
fn fill<T: Sized>(
&self,
allocator: &vk_mem::Allocator,
data: &[T],
) -> Result<(), vk_mem::error::Error> {
let data_ptr = allocator.map_memory(&self.allocation)? as *mut T;
unsafe { data_ptr.copy_from_nonoverlapping(data.as_ptr(), data.len()) };
allocator.unmap_memory(&self.allocation)?;
Ok(())
}
제가 보는 문제가 여러분도 보이시나요? 우리가 보내는 데이터가 올바른 크기인지, 너무 큰지 전혀 확인하지 않습니다. 공간보다 더 많은 데이터를 쓴다고요? 이건 문제의 지름길처럼 들립니다. 그러지 맙시다.
첫 번째 질문: 버퍼의 현재 크기를 어떻게 알 수 있을까요? 답변: 버퍼 생성 시에 알고 있습니다. 그냥 그때 Buffer 구조체의 다른 멤버로 저장합시다. 사실, allocation_info.get_size()도 있습니다. 이것은 기반 메모리 할당의 크기를 알려줍니다. (하지만 BufferCreateInfo의 다른 크기가 얼마나 영향을 미칠지 확신할 수 없으므로, 안전하게 갑시다.)
두 번째 질문: data의 크기를 바이트 단위로 어떻게 알 수 있을까요? 답변:
쉽게 계산할 수 있습니다 (사실, 정점 버퍼를 만드는 함수에서도 본질적으로 같은 것을 사용했습니다):
let bytes_to_write = data.len() * std::mem::size_of::<T>();
세 번째 질문: 새 데이터가 너무 많으면 어떻게 해야 할까요? 추가 데이터를 무시할까요? (가능하지만, 그러지 않는 게 좋습니다.) 대신 충분히 큰 새 버퍼를 만들고, 기존 버퍼는 파괴하도록 합시다. 청소 의무를 소홀히 하고 싶진 않겠죠? (비록 다음 변경 사항이 나중에 문제를 일으킬 것이라고 예상하지만요.)
fn fill<T: Sized>(
&mut self,
allocator: &vk_mem::Allocator,
data: &[T],
) -> Result<(), vk_mem::error::Error> {
let bytes_to_write = (data.len() * std::mem::size_of::<T>()) as u64;
if bytes_to_write > self.size_in_bytes {
allocator.destroy_buffer(self.buffer, &self.allocation);
let newbuffer = Buffer::new(
allocator,
bytes_to_write,
self.buffer_usage,
self.memory_usage,
)?;
*self = newbuffer;
}
let data_ptr = allocator.map_memory(&self.allocation)? as *mut T;
unsafe { data_ptr.copy_from_nonoverlapping(data.as_ptr(), data.len()) };
allocator.unmap_memory(&self.allocation)?;
Ok(())
}
새 버퍼를 생성하려면 버퍼 및 메모리 사용량에 대해 알아야 합니다 — Buffer에 필드로 추가하겠습니다. 이 함수의 변경 사항은 또한 이제 &self 대신 &mut self를 받는다는 것을 의미합니다. (컴파일러가 이 결과로 다른 코드를 어디서 변경해야 하는지 알려줄 겁니다.)
좋습니다, 다시 Model로 돌아가서 update_instancebuffer 함수도 추가합시다.
fn update_instancebuffer(
&mut self,
allocator: &vk_mem::Allocator,
) -> Result<(), vk_mem::error::Error> {
if let Some(buffer) = &mut self.instancebuffer {
buffer.fill(allocator, &self.instances[0..self.first_invisible])?;
Ok(())
} else {
let bytes = (self.first_invisible * std::mem::size_of::<I>()) as u64;
let mut buffer = Buffer::new(
&allocator,
bytes,
vk::BufferUsageFlags::VERTEX_BUFFER,
vk_mem::MemoryUsage::CpuToGpu,
)?;
buffer.fill(allocator, &self.instances[0..self.first_invisible])?;
self.instancebuffer = Some(buffer);
Ok(())
}
}
이 버퍼들을 사용하는 그리기 작업도 Model의 함수로 만들어야 합니다 (물론, 그러면 논리 장치와 명령 버퍼가 필요합니다):
fn draw(&self, logical_device: &ash::Device, commandbuffer: vk::CommandBuffer) {
if let Some(vertexbuffer) = &self.vertexbuffer {
if let Some(instancebuffer) = &self.instancebuffer {
if self.first_invisible > 0 {
unsafe {
logical_device.cmd_bind_vertex_buffers(
commandbuffer,
0,
&[vertexbuffer.buffer],
&[0],
);
logical_device.cmd_bind_vertex_buffers(
commandbuffer,
1,
&[instancebuffer.buffer],
&[0],
);
logical_device.cmd_draw(
commandbuffer,
self.vertexdata.len() as u32,
self.first_invisible as u32,
0,
0,
);
}
}
}
}
}
fill_commandbuffers에서는 정점 버퍼 바인딩과 cmd_draw를 이 함수 호출로 대체할 수 있습니다. Model의 슬라이스를 전달한다고 가정합시다.
for m in models {
m.draw(logical_device, commandbuffer);
}
정점과 인스턴스에 어떤 데이터를 사용할지도 결정해야 합니다.
정점당: 위치, 당연히.
인스턴스당: 전역 위치 오프셋. 색상도? (단색 정육면체를 의미하게 되는데, 괜찮을 것 같습니다.)
점 크기는? 둘 다 아닙니다. 제거합시다.
위치는 어떻게 저장할까요? 여전히 [f32; 4]로? 네 번째 컴포넌트는 아무 데도 사용하지 않는데요. [f32; 3]을 사용하고 마지막 슬롯에는 자동으로 1을 채워 넣도록 합시다.
색상도 마지막 컴포넌트는 무시할 수 있을 것 같습니다. 투명도와 알파에 대해서는 별도로 이야기해야 할 것입니다.
셰이더에서:
#version 450
layout (location=0) in vec3 position;
layout (location=1) in vec3 position_offset;
layout (location=2) in vec3 colour;
layout (location=0) out vec4 colourdata_for_the_fragmentshader;
void main() {
gl_Position = vec4(position+position_offset,1.0);
colourdata_for_the_fragmentshader=vec4(colour,1.0);
}
(또한 vec4를 사용하여 vec3와 다른 float 하나로 네 개 컴포넌트를 가진 벡터를 만드는 방법, 그리고 그 전에 vec3끼리 연산을 수행할 수 있다는 점에 주목하세요.)
어트리뷰트 및 정점 바인딩 설명 측면에서 다음을 의미합니다:
let vertex_attrib_descs = [
vk::VertexInputAttributeDescription {
binding: 0,
location: 0,
offset: 0,
format: vk::Format::R32G32B32_SFLOAT,
},
vk::VertexInputAttributeDescription {
binding: 1,
location: 1,
offset: 0,
format: vk::Format::R32G32B32_SFLOAT,
},
vk::VertexInputAttributeDescription {
binding: 1,
location: 2,
offset: 12,
format: vk::Format::R32G32B32_SFLOAT,
},
];
let vertex_binding_descs = [
vk::VertexInputBindingDescription {
binding: 0,
stride: 12,
input_rate: vk::VertexInputRate::VERTEX,
},
vk::VertexInputBindingDescription {
binding: 1,
stride: 24,
input_rate: vk::VertexInputRate::INSTANCE,
},
];
그리고 위에서 일반화했던 타입에 대해: V = [f32; 3], I = [f32; 6].
정육면체를 만들어 봅시다:
impl Model<[f32; 3], [f32; 6]> {
fn cube() -> Model<[f32; 3], [f32; 6]> {
Model {
vertexdata: vec![],
handle_to_index: std::collections::HashMap::new(),
handles: Vec::new(),
instances: Vec::new(),
first_invisible: 0,
next_handle: 0,
vertexbuffer: None,
instancebuffer: None,
}
}
}
이것은 명백히 아직 점들이 빠져 있습니다. 포함시켜 봅시다. 먼저 점들을 정의합니다:
let lbf = [-0.1,0.1,0.0]; // lbf: 왼쪽-아래-앞
let lbb = [-0.1,0.1,0.1];
let ltf = [-0.1,-0.1,0.0];
let ltb = [-0.1,-0.1,0.1];
let rbf = [0.1,0.1,0.0];
let rbb = [0.1,0.1,0.1];
let rtf = [0.1,-0.1,0.0];
let rtb = [0.1,-0.1,0.1];
그런 다음 vertexdata에 삽입합니다. 예를 들어, 정육면체의 바닥 면은 “lbf-lbb-rbb”와 “lbf-rbb-rbf” 두 삼각형으로 구성되므로, vertexdata에 대한 줄은 다음과 같이 시작합니다: vertexdata: vec![lbf,lbb,rbb,lbf,rbb,rbf.
impl Model<[f32; 3], [f32; 6]> {
fn cube() -> Model<[f32; 3], [f32; 6]> {
let lbf = [-0.1, 0.1, 0.0]; // lbf: 왼쪽-아래-앞
let lbb = [-0.1, 0.1, 0.1];
let ltf = [-0.1, -0.1, 0.0];
let ltb = [-0.1, -0.1, 0.1];
let rbf = [0.1, 0.1, 0.0];
let rbb = [0.1, 0.1, 0.1];
let rtf = [0.1, -0.1, 0.0];
let rtb = [0.1, -0.1, 0.1];
Model {
vertexdata: vec![
lbf, lbb, rbb, lbf, rbb, rbf, // 바닥
ltf, rtb, ltb, ltf, rtf, rtb, // 윗면
lbf, rtf, ltf, lbf, rbf, rtf, // 앞면
lbb, ltb, rtb, lbb, rtb, rbb, // 뒷면
lbf, ltf, lbb, lbb, ltf, ltb, // 왼쪽
rbf, rbb, rtf, rbb, rtb, rtf, // 오른쪽
],
handle_to_index: std::collections::HashMap::new(),
handles: Vec::new(),
instances: Vec::new(),
first_invisible: 0,
next_handle: 0,
vertexbuffer: None,
instancebuffer: None,
}
}
}
상자!
실제로 하나 만들어 봅시다 (원점에 있는 빨간색 상자):
let mut cube = Model::cube();
cube.insert_visibly([0.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
cube.update_vertexbuffer(&allocator);
cube.update_instancebuffer(&allocator);
let models = vec![cube];
Aetna의 버퍼들을 이 Vec of models로 대체할 수 있습니다. drop 함수도 조정해야 합니다:
for m in &self.models {
if let Some(vb) = &m.vertexbuffer {
self.allocator
.destroy_buffer(vb.buffer, &vb.allocation)
.expect("problem with buffer destruction");
}
if let Some(ib) = &m.instancebuffer {
self.allocator
.destroy_buffer(ib.buffer, &ib.allocation)
.expect("problem with buffer destruction");
}
}
많은 코드가 변경되었습니다. 전체 소스 코드를 다시 한번 포함하는 것이 좋겠습니다. 셰이더부터:
#version 450
layout (location=0) in vec3 position;
layout (location=1) in vec3 position_offset;
layout (location=2) in vec3 colour;
layout (location=0) out vec4 colourdata_for_the_fragmentshader;
void main() {
gl_Position = vec4(position+position_offset,1.0);
colourdata_for_the_fragmentshader=vec4(colour,1.0);
}
그리고
#version 450
layout (location=0) out vec4 theColour;
layout (location=0) in vec4 data_from_the_vertexshader;
void main(){
theColour= data_from_the_vertexshader;
}
그리고 main.rs:
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 mut 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(_) => {
let (image_index, _) = unsafe {
aetna
.swapchain
.swapchain_loader
.acquire_next_image(
aetna.swapchain.swapchain,
std::u64::MAX,
aetna.swapchain.image_available[aetna.swapchain.current_image],
vk::Fence::null(),
)
.expect("image acquisition trouble")
};
unsafe {
aetna
.device
.wait_for_fences(
&[aetna.swapchain.may_begin_drawing[aetna.swapchain.current_image]],
true,
std::u64::MAX,
)
.expect("fence-waiting");
aetna
.device
.reset_fences(&[
aetna.swapchain.may_begin_drawing[aetna.swapchain.current_image]
])
.expect("resetting fences");
}
let semaphores_available =
[aetna.swapchain.image_available[aetna.swapchain.current_image]];
let waiting_stages = [vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT];
let semaphores_finished =
[aetna.swapchain.rendering_finished[aetna.swapchain.current_image]];
let commandbuffers = [aetna.commandbuffers[image_index as usize]];
let submit_info = [vk::SubmitInfo::builder()
.wait_semaphores(&semaphores_available)
.wait_dst_stage_mask(&waiting_stages)
.command_buffers(&commandbuffers)
.signal_semaphores(&semaphores_finished)
.build()];
unsafe {
aetna
.device
.queue_submit(
aetna.queues.graphics_queue,
&submit_info,
aetna.swapchain.may_begin_drawing[aetna.swapchain.current_image],
)
.expect("queue submission");
};
let swapchains = [aetna.swapchain.swapchain];
let indices = [image_index];
let present_info = vk::PresentInfoKHR::builder()
.wait_semaphores(&semaphores_finished)
.swapchains(&swapchains)
.image_indices(&indices);
unsafe {
aetna
.swapchain
.swapchain_loader
.queue_present(aetna.queues.graphics_queue, &present_info)
.expect("queue presentation");
};
aetna.swapchain.current_image =
(aetna.swapchain.current_image + 1) % aetna.swapchain.amount_of_images as usize;
}
_ => {}
});
}
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 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::PhysicalDeviceFeatures,
),
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) };
let features = unsafe { instance.get_physical_device_features(p) };
if properties.device_type == vk::PhysicalDeviceType::DISCRETE_GPU {
chosen = Some((p, properties, features));
}
}
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 features = vk::PhysicalDeviceFeatures::builder().fill_mode_non_solid(true);
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)
.enabled_features(&features);
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>,
framebuffers: Vec<vk::Framebuffer>,
surface_format: vk::SurfaceFormatKHR,
extent: vk::Extent2D,
image_available: Vec<vk::Semaphore>,
rendering_finished: Vec<vk::Semaphore>,
may_begin_drawing: Vec<vk::Fence>,
amount_of_images: u32,
current_image: usize,
}
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 extent = surface_capabilities.current_extent;
let surface_present_modes = surfaces.get_present_modes(physical_device)?;
let surface_format = *surfaces.get_formats(physical_device)?.first().unwrap();
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_format.format)
.image_color_space(surface_format.color_space)
.image_extent(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 amount_of_images = swapchain_images.len() as u32;
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);
}
let mut image_available = vec![];
let mut rendering_finished = vec![];
let mut may_begin_drawing = vec![];
let semaphoreinfo = vk::SemaphoreCreateInfo::builder();
let fenceinfo = vk::FenceCreateInfo::builder().flags(vk::FenceCreateFlags::SIGNALED);
for _ in 0..amount_of_images {
let semaphore_available =
unsafe { logical_device.create_semaphore(&semaphoreinfo, None) }?;
let semaphore_finished =
unsafe { logical_device.create_semaphore(&semaphoreinfo, None) }?;
image_available.push(semaphore_available);
rendering_finished.push(semaphore_finished);
let fence = unsafe { logical_device.create_fence(&fenceinfo, None) }?;
may_begin_drawing.push(fence);
}
Ok(SwapchainDongXi {
swapchain_loader,
swapchain,
images: swapchain_images,
imageviews: swapchain_imageviews,
framebuffers: vec![],
surface_format,
extent,
amount_of_images,
current_image: 0,
image_available,
rendering_finished,
may_begin_drawing,
})
}
fn create_framebuffers(
&mut self,
logical_device: &ash::Device,
renderpass: vk::RenderPass,
) -> Result<(), vk::Result> {
for iv in &self.imageviews {
let iview = [*iv];
let framebuffer_info = vk::FramebufferCreateInfo::builder()
.render_pass(renderpass)
.attachments(&iview)
.width(self.extent.width)
.height(self.extent.height)
.layers(1);
let fb = unsafe { logical_device.create_framebuffer(&framebuffer_info, None) }?;
self.framebuffers.push(fb);
}
Ok(())
}
unsafe fn cleanup(&mut self, logical_device: &ash::Device) {
for fence in &self.may_begin_drawing {
logical_device.destroy_fence(*fence, None);
}
for semaphore in &self.image_available {
logical_device.destroy_semaphore(*semaphore, None);
}
for semaphore in &self.rendering_finished {
logical_device.destroy_semaphore(*semaphore, None);
}
for fb in &self.framebuffers {
logical_device.destroy_framebuffer(*fb, None);
}
for iv in &self.imageviews {
logical_device.destroy_image_view(*iv, None);
}
self.swapchain_loader
.destroy_swapchain(self.swapchain, None)
}
}
fn init_renderpass(
logical_device: &ash::Device,
physical_device: vk::PhysicalDevice,
format: vk::Format,
) -> Result<vk::RenderPass, vk::Result> {
let attachments = [vk::AttachmentDescription::builder()
.format(format)
.load_op(vk::AttachmentLoadOp::CLEAR)
.store_op(vk::AttachmentStoreOp::STORE)
.stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
.stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
.initial_layout(vk::ImageLayout::UNDEFINED)
.final_layout(vk::ImageLayout::PRESENT_SRC_KHR)
.samples(vk::SampleCountFlags::TYPE_1)
.build()];
let color_attachment_references = [vk::AttachmentReference {
attachment: 0,
layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
}];
let subpasses = [vk::SubpassDescription::builder()
.color_attachments(&color_attachment_references)
.pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)
.build()];
let subpass_dependencies = [vk::SubpassDependency::builder()
.src_subpass(vk::SUBPASS_EXTERNAL)
.src_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT)
.dst_subpass(0)
.dst_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT)
.dst_access_mask(
vk::AccessFlags::COLOR_ATTACHMENT_READ | vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
)
.build()];
let renderpass_info = vk::RenderPassCreateInfo::builder()
.attachments(&attachments)
.subpasses(&subpasses)
.dependencies(&subpass_dependencies);
let renderpass = unsafe { logical_device.create_render_pass(&renderpass_info, None)? };
Ok(renderpass)
}
struct Pipeline {
pipeline: vk::Pipeline,
layout: vk::PipelineLayout,
}
impl Pipeline {
fn cleanup(&self, logical_device: &ash::Device) {
unsafe {
logical_device.destroy_pipeline(self.pipeline, None);
logical_device.destroy_pipeline_layout(self.layout, None);
}
}
fn init(
logical_device: &ash::Device,
swapchain: &SwapchainDongXi,
renderpass: &vk::RenderPass,
) -> Result<Pipeline, vk::Result> {
let vertexshader_createinfo = vk::ShaderModuleCreateInfo::builder().code(
vk_shader_macros::include_glsl!("./shaders/shader.vert", kind: vert),
);
let vertexshader_module =
unsafe { logical_device.create_shader_module(&vertexshader_createinfo, None)? };
let fragmentshader_createinfo = vk::ShaderModuleCreateInfo::builder()
.code(vk_shader_macros::include_glsl!("./shaders/shader.frag"));
let fragmentshader_module =
unsafe { logical_device.create_shader_module(&fragmentshader_createinfo, None)? };
let mainfunctionname = std::ffi::CString::new("main").unwrap();
let vertexshader_stage = vk::PipelineShaderStageCreateInfo::builder()
.stage(vk::ShaderStageFlags::VERTEX)
.module(vertexshader_module)
.name(&mainfunctionname);
let fragmentshader_stage = vk::PipelineShaderStageCreateInfo::builder()
.stage(vk::ShaderStageFlags::FRAGMENT)
.module(fragmentshader_module)
.name(&mainfunctionname);
let shader_stages = vec![vertexshader_stage.build(), fragmentshader_stage.build()];
let vertex_attrib_descs = [
vk::VertexInputAttributeDescription {
binding: 0,
location: 0,
offset: 0,
format: vk::Format::R32G32B32_SFLOAT,
},
vk::VertexInputAttributeDescription {
binding: 1,
location: 1,
offset: 0,
format: vk::Format::R32G32B32_SFLOAT,
},
vk::VertexInputAttributeDescription {
binding: 1,
location: 2,
offset: 12,
format: vk::Format::R32G32B32_SFLOAT,
},
];
let vertex_binding_descs = [
vk::VertexInputBindingDescription {
binding: 0,
stride: 12,
input_rate: vk::VertexInputRate::VERTEX,
},
vk::VertexInputBindingDescription {
binding: 1,
stride: 24,
input_rate: vk::VertexInputRate::INSTANCE,
},
];
let vertex_input_info = vk::PipelineVertexInputStateCreateInfo::builder()
.vertex_attribute_descriptions(&vertex_attrib_descs)
.vertex_binding_descriptions(&vertex_binding_descs);
let input_assembly_info = vk::PipelineInputAssemblyStateCreateInfo::builder()
.topology(vk::PrimitiveTopology::TRIANGLE_LIST);
let viewports = [vk::Viewport {
x: 0.,
y: 0.,
width: swapchain.extent.width as f32,
height: swapchain.extent.height as f32,
min_depth: 0.,
max_depth: 1.,
}];
let scissors = [vk::Rect2D {
offset: vk::Offset2D { x: 0, y: 0 },
extent: swapchain.extent,
}];
let viewport_info = vk::PipelineViewportStateCreateInfo::builder()
.viewports(&viewports)
.scissors(&scissors);
let rasterizer_info = vk::PipelineRasterizationStateCreateInfo::builder()
.line_width(1.0)
.front_face(vk::FrontFace::COUNTER_CLOCKWISE)
.cull_mode(vk::CullModeFlags::NONE)
.polygon_mode(vk::PolygonMode::FILL);
let multisampler_info = vk::PipelineMultisampleStateCreateInfo::builder()
.rasterization_samples(vk::SampleCountFlags::TYPE_1);
let colourblend_attachments = [vk::PipelineColorBlendAttachmentState::builder()
.blend_enable(true)
.src_color_blend_factor(vk::BlendFactor::SRC_ALPHA)
.dst_color_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
.color_blend_op(vk::BlendOp::ADD)
.src_alpha_blend_factor(vk::BlendFactor::SRC_ALPHA)
.dst_alpha_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
.alpha_blend_op(vk::BlendOp::ADD)
.color_write_mask(
vk::ColorComponentFlags::R
| vk::ColorComponentFlags::G
| vk::ColorComponentFlags::B
| vk::ColorComponentFlags::A,
)
.build()];
let colourblend_info =
vk::PipelineColorBlendStateCreateInfo::builder().attachments(&colourblend_attachments);
let pipelinelayout_info = vk::PipelineLayoutCreateInfo::builder();
let pipelinelayout =
unsafe { logical_device.create_pipeline_layout(&pipelinelayout_info, None) }?;
let pipeline_info = vk::GraphicsPipelineCreateInfo::builder()
.stages(&shader_stages)
.vertex_input_state(&vertex_input_info)
.input_assembly_state(&input_assembly_info)
.viewport_state(&viewport_info)
.rasterization_state(&rasterizer_info)
.multisample_state(&multisampler_info)
.color_blend_state(&colourblend_info)
.layout(pipelinelayout)
.render_pass(*renderpass)
.subpass(0);
let graphicspipeline = unsafe {
logical_device
.create_graphics_pipelines(
vk::PipelineCache::null(),
&[pipeline_info.build()],
None,
)
.expect("A problem with the pipeline creation")
}[0];
unsafe {
logical_device.destroy_shader_module(fragmentshader_module, None);
logical_device.destroy_shader_module(vertexshader_module, None);
}
Ok(Pipeline {
pipeline: graphicspipeline,
layout: pipelinelayout,
})
}
}
struct Pools {
commandpool_graphics: vk::CommandPool,
commandpool_transfer: vk::CommandPool,
}
impl Pools {
fn init(
logical_device: &ash::Device,
queue_families: &QueueFamilies,
) -> Result<Pools, vk::Result> {
let graphics_commandpool_info = vk::CommandPoolCreateInfo::builder()
.queue_family_index(queue_families.graphics_q_index.unwrap())
.flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER);
let commandpool_graphics =
unsafe { logical_device.create_command_pool(&graphics_commandpool_info, None) }?;
let transfer_commandpool_info = vk::CommandPoolCreateInfo::builder()
.queue_family_index(queue_families.transfer_q_index.unwrap())
.flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER);
let commandpool_transfer =
unsafe { logical_device.create_command_pool(&transfer_commandpool_info, None) }?;
Ok(Pools {
commandpool_graphics,
commandpool_transfer,
})
}
fn cleanup(&self, logical_device: &ash::Device) {
unsafe {
logical_device.destroy_command_pool(self.commandpool_graphics, None);
logical_device.destroy_command_pool(self.commandpool_transfer, None);
}
}
}
fn create_commandbuffers(
logical_device: &ash::Device,
pools: &Pools,
amount: u32,
) -> Result<Vec<vk::CommandBuffer>, vk::Result> {
let commandbuf_allocate_info = vk::CommandBufferAllocateInfo::builder()
.command_pool(pools.commandpool_graphics)
.command_buffer_count(amount);
unsafe { logical_device.allocate_command_buffers(&commandbuf_allocate_info) }
}
fn fill_commandbuffers(
commandbuffers: &[vk::CommandBuffer],
logical_device: &ash::Device,
renderpass: &vk::RenderPass,
swapchain: &SwapchainDongXi,
pipeline: &Pipeline,
models: &Vec<Model<[f32; 3], [f32; 6]>>,
) -> Result<(), vk::Result> {
for (i, &commandbuffer) in commandbuffers.iter().enumerate() {
let commandbuffer_begininfo = vk::CommandBufferBeginInfo::builder();
unsafe {
logical_device.begin_command_buffer(commandbuffer, &commandbuffer_begininfo)?;
}
let clearvalues = [vk::ClearValue {
color: vk::ClearColorValue {
float32: [0.0, 0.0, 0.08, 1.0],
},
}];
let renderpass_begininfo = vk::RenderPassBeginInfo::builder()
.render_pass(*renderpass)
.framebuffer(swapchain.framebuffers[i])
.render_area(vk::Rect2D {
offset: vk::Offset2D { x: 0, y: 0 },
extent: swapchain.extent,
})
.clear_values(&clearvalues);
unsafe {
logical_device.cmd_begin_render_pass(
commandbuffer,
&renderpass_begininfo,
vk::SubpassContents::INLINE,
);
logical_device.cmd_bind_pipeline(
commandbuffer,
vk::PipelineBindPoint::GRAPHICS,
pipeline.pipeline,
);
for m in models {
m.draw(logical_device, commandbuffer);
}
logical_device.cmd_end_render_pass(commandbuffer);
logical_device.end_command_buffer(commandbuffer)?;
}
}
Ok(())
}
struct Buffer {
buffer: vk::Buffer,
allocation: vk_mem::Allocation,
allocation_info: vk_mem::AllocationInfo,
size_in_bytes: u64,
buffer_usage: vk::BufferUsageFlags,
memory_usage: vk_mem::MemoryUsage,
}
impl Buffer {
fn new(
allocator: &vk_mem::Allocator,
size_in_bytes: u64,
buffer_usage: vk::BufferUsageFlags,
memory_usage: vk_mem::MemoryUsage,
) -> Result<Buffer, vk_mem::error::Error> {
let allocation_create_info = vk_mem::AllocationCreateInfo {
usage: memory_usage,
..Default::default()
};
let (buffer, allocation, allocation_info) = allocator.create_buffer(
&ash::vk::BufferCreateInfo::builder()
.size(size_in_bytes)
.usage(buffer_usage)
.build(),
&allocation_create_info,
)?;
Ok(Buffer {
buffer,
allocation,
allocation_info,
size_in_bytes,
buffer_usage,
memory_usage,
})
}
fn fill<T: Sized>(
&mut self,
allocator: &vk_mem::Allocator,
data: &[T],
) -> Result<(), vk_mem::error::Error> {
let bytes_to_write = (data.len() * std::mem::size_of::<T>()) as u64;
if bytes_to_write > self.size_in_bytes {
allocator.destroy_buffer(self.buffer, &self.allocation);
let newbuffer = Buffer::new(
allocator,
bytes_to_write,
self.buffer_usage,
self.memory_usage,
)?;
*self = newbuffer;
}
let data_ptr = allocator.map_memory(&self.allocation)? as *mut T;
unsafe { data_ptr.copy_from_nonoverlapping(data.as_ptr(), data.len()) };
allocator.unmap_memory(&self.allocation)?;
Ok(())
}
}
#[derive(Debug, Clone)]
struct InvalidHandle;
impl std::fmt::Display for InvalidHandle {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "유효하지 않은 핸들")
}
}
impl std::error::Error for InvalidHandle {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
struct Model<V, I> {
vertexdata: Vec<V>,
handle_to_index: std::collections::HashMap<usize, usize>,
handles: Vec<usize>,
instances: Vec<I>,
first_invisible: usize,
next_handle: usize,
vertexbuffer: Option<Buffer>,
instancebuffer: Option<Buffer>,
}
impl<V, I> Model<V, I> {
fn get(&self, handle: usize) -> Option<&I> {
if let Some(&index) = self.handle_to_index.get(&handle) {
self.instances.get(index)
} else {
None
}
}
fn get_mut(&mut self, handle: usize) -> Option<&mut I> {
if let Some(&index) = self.handle_to_index.get(&handle) {
self.instances.get_mut(index)
} else {
None
}
}
fn is_visible(&self, handle: usize) -> Result<bool, InvalidHandle> {
if let Some(index) = self.handle_to_index.get(&handle) {
Ok(index < &self.first_invisible)
} else {
Err(InvalidHandle)
}
}
fn make_visible(&mut self, handle: usize) -> Result<(), InvalidHandle> {
// 이미 보이면: 아무것도 하지 않음
if let Some(&index) = self.handle_to_index.get(&handle) {
if index < self.first_invisible {
return Ok(());
}
// 아니면: first_invisible 위치로 이동하고 first_invisible 값을 증가시킴
self.swap_by_index(index, self.first_invisible);
self.first_invisible += 1;
Ok(())
} else {
Err(InvalidHandle)
}
}
fn make_invisible(&mut self, handle: usize) -> Result<(), InvalidHandle> {
// 이미 보이지 않으면: 아무것도 하지 않음
if let Some(&index) = self.handle_to_index.get(&handle) {
if index >= self.first_invisible {
return Ok(());
}
// 아니면: first_invisible 바로 앞 위치로 이동하고 first_invisible 값을 감소시킴
self.swap_by_index(index, self.first_invisible - 1);
self.first_invisible -= 1;
Ok(())
} else {
Err(InvalidHandle)
}
}
fn insert(&mut self, element: I) -> usize {
let handle = self.next_handle;
self.next_handle += 1;
let index = self.instances.len();
self.instances.push(element);
self.handles.push(handle);
self.handle_to_index.insert(handle, index);
handle
}
fn insert_visibly(&mut self, element: I) -> usize {
let new_handle = self.insert(element);
self.make_visible(new_handle).ok();
new_handle
}
fn remove(&mut self, handle: usize) -> Result<I, InvalidHandle> {
if let Some(&index) = self.handle_to_index.get(&handle) {
if index < self.first_invisible {
self.swap_by_index(index, self.first_invisible - 1);
self.first_invisible -= 1;
}
self.swap_by_index(self.first_invisible, self.instances.len() - 1);
self.handles.pop();
self.handle_to_index.remove(&handle);
// 인덱스를 찾을 수 있었으므로 반드시 Some()이어야 함
Ok(self.instances.pop().unwrap())
} else {
Err(InvalidHandle)
}
}
fn swap_by_handle(&mut self, handle1: usize, handle2: usize) -> Result<(), InvalidHandle> {
if handle1 == handle2 {
return Ok(());
}
if let (Some(&index1), Some(&index2)) = (
self.handle_to_index.get(&handle1),
self.handle_to_index.get(&handle2),
) {
self.handles.swap(index1, index2);
self.instances.swap(index1, index2);
self.handle_to_index.insert(index1, handle2);
self.handle_to_index.insert(index2, handle1);
Ok(())
} else {
Err(InvalidHandle)
}
}
fn swap_by_index(&mut self, index1: usize, index2: usize) {
if index1 == index2 {
return;
}
let handle1 = self.handles[index1];
let handle2 = self.handles[index2];
self.handles.swap(index1, index2);
self.instances.swap(index1, index2);
self.handle_to_index.insert(index1, handle2);
self.handle_to_index.insert(index2, handle1);
}
fn update_vertexbuffer(
&mut self,
allocator: &vk_mem::Allocator,
) -> Result<(), vk_mem::error::Error> {
if let Some(buffer) = &mut self.vertexbuffer {
buffer.fill(allocator, &self.vertexdata)?;
Ok(())
} else {
let bytes = (self.vertexdata.len() * std::mem::size_of::<V>()) as u64;
let mut buffer = Buffer::new(
&allocator,
bytes,
vk::BufferUsageFlags::VERTEX_BUFFER,
vk_mem::MemoryUsage::CpuToGpu,
)?;
buffer.fill(allocator, &self.vertexdata)?;
self.vertexbuffer = Some(buffer);
Ok(())
}
}
fn update_instancebuffer(
&mut self,
allocator: &vk_mem::Allocator,
) -> Result<(), vk_mem::error::Error> {
if let Some(buffer) = &mut self.instancebuffer {
buffer.fill(allocator, &self.instances[0..self.first_invisible])?;
Ok(())
} else {
let bytes = (self.first_invisible * std::mem::size_of::<I>()) as u64;
let mut buffer = Buffer::new(
&allocator,
bytes,
vk::BufferUsageFlags::VERTEX_BUFFER,
vk_mem::MemoryUsage::CpuToGpu,
)?;
buffer.fill(allocator, &self.instances[0..self.first_invisible])?;
self.instancebuffer = Some(buffer);
Ok(())
}
}
fn draw(&self, logical_device: &ash::Device, commandbuffer: vk::CommandBuffer) {
if let Some(vertexbuffer) = &self.vertexbuffer {
if let Some(instancebuffer) = &self.instancebuffer {
if self.first_invisible > 0 {
unsafe {
logical_device.cmd_bind_vertex_buffers(
commandbuffer,
0,
&[vertexbuffer.buffer],
&[0],
);
logical_device.cmd_bind_vertex_buffers(
commandbuffer,
1,
&[instancebuffer.buffer],
&[0],
);
logical_device.cmd_draw(
commandbuffer,
self.vertexdata.len() as u32,
self.first_invisible as u32,
0,
0,
);
}
}
}
}
}
}
impl Model<[f32; 3], [f32; 6]> {
fn cube() -> Model<[f32; 3], [f32; 6]> {
let lbf = [-0.1, 0.1, 0.0]; // lbf: 왼쪽-아래-앞
let lbb = [-0.1, 0.1, 0.1];
let ltf = [-0.1, -0.1, 0.0];
let ltb = [-0.1, -0.1, 0.1];
let rbf = [0.1, 0.1, 0.0];
let rbb = [0.1, 0.1, 0.1];
let rtf = [0.1, -0.1, 0.0];
let rtb = [0.1, -0.1, 0.1];
Model {
vertexdata: vec![
lbf, lbb, rbb, lbf, rbb, rbf, // 바닥
ltf, rtb, ltb, ltf, rtf, rtb, // 윗면
lbf, rtf, ltf, lbf, rbf, rtf, // 앞면
lbb, ltb, rtb, lbb, rtb, rbb, // 뒷면
lbf, ltf, lbb, lbb, ltf, ltb, // 왼쪽
rbf, rbb, rtf, rbb, rtb, rtf, // 오른쪽
],
handle_to_index: std::collections::HashMap::new(),
handles: Vec::new(),
instances: Vec::new(),
first_invisible: 0,
next_handle: 0,
vertexbuffer: None,
instancebuffer: 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,
physical_device_features: vk::PhysicalDeviceFeatures,
queue_families: QueueFamilies,
queues: Queues,
device: ash::Device,
swapchain: SwapchainDongXi,
renderpass: vk::RenderPass,
pipeline: Pipeline,
pools: Pools,
commandbuffers: Vec<vk::CommandBuffer>,
allocator: vk_mem::Allocator,
models: Vec<Model<[f32; 3], [f32; 6]>>,
}
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, physical_device_features) =
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 mut swapchain = SwapchainDongXi::init(
&instance,
physical_device,
&logical_device,
&surfaces,
&queue_families,
&queues,
)?;
let renderpass = init_renderpass(
&logical_device,
physical_device,
swapchain.surface_format.format,
)?;
swapchain.create_framebuffers(&logical_device, renderpass)?;
let pipeline = Pipeline::init(&logical_device, &swapchain, &renderpass)?;
let pools = Pools::init(&logical_device, &queue_families)?;
let allocator_create_info = vk_mem::AllocatorCreateInfo {
physical_device,
device: logical_device.clone(),
instance: instance.clone(),
..Default::default()
};
let allocator = vk_mem::Allocator::new(&allocator_create_info)?;
let mut cube = Model::cube();
cube.insert_visibly([0.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
cube.update_vertexbuffer(&allocator);
cube.update_instancebuffer(&allocator);
let models = vec![cube];
let commandbuffers =
create_commandbuffers(&logical_device, &pools, swapchain.amount_of_images)?;
fill_commandbuffers(
&commandbuffers,
&logical_device,
&renderpass,
&swapchain,
&pipeline,
&models,
)?;
Ok(Aetna {
window,
entry,
instance,
debug: std::mem::ManuallyDrop::new(debug),
surfaces: std::mem::ManuallyDrop::new(surfaces),
physical_device,
physical_device_properties,
physical_device_features,
queue_families,
queues,
device: logical_device,
swapchain,
renderpass,
pipeline,
pools,
commandbuffers,
allocator,
models,
})
}
}
impl Drop for Aetna {
fn drop(&mut self) {
unsafe {
self.device
.device_wait_idle()
.expect("something wrong while waiting");
for m in &self.models {
if let Some(vb) = &m.vertexbuffer {
self.allocator
.destroy_buffer(vb.buffer, &vb.allocation)
.expect("problem with buffer destruction");
}
if let Some(ib) = &m.instancebuffer {
self.allocator
.destroy_buffer(ib.buffer, &ib.allocation)
.expect("problem with buffer destruction");
}
}
self.allocator.destroy();
self.pools.cleanup(&self.device);
self.pipeline.cleanup(&self.device);
self.device.destroy_render_pass(self.renderpass, None);
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)
};
}
}
상자 두 개를 더 추가하는 것은 다음 두 줄을 추가하는 것만큼 간단합니다:
cube.insert_visibly([0.0, 0.25, 0.0, 0.6, 0.5, 0.0]);
cube.insert_visibly([0.0, 0.5, 0.0, 0.0, 0.5, 0.0]);
저처럼 여러분도 위치와 색상 중 어느 것이 먼저 오는지 기억하기 어려우신가요? [f32; 6]보다 약간 더 표현력 있는 타입을 사용하는 것이 더 좋지 않을까요?
물론, 더 나은 타입을 사용할 수 있습니다:
struct InstanceData {
position: [f32; 3],
colour: [f32; 3],
}
그러면 이런 식으로 상자를 생성합니다:
cube.insert_visibly(InstanceData {
position: [0.0, 0.0, 0.0],
colour: [1.0, 0.0, 0.0],
});
cube.insert_visibly(InstanceData {
position: [0.0, 0.25, 0.0],
colour: [0.6, 0.5, 0.0],
});
cube.insert_visibly(InstanceData {
position: [0.0, 0.5, 0.0],
colour: [0.0, 0.5, 0.0],
});
그리고 타입을 맞추기 위해, 모든 [f32; 6]를 InstanceData로 교체합니다. 그것만으로 충분합니다.
여기서 중요한 점은 데이터가 셰이더가 예상하는 순서대로 정확히 메모리에 배치되어야 한다는 것입니다. 예를 들어, 다른 필드 사이에 추가적인 패딩(padding)이 없어야 합니다. 때때로 구조체 선언에서 이를 보장해야 할 필요가 있을 수 있습니다.
이런 경우에는 C처럼 메모리에 모든 것을 배치하는 것이 도움이 될 수 있습니다:
#[repr(C)]
struct InstanceData {
position: [f32; 3],
colour: [f32; 3],
}
두 개의 [f32; 3]에 대해서는 불필요하다고 생각합니다. 하지만 최적화를 위해 Rust에서는 구조체의 메모리 레이아웃에 대해 어떠한 보장도 하지 않습니다. #[repr(C)]를 사용하는 것이 더 안전합니다.
정점 데이터도 비슷하게 처리할 수 있습니다. 하지만 [f32; 3]은 저에게는 충분히 편리합니다.