We have managed to have moving objects in the scene (okay, one moving object — but it’s not difficult to adapt to more), but often it’s not just the objects that should move, but also “the player” or “the camera”. In this chapter, we shall have a look at how to achieve this.
Preparation: Let us remove the motion code from Chapter 24, so that MainEventsCleared
looks as follows:
Event::MainEventsCleared => {
aetna.window.request_redraw();
}
and let us include the following setup for the scene (just so that there are many cubes to look at - among them I include a representation of the axes):
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];
How can we get a moving camera?
First idea is to change every model matrix before sending it to the GPU.
And changing this matrix is a good idea. Whether we move a “camera” to the left, or an object to the right: The effect is the same. This motion is just a linear transformation, so it can be represented by another 4x4-matrix, and that can be combined with the model matrix into one.
However, this is a lot of computations on the CPU, it is always the same matrix that we have to multiply; we could as well decide to submit this matrix separately to the shaders. (And, for example, in cases where the instance data is not uploaded every frame, this may well save some bandwidth between CPU and GPU. It also shifts some computational work from CPU to GPU. Switching between totally different cameras becomes easier — and it gives us an opportunity to cover this topic here …)
This additional matrix which turns coordinates from the global coordinate system (origin at some fixed point) into a camera system (origin in the centre of the screen), by the way, is called a “view matrix”.
We need: A way to pass this view matrix to the vertex shader. Not as something transferred for every vertex. But as some variable that is uniform over all vertices.
#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);
}
In this vertex shader we multiply the position by a view matrix — but the more interesting part is where we get said matrix from:
layout (set=0, binding=0) uniform UniformBufferObject {
mat4 view_matrix;
} ubo;
It’s a mat4
(unsurprisingly), but we had to put it into a separate struct (of a type which we have called UniformBufferObject
, and with variable name ubo
); instead of in
or out
we use uniform
. And the layout
has two numbers: set
and binding
. We will need both on the Rust code side of things.
Every resource to be used in a shader (like this uniform buffer) is represented by a “resource descriptor”; what is part of this resource descriptor and other useful bits of information are collected in a descriptor set layout. This is what we tell our program during pipeline creation.
Set
was one of the descriptors in the layout
qualifier; the other was binding
. Let us give a description of the binding first. (Actually, as a
Vec
of all bindings, but we’re having only one now.)
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()];
Which binding is it? — number 0
What type of resource is it? — A uniform buffer.
How many descriptors become part of this binding? Do we want to access them in an array in the shader? — No, only one descriptor.
In which shaders do we want to use this uniform buffer? — Only in the vertex shader.
We then create the descriptor set layout (after producing a corresponding CreateInfo
) and make it part of the pipeline layout (in
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);
The program already compiles and presents us with an error message:
[Debug][error][validation] "VkPipeline 0x1e uses set #0 but that set is not bound."
Sure. We have described what kind of data we expect, but we have not produced it, nor have we sent it to the pipeline.
There is another error:
[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)"
So, let’s keep the descriptor set layout around longer (so that at the end, we still remember what to destroy), maybe by returning
Ok(Pipeline {
pipeline: graphicspipeline,
layout: pipelinelayout,
descriptor_set_layouts: desclayouts,
})
with, apparently, a new field in Pipeline
. Let’s give the Pipeline::cleanup
function a few new lines:
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);
}
}
That’s one error taken care of.
As to the other error, we need a descriptor set. First step to obtain it: Creating a buffer.
Aetna
gets a new field:
uniformbuffer: Buffer,
we create and fill this 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)?;
and finally destroy it:
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);
Creating the buffer is not enough. In order to make it usable, it has to become part of a descriptor set.
Where do we get — or rather: Where do we store — descriptor sets? In a DescriptorPool
. A descriptor set is not very big in memory, it’s essentially
reference information: “There is the uniform buffer you want.” Still, it has to be saved somewhere, this “somewhere” is a pool, some GPU memory we
have to set aside for this purpose.
How many descriptor sets will we use at most? (.max_sets()
) — How many of which type are we talking? That’s encoded in an array of
DescriptorPoolSize
s. For now, we only have uniform buffers; as to how many: one per frame, just like with the command buffers.
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) }?;
And, of course, I’m adding a new field (descriptor_pool
) to Aetna
and an appropriate line to its drop()
.
In vk::DescriptorPoolSize
you can observe a difference between Vulkan and ash: type
has become ty
. (In Rust, type
is a reserved keyword; this
name change is applied consistently.)
We then have to allocate these descriptor sets:
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) }?;
Information provided: Which (and implicitly: how many) layouts we want to have descriptors for, and which pool to use.
Another new field for Aetna
(descriptor_sets
), but no new cleanup.
Next, these descriptor sets have to be filled with data. We have never said that they have anything to do with our uniform buffer. Time to rectify that.
The update itself happens by means of
unsafe { logical_device.update_descriptor_sets(&desc_sets_write, &[]) };
— a function which expects arrays of write (first argument) or copy (second argument) instructions.
For us, filling in these instructions looks like this:
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, &[]) };
}
We indicate which buffer to use, that we want to use it from its beginning, without offset, and that it’s 64 bytes large. Furthermore we pick a descriptor set, the binding and, once more, the type.
Code copy of all of these:
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,
})
}
What is left to do is using these descriptor sets. In the update_commandbuffer
function, just after binding the pipeline, we bind the descriptor
set:
self.device.cmd_bind_descriptor_sets(
commandbuffer,
vk::PipelineBindPoint::GRAPHICS,
self.pipeline.layout,
0,
&[self.descriptor_sets[index]],
&[],
);
We specify command buffer and bindpoint (like when binding the pipeline), indicate the pipeline layout. Then we provide an array of descriptor sets, and a number saying which binding number is the first to be updated with the elements of this array. The last argument is for “dynamic offsets” and does not concern us here.
Now we are back to a running program. Our camera, however, does not move. Let us invent a separate Camera
struct, which we update in response to
key presses and which in turn leads to updates of our uniform buffer.
struct Camera {
viewmatrix: na::Matrix4<f32>,
}
This is not quite what I have in mind as final form of Camera
; for example, I’d rather like to change the position and view direction of the camera
than having to put this into (or read it out of) the view matrix.
But let us begin with this simple version.
Instead of a new()
function, we implement Default
:
impl Default for Camera {
fn default() -> Self {
Camera {
viewmatrix: na::Matrix4::identity(),
}
}
}
and then introduce one before the event loop starts:
let mut camera = Camera::default();
If we want to steer it via keyboard, let’s make sure we can use our keyboard and react to key presses. That gives another branch for the match event
logic:
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");
}
_ => {}
},
_ => {}
},
Instead of just printing something, we should affect the camera:
match keycode {
winit::event::VirtualKeyCode::Right => {
camera.viewmatrix = na::Matrix4::new_scaling(0.5);
}
winit::event::VirtualKeyCode::Left => {
camera.viewmatrix = na::Matrix4::identity();
}
I admit, shrinking everything and restoring its size are not quite the usual effects of moving a camera, but they are easy to spot. We can (and will) take care of better camera motion later.
First we have to make sure that changes to this viewmatrix actually affect what is drawn.
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);
}
}
And right before we call update_instancebuffer
for all models (in Event::RedrawRequested
), let us call also this function:
camera.update_buffer(&aetna.allocator, &mut aetna.uniformbuffer);
Okay, now hitting the left and right arrow keys should make everything smaller and return it to the usual size again.
Time to think about our camera. How do we want to represent it?
Its position would be a good choice, I think. Easy to imagine, straightforward to adjust when, for example, up or down arrow keys are pressed.
A position alone does not completely describe the state of a camera. If I only know its position, I don’t yet know what can be seen.
In which direction is it looking? That is certainly an important question if we want to figure out what can be seen. We can store this information in different ways. Either as a direction or as a point the camera should be looking at, for example. Let’s go with the direction.
These two pieces combined should be good for knowing what can be seen. But not yet for where it can be seen: We could still rotate the camera. Which direction is up/right/down/left? One of these is enough, but without all something is missing.
struct Camera {
viewmatrix: na::Matrix4<f32>,
position: na::Vector3<f32>,
view_direction: na::Vector3<f32>,
down_direction: na::Vector3<f32>,
}
This way? Maybe.
Thinking about it: Directions should have a length of one. (A direction of zero would be bad. The difference between view directions (0,0,1) and (0,0,2) would be difficult to find.)
It is possible in nalgebra to encode such requirements in the variable type:
struct Camera {
viewmatrix: na::Matrix4<f32>,
position: na::Vector3<f32>,
view_direction: na::Unit<na::Vector3<f32>>,
down_direction: na::Unit<na::Vector3<f32>>,
}
And the default state of a camera has to include the new fields:
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)),
}
}
}
There are two main topics to cover: How to change the viewmatrix
so that it reflects position
, view_direction
and down_direction
? And: How to
change these values, i.e. what to do when the keys are pressed?
Let us first deal with the second question.
fn update_viewmatrix(&mut self) {
//placeholder
}
A placeholder, something we’ll have to take care of later.
Moving forward is simple: Adding the view direction to our position. Or a multiple of it, depending on how far we want to move.
fn move_forward(&mut self, distance: f32) {
self.position += distance * self.view_direction.as_ref();
self.update_viewmatrix();
}
Shall we have another function to move backward?
fn move_backward(&mut self, distance:f32){
self.move_forward(-distance);
}
Turning right means that the down direction stays the same and we actually turn around this down vector, which means rotating our view direction. Is rotation by a positive angle really correct? Yes, see previous chapter.
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);
}
Looking up or down is a rotation around the direction pointing to the right. (With up being positive angles, check with your right thumb.)
We did not store “right”, but we can compute it from “down” and “forward” (i.e. “view”) as a cross product.
We then have to rotate both view and down directions around this axis:
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);
}
Of course, we should also call these functions:
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);
}
_ => {}
}
}
}
What we still have to do is finding the view matrix.
What does this transformation have to achieve? Firstly, it should move us to the correct position. Then it should rotate our view in the correct direction.
Let me describe the same scenario again: The view matrix should translate the correct position to the origin. It should then rotate everything (around
the origin), such that the view_direction
is turned into the direction pointing forward (i.e.
), and the down_direction
points down, i.e.
coincides with
.
(We cannot move “our position”, because Vulkan will always expect the screen to be this [-1,1]x[-1,1]x[0,1] box we found in Chapter 13. But moving everything else has the same effect.)
So, first a translation
by
with
=position
(the minus, so that position
ends up in the origin). As matrix that is
(cf. Chapter 15); the rotation (say,
) turning
(the direction to the right),
(the down_direction
) and
(the view_direction
) into
,
and
also becomes a matrix (cf. previous chapter):
We can, again, compute the direction to the right as cross-product. And down, view and right have length one and are orthogonal to each other. (Attention: This orthogonality is not guaranteed by types, it relies on us only using “good” transformations when changing these directions. But the functions we have introduced above only rotate.)
The view matrix should then be “first apply T, then R”, so
In code:
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;
}
Let me issue a warning: nalgebra provides functions yielding such “look at matrices”. Since we are working with a right handed coordinate system, you
might expect that the look_at_rh
function (which expects position, a point to look at (i.e. position+view_direction) and the direction which is
“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();
would give you the correct matrix. It doesn’t. (The signs don’t fit in a few places.) The reason is that nalgebra uses the coordinate system of
OpenGL, which follows slightly different conventions than that of Vulkan (
points up in OpenGL, and for
also the range is different).
It is by no means impossible to adapt the functions or the coordinate system or to work around this issue (for example, we can use down_direction
as
the up
argument and use look_at_lh
instead) — but it is something to be aware of.
Now we can move around. And it works. (The right arrow makes us “turn our head to the right”, so everything on screen moves to the left, PageUp and PageDown make us look up or down, respectively; and we can shift forward or backwards with the up and down arrow keys.)
But somehow, things look off. Sometimes the boxes just disappear when we are moving backwards or turning to a side.
Remember that we only show on screen everything that is in our [-1,1]x[-1,1]x[0,1] box. As soon as one of the cubes leaves this box (after this view matrix transform is applied), it immediately disappears.
Also, there is no perspective in here: Getting things on the screen still means “just ignore their z-coordinate”. (Okay, that’s also some kind of perspective, but it is not the one our eyes use.)
We need a perspective projection, like with these train tracks in the picture in Chapter 15.
We’ll take care of that next chapter. For now, once more the source code:
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)
};
}
}