Compare commits

..

2 Commits

Author SHA1 Message Date
Matthew Gordon e89c1e4c3d Make the cube rotate 2024-11-14 22:08:23 -04:00
Matthew Gordon 35785dbf01 Redraw continuously instead of busy-waiting at 100% CPU
Previously, Pteropus would only redraw when the window was resized, but
it would continuously poll for events, keeping a CPU core at 100% even
when not doing anything.

It now automatically queues a new RedrawRequested event after each draw
finishes. The view() call will also limit the frame rate to the video
refresh rate and block when there are more than three frames queued up,
so we're not busywaiting any more.
2024-11-14 20:51:23 -04:00
3 changed files with 73 additions and 21 deletions

View File

@ -12,6 +12,8 @@ pub struct DemRenderer {
vertex_buffer: wgpu::Buffer,
index_buffer: wgpu::Buffer,
index_count: usize,
uniforms: Uniforms,
animation_start: std::time::Instant,
}
#[repr(C)]
@ -20,6 +22,51 @@ struct Vertex {
position: [f32; 4],
}
struct Uniforms {
projection_matrix: glam::Mat4,
view_matrix: glam::Mat4,
buffer: wgpu::Buffer,
}
impl Uniforms {
fn new(device: &wgpu::Device, aspect_ratio: f32) -> Self {
let projection_matrix =
glam::Mat4::perspective_rh(std::f32::consts::FRAC_PI_4, aspect_ratio, 1.0, 10.0);
let view_matrix = glam::Mat4::look_at_rh(glam::Vec3::ZERO, glam::Vec3::ZERO, glam::Vec3::Z);
let camera_matrix = projection_matrix * view_matrix;
let camera_matrix_ref: &[f32; 16] = camera_matrix.as_ref();
let buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Uniform Buffer"),
contents: bytemuck::cast_slice(camera_matrix_ref),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
});
Self {
projection_matrix,
view_matrix,
buffer,
}
}
fn set_camera_position(&mut self, position: glam::Vec3) {
self.view_matrix = glam::Mat4::look_at_rh(position, glam::Vec3::ZERO, glam::Vec3::Z);
}
fn set_aspect_ratio(&mut self, ratio: f32) {
self.projection_matrix =
glam::Mat4::perspective_rh(std::f32::consts::FRAC_PI_4, ratio, 1.0, 10.0);
}
fn update_buffer(&self, queue: &wgpu::Queue) {
let camera_matrix = self.projection_matrix * self.view_matrix;
let camera_matrix_ref: &[f32; 16] = camera_matrix.as_ref();
queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(camera_matrix_ref))
}
fn binding(&self) -> wgpu::BindingResource<'_> {
self.buffer.as_entire_binding()
}
}
impl DemRenderer {
pub fn new(
source: Rc<Dem>,
@ -42,16 +89,10 @@ impl DemRenderer {
let index_count = index_data.len();
let uniform_buf = {
let mx_total =
generate_matrix(surface_config.width as f32 / surface_config.height as f32);
let mx_ref: &[f32; 16] = mx_total.as_ref();
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Uniform Buffer"),
contents: bytemuck::cast_slice(mx_ref),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
})
};
let uniforms = Uniforms::new(
device,
surface_config.width as f32 / surface_config.height as f32,
);
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: None,
@ -83,7 +124,7 @@ impl DemRenderer {
layout: &bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: uniform_buf.as_entire_binding(),
resource: uniforms.binding(),
}],
label: Some("DemRendererBindGroup"),
});
@ -140,6 +181,8 @@ impl DemRenderer {
vertex_buffer,
index_buffer,
index_count,
animation_start: std::time::Instant::now(),
uniforms,
}
}
@ -147,6 +190,9 @@ impl DemRenderer {
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("DemRendererCommandEncoder"),
});
self.uniforms
.set_camera_position(get_animated_camera_position(self.animation_start.elapsed()));
self.uniforms.update_buffer(queue);
{
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("DemRendererRenderPass"),
@ -224,13 +270,11 @@ fn create_vertices() -> (Vec<Vertex>, Vec<u16>) {
(vertex_data.to_vec(), index_data.to_vec())
}
fn generate_matrix(aspect_ratio: f32) -> glam::Mat4 {
let projection =
glam::Mat4::perspective_rh(std::f32::consts::FRAC_PI_4, aspect_ratio, 1.0, 10.0);
let view = glam::Mat4::look_at_rh(
glam::Vec3::new(1.5f32, -5.0, 3.0),
glam::Vec3::ZERO,
glam::Vec3::Z,
);
projection * view
fn get_animated_camera_position(animation_time: std::time::Duration) -> glam::Vec3 {
let animation_phase = 2.0 * std::f32::consts::PI * (animation_time.as_secs_f32() % 10.0) / 10.0;
glam::Vec3::new(
5.0 * f32::sin(animation_phase),
5.0 * f32::cos(animation_phase),
3.0,
)
}

View File

@ -106,6 +106,7 @@ impl MvuApp<Model> for App {
let mut config = surface
.get_default_config(&adapter, size.width, size.height)
.unwrap();
config.present_mode = wgpu::PresentMode::AutoVsync;
config.view_formats.push(config.format);
surface.configure(&device, &config);

View File

@ -45,6 +45,10 @@ where
block_on(self.app.init(&instance, surface, Size2i { width, height }));
}
// TODO: The idea with these `block_on()` calls is that eventually I'll
// write an async executor that runs on top of the winit even loop and then
// this stuff can be properly async.
fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
match event {
WindowEvent::Resized(new_size) => block_on(self.app.resize(Size2i {
@ -55,7 +59,10 @@ where
println!("The close button was pressed; stopping");
event_loop.exit();
}
WindowEvent::RedrawRequested => block_on(self.app.view(self.model.clone())).unwrap(),
WindowEvent::RedrawRequested => {
block_on(self.app.view(self.model.clone())).unwrap();
self.window.as_ref().unwrap().request_redraw();
},
WindowEvent::DroppedFile(file_path) => {
self.model = block_on(
self.app