우리는 씬에서 움직이는 객체를 만드는 데 성공했습니다 (좋습니다, 움직이는 객체 하나뿐이지만 — 더 많은 객체에 적용하는 것은 어렵지 않습니다). 하지만 종종 움직여야 할 것은 객체뿐만 아니라 “플레이어”나 “카메라”이기도 합니다. 이번 장에서는 이를 어떻게 달성하는지 살펴보겠습니다.
준비: 24장의 모션 코드를 제거하여 MainEventsCleared가 다음과 같이 보이도록 합시다:
Event::MainEventsCleared => {
aetna.window.request_redraw();
}
그리고 씬에 대한 다음 설정을 포함합시다 (바라볼 큐브가 많도록 하기 위함입니다 - 그중에는 축을 나타내는 표현도 포함했습니다):
let mut aetna = Aetna::init(window)?;
let mut cube = Model::cube();
cube.insert_visibly(InstanceData {
modelmatrix: (na::Matrix4::new_translation(&na::Vector3::new(0.0, 0.0, 0.1))
* na::Matrix4::new_scaling(0.1))
.into(),
colour: [0.2, 0.4, 1.0],
});
cube.insert_visibly(InstanceData {
modelmatrix: (na::Matrix4::new_translation(&na::Vector3::new(0.05, 0.05, 0.0))
* na::Matrix4::new_scaling(0.1))
.into(),
colour: [1.0, 1.0, 0.2],
});
for i in 0..10 {
for j in 0..10 {
cube.insert_visibly(InstanceData {
modelmatrix: (na::Matrix4::new_translation(&na::Vector3::new(
i as f32 * 0.2 - 1.0,
j as f32 * 0.2 - 1.0,
0.5,
)) * na::Matrix4::new_scaling(0.03))
.into(),
colour: [1.0, i as f32 * 0.07, j as f32 * 0.07],
});
cube.insert_visibly(InstanceData {
modelmatrix: (na::Matrix4::new_translation(&na::Vector3::new(
i as f32 * 0.2 - 1.0,
0.0,
j as f32 * 0.2 - 1.0,
)) * na::Matrix4::new_scaling(0.02))
.into(),
colour: [i as f32 * 0.07, j as f32 * 0.07, 1.0],
});
}
}
cube.insert_visibly(InstanceData {
modelmatrix: (na::Matrix4::from_scaled_axis(na::Vector3::new(0.0, 0.0, 1.4))
* na::Matrix4::new_translation(&na::Vector3::new(0.0, 0.5, 0.0))
* na::Matrix4::new_scaling(0.1))
.into(),
colour: [0.0, 0.5, 0.0],
});
cube.insert_visibly(InstanceData {
modelmatrix: (na::Matrix4::new_translation(&na::Vector3::new(0.5, 0.0, 0.0))
* na::Matrix4::new_nonuniform_scaling(&na::Vector3::new(0.5, 0.01, 0.01)))
.into(),
colour: [1.0, 0.5, 0.5],
});
cube.insert_visibly(InstanceData {
modelmatrix: (na::Matrix4::new_translation(&na::Vector3::new(0.0, 0.5, 0.0))
* na::Matrix4::new_nonuniform_scaling(&na::Vector3::new(0.01, 0.5, 0.01)))
.into(),
colour: [0.5, 1.0, 0.5],
});
cube.insert_visibly(InstanceData {
modelmatrix: (na::Matrix4::new_translation(&na::Vector3::new(0.0, 0.0, 0.0))
* na::Matrix4::new_nonuniform_scaling(&na::Vector3::new(0.01, 0.01, 0.5)))
.into(),
colour: [0.5, 0.5, 1.0],
});
cube.update_vertexbuffer(&aetna.allocator);
cube.update_instancebuffer(&aetna.allocator);
aetna.models = vec![cube];
어떻게 움직이는 카메라를 만들 수 있을까요?
첫 번째 아이디어는 GPU로 보내기 전에 모든 모델 행렬을 변경하는 것입니다.
그리고 이 행렬을 변경하는 것은 좋은 생각입니다. “카메라”를 왼쪽으로 움직이든, 객체를 오른쪽으로 움직이든 효과는 같습니다. 이 움직임은 단지 선형 변환이므로, 또 다른 4x4 행렬로 표현될 수 있고, 이는 모델 행렬과 하나로 결합될 수 있습니다.
하지만, 이것은 CPU에서 많은 계산을 요구하며, 항상 같은 행렬을 곱해야 합니다. 이 행렬을 셰이더에 별도로 제출하기로 결정할 수도 있습니다. (그리고 예를 들어, 인스턴스 데이터가 매 프레임마다 업로드되지 않는 경우, 이는 CPU와 GPU 사이의 대역폭을 절약할 수 있습니다. 또한 일부 계산 작업을 CPU에서 GPU로 옮깁니다. 완전히 다른 카메라 간의 전환이 더 쉬워집니다 — 그리고 이는 우리가 여기서 이 주제를 다룰 기회를 줍니다…)
전역 좌표계(원점이 고정된 지점에 있음)에서 카메라 시스템(원점이 화면 중앙에 있음)으로 좌표를 변환하는 이 추가적인 행렬은 “뷰 행렬(view matrix)”이라고 불립니다.
우리에게 필요한 것: 이 뷰 행렬을 정점 셰이더로 전달하는 방법입니다. 모든 정점에 대해 전달되는 것이 아니라, 모든 정점에 대해 균일한(uniform) 변수로서 말입니다.
#version 450
layout (location=0) in vec3 position;
layout (location=1) in mat4 model_matrix;
layout (location=5) in vec3 colour;
layout (set=0, binding=0) uniform UniformBufferObject {
mat4 view_matrix;
} ubo;
layout (location=0) out vec4 colourdata_for_the_fragmentshader;
void main() {
gl_Position = ubo.view_matrix*model_matrix*vec4(position,1.0);
colourdata_for_the_fragmentshader=vec4(colour,1.0);
}
이 정점 셰이더에서 우리는 위치에 뷰 행렬을 곱합니다 — 하지만 더 흥미로운 부분은 우리가 그 행렬을 어디서 얻느냐 하는 것입니다:
layout (set=0, binding=0) uniform UniformBufferObject {
mat4 view_matrix;
} ubo;
이것은 mat4입니다 (놀랍지 않죠). 하지만 우리는 이것을 별도의 구조체(우리가 UniformBufferObject라고 부른 타입이고, 변수 이름은 ubo)에 넣어야 했습니다. in이나 out 대신 uniform을 사용합니다. 그리고 layout에는 set과 binding이라는 두 개의 숫자가 있습니다. Rust 코드 측에서 이 두 가지가 모두 필요합니다.
셰이더에서 사용될 모든 리소스(이 유니폼 버퍼처럼)는 “리소스 디스크립터(resource descriptor)”로 표현됩니다. 이 리소스 디스크립터에 포함되는 것과 다른 유용한 정보들은 디스크립터 셋 레이아웃(descriptor set layout)에 수집됩니다. 이것이 우리가 파이프라인 생성 중에 프로그램에 알려주는 것입니다.
Set은 layout 한정자의 디스크립터 중 하나였고, 다른 하나는 binding이었습니다. 먼저 바인딩에 대한 설명을 해봅시다. (사실, 모든 바인딩의 Vec으로, 하지만 지금은 하나만 있습니다.)
let descriptorset_layout_binding_descs = [vk::DescriptorSetLayoutBinding::builder()
.binding(0)
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER)
.descriptor_count(1)
.stage_flags(vk::ShaderStageFlags::VERTEX)
.build()];
어떤 바인딩인가요? — 0번입니다.
어떤 타입의 리소스인가요? — 유니폼 버퍼입니다.
이 바인딩에 몇 개의 디스크립터가 포함되나요? 셰이더에서 배열로 접근하고 싶나요? — 아니요, 디스크립터는 하나뿐입니다.
어떤 셰이더에서 이 유니폼 버퍼를 사용하고 싶나요? — 정점 셰이더에서만 사용합니다.
그런 다음 디스크립터 셋 레이아웃을 생성하고 (해당 CreateInfo를 만든 후) 파이프라인 레이아웃의 일부로 만듭니다 (Pipeline::init()에서):
let descriptorset_layout_info = vk::DescriptorSetLayoutCreateInfo::builder()
.bindings(&descriptorset_layout_binding_descs);
let descriptorsetlayout = unsafe {
logical_device.create_descriptor_set_layout(&descriptorset_layout_info, None)
}?;
let desclayouts = vec![descriptorsetlayout];
let pipelinelayout_info = vk::PipelineLayoutCreateInfo::builder().set_layouts(&desclayouts);
프로그램은 이미 컴파일되고 우리에게 오류 메시지를 보여줍니다:
[Debug][error][validation] "VkPipeline 0x1e uses set #0 but that set is not bound."
물론이죠. 우리는 어떤 종류의 데이터를 기대하는지 설명했지만, 그것을 생성하지도, 파이프라인으로 보내지도 않았습니다.
또 다른 오류가 있습니다:
[Debug][error][validation] "OBJ ERROR : For device 0x559eb24c1450, DescriptorSetLayout object 0x1c has not been
destroyed. The Vulkan spec states: All child objects created on device must have been destroyed prior to destroying
device (https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/vkspec.html#VUID-vkDestroyDevice-device-00378)"
따라서 디스크립터 셋 레이아웃을 더 오래 유지하여 (마지막에 무엇을 파괴해야 할지 기억할 수 있도록), 아마도 다음과 같이 반환함으로써 해결합시다.
Ok(Pipeline {
pipeline: graphicspipeline,
layout: pipelinelayout,
descriptor_set_layouts: desclayouts,
})
Pipeline에 새로운 필드가 추가되었네요. Pipeline::cleanup 함수에 몇 줄을 추가해 줍시다:
fn cleanup(&self, logical_device: &ash::Device) {
unsafe {
for dsl in &self.descriptor_set_layouts {
logical_device.destroy_descriptor_set_layout(*dsl, None);
}
logical_device.destroy_pipeline(self.pipeline, None);
logical_device.destroy_pipeline_layout(self.layout, None);
}
}
이것으로 하나의 오류는 해결되었습니다.
다른 오류에 관해서는, 디스크립터 셋이 필요합니다. 그것을 얻기 위한 첫 번째 단계: 버퍼 생성하기.
Aetna에 새 필드가 생깁니다:
uniformbuffer: Buffer,
이 버퍼를 생성하고 채웁니다:
let mut uniformbuffer = Buffer::new(
&allocator,
64,
vk::BufferUsageFlags::UNIFORM_BUFFER,
vk_mem::MemoryUsage::CpuToGpu,
)?;
let cameratransform: [[f32; 4]; 4] = na::Matrix4::identity().into();
uniformbuffer.fill(&allocator, &cameratransform)?;
그리고 마지막으로 파괴합니다:
impl Drop for Aetna {
fn drop(&mut self) {
unsafe {
self.device
.device_wait_idle()
.expect("something wrong while waiting");
self.allocator
.destroy_buffer(self.uniformbuffer.buffer, &self.uniformbuffer.allocation);
버퍼를 생성하는 것만으로는 충분하지 않습니다. 사용 가능하게 만들려면 디스크립터 셋의 일부가 되어야 합니다.
디스크립터 셋은 어디서 얻거나, 더 정확히는 어디에 저장할까요? DescriptorPool에 저장합니다. 디스크립터 셋은 메모리에서 그다지 크지 않습니다. 본질적으로 “저기에 당신이 원하는 유니폼 버퍼가 있습니다.”라는 참조 정보입니다. 그럼에도 불구하고 이것을 저장해야 할 어딘가가 필요한데, 이 “어딘가”가 바로 풀, 즉 이 목적을 위해 우리가 따로 마련해야 하는 GPU 메모리입니다.
최대 몇 개의 디스크립터 셋을 사용할까요? (.max_sets()) — 어떤 타입이 몇 개나 될까요? 이는 DescriptorPoolSize의 배열로 인코딩됩니다. 지금은 유니폼 버퍼만 있으며, 개수는 커맨드 버퍼처럼 프레임당 하나입니다.
let pool_sizes = [vk::DescriptorPoolSize {
ty: vk::DescriptorType::UNIFORM_BUFFER,
descriptor_count: swapchain.amount_of_images,
}];
let descriptor_pool_info = vk::DescriptorPoolCreateInfo::builder()
.max_sets(swapchain.amount_of_images)
.pool_sizes(&pool_sizes);
let descriptor_pool =
unsafe { logical_device.create_descriptor_pool(&descriptor_pool_info, None) }?;
그리고 물론, Aetna에 새로운 필드(descriptor_pool)를 추가하고 drop()에 적절한 줄을 추가합니다.
Vulkan과 ash의 차이점을 vk::DescriptorPoolSize에서 볼 수 있습니다. type이 ty가 되었습니다. (Rust에서 type은 예약된 키워드이므로, 이 이름 변경은 일관되게 적용됩니다.)
그런 다음 이 디스크립터 셋들을 할당해야 합니다:
let desc_layouts =
vec![pipeline.descriptor_set_layouts[0]; swapchain.amount_of_images as usize];
let descriptor_set_allocate_info = vk::DescriptorSetAllocateInfo::builder()
.descriptor_pool(descriptor_pool)
.set_layouts(&desc_layouts);
let descriptor_sets =
unsafe { logical_device.allocate_descriptor_sets(&descriptor_set_allocate_info) }?;
제공된 정보: 어떤 (그리고 암묵적으로: 몇 개의) 레이아웃에 대한 디스크립터를 원하는지, 그리고 어떤 풀을 사용할지.
Aetna에 또 다른 새 필드(descriptor_sets)가 생겼지만, 새로운 정리(cleanup) 코드는 없습니다.
다음으로, 이 디스크립터 셋들은 데이터로 채워져야 합니다. 우리는 이것들이 우리의 유니폼 버퍼와 관련이 있다고 말한 적이 없습니다. 이제 바로잡을 시간입니다.
업데이트 자체는 다음을 통해 이루어집니다.
unsafe { logical_device.update_descriptor_sets(&desc_sets_write, &[]) };
— 이 함수는 쓰기(첫 번째 인자) 또는 복사(두 번째 인자) 명령어의 배열을 받습니다.
우리에게 이 명령어들을 채우는 것은 다음과 같습니다:
for (i, descset) in descriptor_sets.iter().enumerate() {
let buffer_infos = [vk::DescriptorBufferInfo {
buffer: uniformbuffer.buffer,
offset: 0,
range: 64,
}];
let desc_sets_write = [vk::WriteDescriptorSet::builder()
.dst_set(*descset)
.dst_binding(0)
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER)
.buffer_info(&buffer_infos)
.build()];
unsafe { logical_device.update_descriptor_sets(&desc_sets_write, &[]) };
}
우리는 사용할 버퍼를 지정하고, 오프셋 없이 처음부터 사용하고 싶다는 것과, 크기가 64바이트라는 것을 나타냅니다. 또한 디스크립터 셋, 바인딩, 그리고 다시 한 번 타입을 선택합니다.
이 모든 코드 복사본:
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], InstanceData>>,
uniformbuffer: Buffer,
descriptor_pool: vk::DescriptorPool,
descriptor_sets: Vec<vk::DescriptorSet>,
}
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 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 swapchain = SwapchainDongXi::init(
&instance,
physical_device,
&logical_device,
&surfaces,
&queue_families,
&allocator,
)?;
let renderpass = init_renderpass(&logical_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 commandbuffers =
create_commandbuffers(&logical_device, &pools, swapchain.amount_of_images)?;
let mut uniformbuffer = Buffer::new(
&allocator,
64,
vk::BufferUsageFlags::UNIFORM_BUFFER,
vk_mem::MemoryUsage::CpuToGpu,
)?;
let cameratransform: [[f32; 4]; 4] = na::Matrix4::identity().into();
uniformbuffer.fill(&allocator, &cameratransform)?;
let pool_sizes = [vk::DescriptorPoolSize {
ty: vk::DescriptorType::UNIFORM_BUFFER,
descriptor_count: swapchain.amount_of_images,
}];
let descriptor_pool_info = vk::DescriptorPoolCreateInfo::builder()
.max_sets(swapchain.amount_of_images)
.pool_sizes(&pool_sizes);
let descriptor_pool =
unsafe { logical_device.create_descriptor_pool(&descriptor_pool_info, None) }?;
let desc_layouts =
vec![pipeline.descriptor_set_layouts[0]; swapchain.amount_of_images as usize];
let descriptor_set_allocate_info = vk::DescriptorSetAllocateInfo::builder()
.descriptor_pool(descriptor_pool)
.set_layouts(&desc_layouts);
let descriptor_sets =
unsafe { logical_device.allocate_descriptor_sets(&descriptor_set_allocate_info) }?;
for (i, descset) in descriptor_sets.iter().enumerate() {
let buffer_infos = [vk::DescriptorBufferInfo {
buffer: uniformbuffer.buffer,
offset: 0,
range: 64,
}];
let desc_sets_write = [vk::WriteDescriptorSet::builder()
.dst_set(*descset)
.dst_binding(0)
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER)
.buffer_info(&buffer_infos)
.build()];
unsafe { logical_device.update_descriptor_sets(&desc_sets_write, &[]) };
}
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: vec![],
uniformbuffer,
descriptor_pool,
descriptor_sets,
})
}
남은 일은 이 디스크립터 셋을 사용하는 것입니다. update_commandbuffer 함수에서, 파이프라인을 바인딩한 직후에 디스크립터 셋을 바인딩합니다:
self.device.cmd_bind_descriptor_sets(
commandbuffer,
vk::PipelineBindPoint::GRAPHICS,
self.pipeline.layout,
0,
&[self.descriptor_sets[index]],
&[],
);
커맨드 버퍼와 바인드 포인트(파이프라인을 바인딩할 때처럼)를 지정하고, 파이프라인 레이아웃을 나타냅니다. 그런 다음 디스크립터 셋의 배열을 제공하고, 이 배열의 요소로 업데이트될 첫 번째 바인딩 번호를 나타내는 숫자를 제공합니다. 마지막 인자는 “동적 오프셋(dynamic offsets)”을 위한 것이며 여기서는 관련이 없습니다.
이제 우리는 다시 실행되는 프로그램으로 돌아왔습니다. 하지만 카메라는 움직이지 않습니다. 별도의 Camera 구조체를 만들어서 키 입력에 따라 업데이트하고, 이 업데이트가 유니폼 버퍼의 업데이트로 이어지도록 합시다.
struct Camera {
viewmatrix: na::Matrix4<f32>,
}
이것은 제가 최종적으로 생각하는 Camera의 형태는 아닙니다. 예를 들어, 뷰 행렬에 이걸 넣거나 (또는 읽어오는 것) 보다는 카메라의 위치와 시야 방향을 바꾸는 것을 더 선호합니다.
하지만 이 간단한 버전으로 시작해 봅시다.
new() 함수 대신 Default를 구현합니다:
impl Default for Camera {
fn default() -> Self {
Camera {
viewmatrix: na::Matrix4::identity(),
}
}
}
그리고 이벤트 루프가 시작되기 전에 하나를 도입합니다:
let mut camera = Camera::default();
키보드로 조종하고 싶다면, 키보드를 사용하고 키 입력에 반응할 수 있는지 확인합시다. 이는 match event 로직에 또 다른 분기를 제공합니다:
Event::WindowEvent {
event: WindowEvent::KeyboardInput { input, .. },
..
} => match input {
winit::event::KeyboardInput {
state: winit::event::ElementState::Pressed,
virtual_keycode: Some(keycode),
..
} => match keycode {
winit::event::VirtualKeyCode::Right => {
println!("right");
}
winit::event::VirtualKeyCode::Left => {
println!("left");
}
_ => {}
},
_ => {}
},
단순히 무언가를 출력하는 대신, 카메라에 영향을 주어야 합니다:
match keycode {
winit::event::VirtualKeyCode::Right => {
camera.viewmatrix = na::Matrix4::new_scaling(0.5);
}
winit::event::VirtualKeyCode::Left => {
camera.viewmatrix = na::Matrix4::identity();
}
모든 것을 축소하고 크기를 복원하는 것이 카메라를 움직이는 일반적인 효과는 아니라는 점을 인정합니다만, 쉽게 알아볼 수 있습니다. 나중에 더 나은 카메라 움직임을 처리할 수 있습니다 (그리고 할 것입니다).
먼저 이 viewmatrix의 변경 사항이 실제로 그려지는 것에 영향을 미치는지 확인해야 합니다.
impl Camera {
fn update_buffer(&self, allocator: &vk_mem::Allocator, buffer: &mut Buffer) {
let data: [[f32; 4]; 4] = self.viewmatrix.into();
buffer.fill(allocator, &data);
}
}
그리고 모든 모델에 대해 update_instancebuffer를 호출하기 직전(Event::RedrawRequested에서), 이 함수도 호출합시다:
camera.update_buffer(&aetna.allocator, &mut aetna.uniformbuffer);
좋습니다. 이제 왼쪽과 오른쪽 화살표 키를 누르면 모든 것이 작아지고 다시 원래 크기로 돌아가야 합니다.
이제 우리 카메라에 대해 생각해 볼 시간입니다. 어떻게 표현하고 싶을까요?
그 위치(position)는 좋은 선택일 것 같습니다. 상상하기 쉽고, 예를 들어 위 또는 아래 화살표 키를 눌렀을 때 조정하기가 간단합니다.
위치만으로는 카메라의 상태를 완전히 설명할 수 없습니다. 위치만 안다면, 무엇이 보이는지는 아직 알 수 없습니다.
어느 방향을 보고 있는가? 이것은 무엇이 보이는지 알아내고 싶을 때 확실히 중요한 질문입니다. 이 정보는 다른 방식으로 저장할 수 있습니다. 예를 들어, 방향으로 저장하거나 카메라가 바라봐야 할 지점으로 저장할 수 있습니다. 방향으로 가봅시다.
이 두 가지 정보를 합치면 무엇이 보이는지 알기에 충분할 것입니다. 하지만 아직 어디서 보이는지는 알 수 없습니다. 우리는 여전히 카메라를 회전시킬 수 있습니다. 어느 방향이 위/오른쪽/아래/왼쪽인가요? 이 중 하나만으로도 충분하지만, 전부 없으면 무언가 빠져 있습니다.
struct Camera {
viewmatrix: na::Matrix4<f32>,
position: na::Vector3<f32>,
view_direction: na::Vector3<f32>,
down_direction: na::Vector3<f32>,
}
이런 식으로요? 아마도요.
생각해보니: 방향은 길이가 1이어야 합니다. (길이가 0인 방향은 나쁠 것입니다. 뷰 방향 (0,0,1)과 (0,0,2)의 차이는 찾기 어려울 것입니다.)
nalgebra에서는 이러한 요구 사항을 변수 타입에 인코딩하는 것이 가능합니다:
struct Camera {
viewmatrix: na::Matrix4<f32>,
position: na::Vector3<f32>,
view_direction: na::Unit<na::Vector3<f32>>,
down_direction: na::Unit<na::Vector3<f32>>,
}
그리고 카메라의 기본 상태는 새로운 필드를 포함해야 합니다:
impl Default for Camera {
fn default() -> Self {
Camera {
viewmatrix: na::Matrix4::identity(),
position: na::Vector3::new(0.0, 0.0, 0.0),
view_direction: na::Unit::new_normalize(na::Vector3::new(0.0, 0.0, 1.0)),
down_direction: na::Unit::new_normalize(na::Vector3::new(0.0, 1.0, 0.0)),
}
}
}
다룰 주요 주제는 두 가지입니다: position, view_direction, down_direction을 반영하도록 viewmatrix를 어떻게 변경하는가? 그리고: 이 값들을 어떻게 변경하는가, 즉 키가 눌렸을 때 무엇을 해야 하는가?
먼저 두 번째 질문을 다루어 봅시다.
fn update_viewmatrix(&mut self) {
//placeholder
}
자리 표시자, 나중에 처리해야 할 부분입니다.
앞으로 움직이는 것은 간단합니다: 우리 위치에 뷰 방향을 더하는 것입니다. 또는 우리가 얼마나 멀리 움직이고 싶은지에 따라 그 배수를 더합니다.
fn move_forward(&mut self, distance: f32) {
self.position += distance * self.view_direction.as_ref();
self.update_viewmatrix();
}
뒤로 움직이는 함수를 따로 둘까요?
fn move_backward(&mut self, distance:f32){
self.move_forward(-distance);
}
오른쪽으로 도는 것은 아래 방향은 그대로 유지되고, 실제로 이 아래 벡터를 중심으로 회전한다는 것을 의미하며, 이는 뷰 방향을 회전시키는 것입니다. 양의 각도로 회전하는 것이 정말 맞을까요? 네, 이전 장을 참조하세요.
fn turn_right(&mut self, angle: f32) {
let rotation = na::Rotation3::from_axis_angle(&self.down_direction, angle);
self.view_direction = rotation * self.view_direction;
self.update_viewmatrix();
}
fn turn_left(&mut self, angle: f32) {
self.turn_right(-angle);
}
위나 아래를 보는 것은 오른쪽을 가리키는 방향을 중심으로 회전하는 것입니다. (위가 양의 각도, 오른손 엄지손가락으로 확인하세요.)
‘오른쪽’을 저장하지는 않았지만, ‘아래’와 ‘앞’(즉, ‘뷰’) 방향의 외적(cross product)으로 계산할 수 있습니다.
그런 다음 뷰와 아래 방향을 모두 이 축을 중심으로 회전시켜야 합니다:
fn turn_up(&mut self, angle: f32) {
let right = na::Unit::new_normalize(self.down_direction.cross(&self.view_direction));
let rotation = na::Rotation3::from_axis_angle(&right, angle);
self.view_direction = rotation * self.view_direction;
self.down_direction = rotation * self.down_direction;
self.update_viewmatrix();
}
fn turn_down(&mut self, angle: f32) {
self.turn_up(-angle);
}
물론, 이 함수들도 호출해야 합니다:
Event::WindowEvent {
event: WindowEvent::KeyboardInput { input, .. },
..
} => {
if let winit::event::KeyboardInput {
state: winit::event::ElementState::Pressed,
virtual_keycode: Some(keycode),
..
} = input
{
match keycode {
winit::event::VirtualKeyCode::Right => {
camera.turn_right(0.1);
}
winit::event::VirtualKeyCode::Left => {
camera.turn_left(0.1);
}
winit::event::VirtualKeyCode::Up => {
camera.move_forward(0.05);
}
winit::event::VirtualKeyCode::Down => {
camera.move_backward(0.05);
}
winit::event::VirtualKeyCode::PageUp => {
camera.turn_up(0.02);
}
winit::event::VirtualKeyCode::PageDown => {
camera.turn_down(0.02);
}
_ => {}
}
}
}
아직 해야 할 일은 뷰 행렬을 찾는 것입니다.
이 변환이 달성해야 할 것은 무엇일까요? 첫째, 우리를 올바른 위치로 이동시켜야 합니다. 그런 다음 우리의 시야를 올바른 방향으로 회전시켜야 합니다.
같은 시나리오를 다시 설명하겠습니다: 뷰 행렬은 올바른 위치를 원점으로 이동시켜야 합니다. 그런 다음 모든 것을 (원점을 중심으로) 회전시켜 view_direction이 앞을 가리키는 방향(즉, )으로,
down_direction이 아래를 가리키도록 (즉, 와 일치하도록) 해야 합니다.
(우리는 “우리 위치”를 움직일 수 없습니다. 왜냐하면 Vulkan은 항상 화면이 13장에서 찾은 [-1,1]x[-1,1]x[0,1] 상자이기를 기대하기 때문입니다. 하지만 다른 모든 것을 움직이는 것은 같은 효과를 냅니다.)
그래서, 먼저 만큼의 이동
, 여기서
=
position 입니다 (마이너스 부호는 position이 원점에 오도록 하기 위함입니다). 행렬로는 다음과 같습니다.
(15장 참조); (오른쪽 방향),
(
down_direction), 그리고 (
view_direction)을 ,
, 그리고
로 바꾸는 회전(가령,
) 또한 행렬이 됩니다 (이전 장 참조):
우리는 다시, 오른쪽 방향을 외적으로 계산할 수 있습니다. 그리고 아래, 뷰, 오른쪽 방향은 길이가 1이며 서로 직교합니다. (주의: 이 직교성은 타입에 의해 보장되지 않으며, 이 방향들을 변경할 때 “좋은” 변환만 사용한다는 것에 의존합니다. 하지만 우리가 위에서 소개한 함수들은 회전만 합니다.)
뷰 행렬은 “먼저 T를 적용하고, 그 다음 R을 적용”해야 하므로, 다음과 같습니다.
코드로 표현하면 다음과 같습니다:
fn update_viewmatrix(&mut self) {
let right = na::Unit::new_normalize(self.down_direction.cross(&self.view_direction));
let m = na::Matrix4::new(
right.x,
right.y,
right.z,
-right.dot(&self.position), //
self.down_direction.x,
self.down_direction.y,
self.down_direction.z,
-self.down_direction.dot(&self.position), //
self.view_direction.x,
self.view_direction.y,
self.view_direction.z,
-self.view_direction.dot(&self.position), //
0.0,
0.0,
0.0,
1.0,
);
self.viewmatrix = m;
}
경고 하나 하겠습니다: nalgebra는 이러한 “look at 행렬”을 생성하는 함수를 제공합니다. 우리가 오른손 좌표계를 사용하고 있으므로, look_at_rh 함수(위치, 바라볼 지점(즉, position+view_direction), 그리고 “up” 방향을 받음)가 올바른 행렬을 줄 것이라고 예상할 수 있습니다:
let m2 = na::Isometry3::look_at_rh(
&na::Point3::from(self.position),
&na::Point3::from(self.position + self.view_direction.as_ref()),
&-self.down_direction,
)
.to_homogeneous();
하지만 그렇지 않습니다. (몇 군데에서 부호가 맞지 않습니다.) 그 이유는 nalgebra가 OpenGL의 좌표계를 사용하기 때문인데, 이는 Vulkan의 관례와는 약간 다릅니다(가 OpenGL에서는 위를 가리키고,
의 범위도 다릅니다). 함수나 좌표계를 조정하거나 이 문제를 우회하는 것(예를 들어,
down_direction을 up 인자로 사용하고 대신 look_at_lh를 사용하는 것)이 불가능한 것은 아니지만 — 하지만 이는 인지하고 있어야 할 사항입니다.
이제 우리는 돌아다닐 수 있습니다. 그리고 잘 작동합니다. (오른쪽 화살표 키는 “머리를 오른쪽으로 돌리게” 하므로 화면의 모든 것이 왼쪽으로 움직이고, PageUp과 PageDown 키는 위나 아래를 보게 하며, 위아래 화살표 키로 앞뒤로 이동할 수 있습니다.)
하지만 어딘가 이상해 보입니다. 뒤로 움직이거나 옆으로 돌 때 상자들이 그냥 사라져 버리기도 합니다.
우리는 [-1,1]x[-1,1]x[0,1] 상자 안에 있는 것만 화면에 표시한다는 것을 기억하세요. 이 뷰 행렬 변환이 적용된 후 큐브 중 하나가 이 상자를 벗어나자마자 즉시 사라집니다.
또한, 여기에는 원근감이 없습니다. 화면에 무언가를 표시하는 것은 여전히 “그냥 z-좌표를 무시하는 것”을 의미합니다. (좋습니다, 그것도 일종의 원근법이지만, 우리 눈이 사용하는 것과는 다릅니다.)
우리는 15장의 그림에 있는 기찻길과 같은 원근 투영(perspective projection)이 필요합니다.
그것은 다음 장에서 다루겠습니다. 지금은, 다시 한번 소스 코드를 봅시다:
use ash::version::DeviceV1_0;
use ash::version::EntryV1_0;
use ash::version::InstanceV1_0;
use ash::vk;
use nalgebra as na;
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)?;
let mut cube = Model::cube();
cube.insert_visibly(InstanceData {
modelmatrix: (na::Matrix4::new_translation(&na::Vector3::new(0.0, 0.0, 0.1))
* na::Matrix4::new_scaling(0.1))
.into(),
colour: [0.2, 0.4, 1.0],
});
cube.insert_visibly(InstanceData {
modelmatrix: (na::Matrix4::new_translation(&na::Vector3::new(0.05, 0.05, 0.0))
* na::Matrix4::new_scaling(0.1))
.into(),
colour: [1.0, 1.0, 0.2],
});
for i in 0..10 {
for j in 0..10 {
cube.insert_visibly(InstanceData {
modelmatrix: (na::Matrix4::new_translation(&na::Vector3::new(
i as f32 * 0.2 - 1.0,
j as f32 * 0.2 - 1.0,
0.5,
)) * na::Matrix4::new_scaling(0.03))
.into(),
colour: [1.0, i as f32 * 0.07, j as f32 * 0.07],
});
cube.insert_visibly(InstanceData {
modelmatrix: (na::Matrix4::new_translation(&na::Vector3::new(
i as f32 * 0.2 - 1.0,
0.0,
j as f32 * 0.2 - 1.0,
)) * na::Matrix4::new_scaling(0.02))
.into(),
colour: [i as f32 * 0.07, j as f32 * 0.07, 1.0],
});
}
}
cube.insert_visibly(InstanceData {
modelmatrix: (na::Matrix4::from_scaled_axis(na::Vector3::new(0.0, 0.0, 1.4))
* na::Matrix4::new_translation(&na::Vector3::new(0.0, 0.5, 0.0))
* na::Matrix4::new_scaling(0.1))
.into(),
colour: [0.0, 0.5, 0.0],
});
cube.insert_visibly(InstanceData {
modelmatrix: (na::Matrix4::new_translation(&na::Vector3::new(0.5, 0.0, 0.0))
* na::Matrix4::new_nonuniform_scaling(&na::Vector3::new(0.5, 0.01, 0.01)))
.into(),
colour: [1.0, 0.5, 0.5],
});
cube.insert_visibly(InstanceData {
modelmatrix: (na::Matrix4::new_translation(&na::Vector3::new(0.0, 0.5, 0.0))
* na::Matrix4::new_nonuniform_scaling(&na::Vector3::new(0.01, 0.5, 0.01)))
.into(),
colour: [0.5, 1.0, 0.5],
});
cube.insert_visibly(InstanceData {
modelmatrix: (na::Matrix4::new_translation(&na::Vector3::new(0.0, 0.0, 0.0))
* na::Matrix4::new_nonuniform_scaling(&na::Vector3::new(0.01, 0.01, 0.5)))
.into(),
colour: [0.5, 0.5, 1.0],
});
cube.update_vertexbuffer(&aetna.allocator);
cube.update_instancebuffer(&aetna.allocator);
aetna.models = vec![cube];
let mut camera = Camera::default();
use winit::event::{Event, WindowEvent};
eventloop.run(move |event, _, controlflow| match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => {
*controlflow = winit::event_loop::ControlFlow::Exit;
}
Event::WindowEvent {
event: WindowEvent::KeyboardInput { input, .. },
..
} => {
if let winit::event::KeyboardInput {
state: winit::event::ElementState::Pressed,
virtual_keycode: Some(keycode),
..
} = input
{
match keycode {
winit::event::VirtualKeyCode::Right => {
camera.turn_right(0.1);
}
winit::event::VirtualKeyCode::Left => {
camera.turn_left(0.1);
}
winit::event::VirtualKeyCode::Up => {
camera.move_forward(0.05);
}
winit::event::VirtualKeyCode::Down => {
camera.move_backward(0.05);
}
winit::event::VirtualKeyCode::PageUp => {
camera.turn_up(0.02);
}
winit::event::VirtualKeyCode::PageDown => {
camera.turn_down(0.02);
}
_ => {}
}
}
}
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");
}
camera.update_buffer(&aetna.allocator, &mut aetna.uniformbuffer);
for m in &mut aetna.models {
m.update_instancebuffer(&aetna.allocator);
}
aetna
.update_commandbuffer(image_index as usize)
.expect("updating the command buffer");
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>,
depth_image: vk::Image,
depth_image_allocation: vk_mem::Allocation,
depth_image_allocation_info: vk_mem::AllocationInfo,
depth_imageview: 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,
allocator: &vk_mem::Allocator,
) -> Result<SwapchainDongXi, Box<dyn std::error::Error>> {
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 extent3d = vk::Extent3D {
width: extent.width,
height: extent.height,
depth: 1,
};
let depth_image_info = vk::ImageCreateInfo::builder()
.image_type(vk::ImageType::TYPE_2D)
.format(vk::Format::D32_SFLOAT)
.extent(extent3d)
.mip_levels(1)
.array_layers(1)
.samples(vk::SampleCountFlags::TYPE_1)
.tiling(vk::ImageTiling::OPTIMAL)
.usage(vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT)
.sharing_mode(vk::SharingMode::EXCLUSIVE)
.queue_family_indices(&queuefamilies);
let allocation_info = vk_mem::AllocationCreateInfo {
usage: vk_mem::MemoryUsage::GpuOnly,
..Default::default()
};
let (depth_image, depth_image_allocation, depth_image_allocation_info) =
allocator.create_image(&depth_image_info, &allocation_info)?;
let subresource_range = vk::ImageSubresourceRange::builder()
.aspect_mask(vk::ImageAspectFlags::DEPTH)
.base_mip_level(0)
.level_count(1)
.base_array_layer(0)
.layer_count(1);
let imageview_create_info = vk::ImageViewCreateInfo::builder()
.image(depth_image)
.view_type(vk::ImageViewType::TYPE_2D)
.format(vk::Format::D32_SFLOAT)
.subresource_range(*subresource_range);
let depth_imageview =
unsafe { logical_device.create_image_view(&imageview_create_info, None) }?;
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,
depth_image,
depth_image_allocation,
depth_image_allocation_info,
depth_imageview,
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, self.depth_imageview];
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, allocator: &vk_mem::Allocator) {
logical_device.destroy_image_view(self.depth_imageview, None);
allocator.destroy_image(self.depth_image, &self.depth_image_allocation);
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,
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(),
vk::AttachmentDescription::builder()
.format(vk::Format::D32_SFLOAT)
.load_op(vk::AttachmentLoadOp::CLEAR)
.store_op(vk::AttachmentStoreOp::DONT_CARE)
.stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
.stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
.initial_layout(vk::ImageLayout::UNDEFINED)
.final_layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL)
.samples(vk::SampleCountFlags::TYPE_1)
.build(),
];
let color_attachment_references = [vk::AttachmentReference {
attachment: 0,
layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
}];
let depth_attachment_reference = vk::AttachmentReference {
attachment: 1,
layout: vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
};
let subpasses = [vk::SubpassDescription::builder()
.color_attachments(&color_attachment_references)
.depth_stencil_attachment(&depth_attachment_reference)
.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,
descriptor_set_layouts: Vec<vk::DescriptorSetLayout>,
}
impl Pipeline {
fn cleanup(&self, logical_device: &ash::Device) {
unsafe {
for dsl in &self.descriptor_set_layouts {
logical_device.destroy_descriptor_set_layout(*dsl, None);
}
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::R32G32B32A32_SFLOAT,
},
vk::VertexInputAttributeDescription {
binding: 1,
location: 2,
offset: 16,
format: vk::Format::R32G32B32A32_SFLOAT,
},
vk::VertexInputAttributeDescription {
binding: 1,
location: 3,
offset: 32,
format: vk::Format::R32G32B32A32_SFLOAT,
},
vk::VertexInputAttributeDescription {
binding: 1,
location: 4,
offset: 48,
format: vk::Format::R32G32B32A32_SFLOAT,
},
vk::VertexInputAttributeDescription {
binding: 1,
location: 5,
offset: 64,
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: 76,
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 depth_stencil_info = vk::PipelineDepthStencilStateCreateInfo::builder()
.depth_test_enable(true)
.depth_write_enable(true)
.depth_compare_op(vk::CompareOp::LESS_OR_EQUAL);
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 descriptorset_layout_binding_descs = [vk::DescriptorSetLayoutBinding::builder()
.binding(0)
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER)
.descriptor_count(1)
.stage_flags(vk::ShaderStageFlags::VERTEX)
.build()];
let descriptorset_layout_info = vk::DescriptorSetLayoutCreateInfo::builder()
.bindings(&descriptorset_layout_binding_descs);
let descriptorsetlayout = unsafe {
logical_device.create_descriptor_set_layout(&descriptorset_layout_info, None)
}?;
let desclayouts = vec![descriptorsetlayout];
let pipelinelayout_info = vk::PipelineLayoutCreateInfo::builder().set_layouts(&desclayouts);
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)
.depth_stencil_state(&depth_stencil_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,
descriptor_set_layouts: desclayouts,
})
}
}
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) }
}
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, "invalid handle")
}
}
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 already visible: do nothing
if let Some(&index) = self.handle_to_index.get(&handle) {
if index < self.first_invisible {
return Ok(());
}
//else: move to position first_invisible and increase value of 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 already invisible: do nothing
if let Some(&index) = self.handle_to_index.get(&handle) {
if index >= self.first_invisible {
return Ok(());
}
//else: move to position before first_invisible and decrease value of 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);
//must be Some(), otherwise we couldn't have found an index
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], InstanceData> {
fn cube() -> Model<[f32; 3], InstanceData> {
let lbf = [-1.0, 1.0, 0.0]; //lbf: left-bottom-front
let lbb = [-1.0, 1.0, 1.0];
let ltf = [-1.0, -1.0, 0.0];
let ltb = [-1.0, -1.0, 1.0];
let rbf = [1.0, 1.0, 0.0];
let rbb = [1.0, 1.0, 1.0];
let rtf = [1.0, -1.0, 0.0];
let rtb = [1.0, -1.0, 1.0];
Model {
vertexdata: vec![
lbf, lbb, rbb, lbf, rbb, rbf, //bottom
ltf, rtb, ltb, ltf, rtf, rtb, //top
lbf, rtf, ltf, lbf, rbf, rtf, //front
lbb, ltb, rtb, lbb, rtb, rbb, //back
lbf, ltf, lbb, lbb, ltf, ltb, //left
rbf, rbb, rtf, rbb, rtb, rtf, //right
],
handle_to_index: std::collections::HashMap::new(),
handles: Vec::new(),
instances: Vec::new(),
first_invisible: 0,
next_handle: 0,
vertexbuffer: None,
instancebuffer: None,
}
}
}
#[repr(C)]
struct InstanceData {
modelmatrix: [[f32; 4]; 4],
colour: [f32; 3],
}
struct Camera {
viewmatrix: na::Matrix4<f32>,
position: na::Vector3<f32>,
view_direction: na::Unit<na::Vector3<f32>>,
down_direction: na::Unit<na::Vector3<f32>>,
}
impl Default for Camera {
fn default() -> Self {
Camera {
viewmatrix: na::Matrix4::identity(),
position: na::Vector3::new(0.0, 0.0, 0.0),
view_direction: na::Unit::new_normalize(na::Vector3::new(0.0, 0.0, 1.0)),
down_direction: na::Unit::new_normalize(na::Vector3::new(0.0, 1.0, 0.0)),
}
}
}
impl Camera {
fn update_buffer(&self, allocator: &vk_mem::Allocator, buffer: &mut Buffer) {
let data: [[f32; 4]; 4] = self.viewmatrix.into();
buffer.fill(allocator, &data);
}
fn update_viewmatrix(&mut self) {
let right = na::Unit::new_normalize(self.down_direction.cross(&self.view_direction));
let m = na::Matrix4::new(
right.x,
right.y,
right.z,
-right.dot(&self.position), //
self.down_direction.x,
self.down_direction.y,
self.down_direction.z,
-self.down_direction.dot(&self.position), //
self.view_direction.x,
self.view_direction.y,
self.view_direction.z,
-self.view_direction.dot(&self.position), //
0.0,
0.0,
0.0,
1.0,
);
self.viewmatrix = m;
}
fn move_forward(&mut self, distance: f32) {
self.position += distance * self.view_direction.as_ref();
self.update_viewmatrix();
}
fn move_backward(&mut self, distance: f32) {
self.move_forward(-distance);
}
fn turn_right(&mut self, angle: f32) {
let rotation = na::Rotation3::from_axis_angle(&self.down_direction, angle);
self.view_direction = rotation * self.view_direction;
self.update_viewmatrix();
}
fn turn_left(&mut self, angle: f32) {
self.turn_right(-angle);
}
fn turn_up(&mut self, angle: f32) {
let right = na::Unit::new_normalize(self.down_direction.cross(&self.view_direction));
let rotation = na::Rotation3::from_axis_angle(&right, angle);
self.view_direction = rotation * self.view_direction;
self.down_direction = rotation * self.down_direction;
self.update_viewmatrix();
}
fn turn_down(&mut self, angle: f32) {
self.turn_up(-angle);
}
}
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], InstanceData>>,
uniformbuffer: Buffer,
descriptor_pool: vk::DescriptorPool,
descriptor_sets: Vec<vk::DescriptorSet>,
}
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 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 swapchain = SwapchainDongXi::init(
&instance,
physical_device,
&logical_device,
&surfaces,
&queue_families,
&allocator,
)?;
let renderpass = init_renderpass(&logical_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 commandbuffers =
create_commandbuffers(&logical_device, &pools, swapchain.amount_of_images)?;
let mut uniformbuffer = Buffer::new(
&allocator,
64,
vk::BufferUsageFlags::UNIFORM_BUFFER,
vk_mem::MemoryUsage::CpuToGpu,
)?;
let cameratransform: [[f32; 4]; 4] = na::Matrix4::identity().into();
uniformbuffer.fill(&allocator, &cameratransform)?;
let pool_sizes = [vk::DescriptorPoolSize {
ty: vk::DescriptorType::UNIFORM_BUFFER,
descriptor_count: swapchain.amount_of_images,
}];
let descriptor_pool_info = vk::DescriptorPoolCreateInfo::builder()
.max_sets(swapchain.amount_of_images)
.pool_sizes(&pool_sizes);
let descriptor_pool =
unsafe { logical_device.create_descriptor_pool(&descriptor_pool_info, None) }?;
let desc_layouts =
vec![pipeline.descriptor_set_layouts[0]; swapchain.amount_of_images as usize];
let descriptor_set_allocate_info = vk::DescriptorSetAllocateInfo::builder()
.descriptor_pool(descriptor_pool)
.set_layouts(&desc_layouts);
let descriptor_sets =
unsafe { logical_device.allocate_descriptor_sets(&descriptor_set_allocate_info) }?;
for (i, descset) in descriptor_sets.iter().enumerate() {
let buffer_infos = [vk::DescriptorBufferInfo {
buffer: uniformbuffer.buffer,
offset: 0,
range: 64,
}];
let desc_sets_write = [vk::WriteDescriptorSet::builder()
.dst_set(*descset)
.dst_binding(0)
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER)
.buffer_info(&buffer_infos)
.build()];
unsafe { logical_device.update_descriptor_sets(&desc_sets_write, &[]) };
}
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: vec![],
uniformbuffer,
descriptor_pool,
descriptor_sets,
})
}
fn update_commandbuffer(&mut self, index: usize) -> Result<(), vk::Result> {
let commandbuffer = self.commandbuffers[index];
let commandbuffer_begininfo = vk::CommandBufferBeginInfo::builder();
unsafe {
self.device
.begin_command_buffer(commandbuffer, &commandbuffer_begininfo)?;
}
let clearvalues = [
vk::ClearValue {
color: vk::ClearColorValue {
float32: [0.0, 0.0, 0.08, 1.0],
},
},
vk::ClearValue {
depth_stencil: vk::ClearDepthStencilValue {
depth: 1.0,
stencil: 0,
},
},
];
let renderpass_begininfo = vk::RenderPassBeginInfo::builder()
.render_pass(self.renderpass)
.framebuffer(self.swapchain.framebuffers[index])
.render_area(vk::Rect2D {
offset: vk::Offset2D { x: 0, y: 0 },
extent: self.swapchain.extent,
})
.clear_values(&clearvalues);
unsafe {
self.device.cmd_begin_render_pass(
commandbuffer,
&renderpass_begininfo,
vk::SubpassContents::INLINE,
);
self.device.cmd_bind_pipeline(
commandbuffer,
vk::PipelineBindPoint::GRAPHICS,
self.pipeline.pipeline,
);
self.device.cmd_bind_descriptor_sets(
commandbuffer,
vk::PipelineBindPoint::GRAPHICS,
self.pipeline.layout,
0,
&[self.descriptor_sets[index]],
&[],
);
for m in &self.models {
m.draw(&self.device, commandbuffer);
}
self.device.cmd_end_render_pass(commandbuffer);
self.device.end_command_buffer(commandbuffer)?;
}
Ok(())
}
}
impl Drop for Aetna {
fn drop(&mut self) {
unsafe {
self.device
.device_wait_idle()
.expect("something wrong while waiting");
self.device
.destroy_descriptor_pool(self.descriptor_pool, None);
self.allocator
.destroy_buffer(self.uniformbuffer.buffer, &self.uniformbuffer.allocation);
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.pools.cleanup(&self.device);
self.pipeline.cleanup(&self.device);
self.device.destroy_render_pass(self.renderpass, None);
self.swapchain.cleanup(&self.device, &self.allocator);
self.allocator.destroy();
self.device.destroy_device(None);
std::mem::ManuallyDrop::drop(&mut self.surfaces);
std::mem::ManuallyDrop::drop(&mut self.debug);
self.instance.destroy_instance(None)
};
}
}