pub mod texture; use bytemuck; use core::f32::consts; use eframe::{ egui, egui_wgpu::{self, wgpu}, wgpu::util::DeviceExt, }; const NUM_INSTANCES_PER_ROW: u32 = 10; const INSTANCE_DISPLACEMENT: glam::Vec3 = glam::Vec3::new( NUM_INSTANCES_PER_ROW as f32 * 0.5, 0.0, NUM_INSTANCES_PER_ROW as f32 * 0.5, ); fn main() -> eframe::Result<()> { env_logger::init(); let options = eframe::NativeOptions { renderer: eframe::Renderer::Wgpu, ..Default::default() }; eframe::run_native( "Eskin Model Player", options, Box::new(|cc| Ok(Box::new(EskinDesktopApp::new(cc)))), ) } struct EskinDesktopApp { show_scene_panel: bool, show_config_panel: bool, show_stats_panel: bool, } impl EskinDesktopApp { fn new(cc: &eframe::CreationContext<'_>) -> Self { let wgpu_state = cc .wgpu_render_state .as_ref() .expect("need open eframe wgpu renderer feature"); let mut renderer = wgpu_state.renderer.write(); renderer .callback_resources .insert(BackgroundRenderResources::new( &wgpu_state.device, &wgpu_state.target_format, &wgpu_state.queue, 12, 7, )); Self { show_scene_panel: true, show_config_panel: true, show_stats_panel: true, } } fn draw_wgpu_background(&mut self, ui: &mut egui::Ui) { let rect = ui.max_rect(); let width = rect.width(); let height = rect.height(); ui.painter().add(egui_wgpu::Callback::new_paint_callback( rect, WgpuBackgroundCallback { aspect: width / height, }, )); } fn draw_toolbar(&mut self, ui: &mut egui::Ui) { egui::Panel::top("main_menu").show_inside(ui, |ui| { ui.horizontal(|ui| { ui.checkbox(&mut self.show_scene_panel, "Scene"); ui.checkbox(&mut self.show_config_panel, "Config"); ui.checkbox(&mut self.show_stats_panel, "Stats"); }); }); } fn draw_floating_panels(&mut self, ctx: &egui::Context) { egui::Window::new("Scene") .open(&mut self.show_scene_panel) .default_pos([16.0, 48.0]) .show(ctx, |ui| { ui.label("Models / materials / lights"); }); egui::Window::new("Config") .open(&mut self.show_config_panel) .default_pos([840.0, 48.0]) .show(ctx, |ui| { ui.label("Render and viewport settings"); }); egui::Window::new("Stats") .open(&mut self.show_stats_panel) .default_pos([16.0, 520.0]) .show(ctx, |ui| { ui.label("FPS / GPU info"); }); } } impl eframe::App for EskinDesktopApp { fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) { let ctx = ui.ctx().clone(); self.draw_wgpu_background(ui); self.draw_toolbar(ui); self.draw_floating_panels(&ctx); // Keep repainting while the wgpu background is a realtime viewport. ctx.request_repaint(); } } struct WgpuBackgroundCallback { aspect: f32, } impl egui_wgpu::CallbackTrait for WgpuBackgroundCallback { fn prepare( &self, _device: &wgpu::Device, queue: &wgpu::Queue, _screen_descriptor: &egui_wgpu::ScreenDescriptor, _egui_encoder: &mut wgpu::CommandEncoder, resources: &mut egui_wgpu::CallbackResources, ) -> Vec { let resources: &mut BackgroundRenderResources = resources.get_mut().unwrap(); resources.prepare(queue, self.aspect); Vec::new() } fn paint( &self, _info: egui::PaintCallbackInfo, render_pass: &mut wgpu::RenderPass<'static>, resources: &egui_wgpu::CallbackResources, ) { let resources: &BackgroundRenderResources = resources.get().unwrap(); resources.paint(render_pass); } } struct BackgroundRenderResources { camera: Camera, camera_uniform: CameraUniform, camera_buffer: wgpu::Buffer, camera_bind_group: wgpu::BindGroup, #[allow(dead_code)] diffuse_texture: texture::Texture, diffuse_bind_group: wgpu::BindGroup, render_pipeline: wgpu::RenderPipeline, vertex_buffer: wgpu::Buffer, index_buffer: wgpu::Buffer, num_indices: u32, instance: Vec, instance_buffer: wgpu::Buffer, cols: Option, rows: Option, } impl BackgroundRenderResources { fn new( device: &wgpu::Device, target_format: &wgpu::TextureFormat, queue: &wgpu::Queue, rows: u8, cols: u8, ) -> Self { let diffuse_bytes = include_bytes!("happy-tree.png"); let diffuse_texture = texture::Texture::from_bytes(device, queue, diffuse_bytes, "happy-tree.png").unwrap(); let texture_group_bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("texture_bind_group_layout"), entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), count: None, }, ], }); let diffuse_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("texture_bind_group"), layout: &texture_group_bind_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&diffuse_texture.view), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler), }, ], }); let camera = Camera { eye: (0.0, 5.0, 10.0).into(), target: (0.0, 0.0, 0.0).into(), up: glam::Vec3::Y, aspect: 1.0, fovy: 45.0, znear: 0.1, zfar: 100.0, }; let mut camera_uniform = CameraUniform::new(); camera_uniform.update_view_proj(&camera); let camera_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Camera Buffer"), contents: bytemuck::cast_slice(&[camera_uniform]), usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, }); let instances = (0..NUM_INSTANCES_PER_ROW) .flat_map(|z| { (0..NUM_INSTANCES_PER_ROW).map(move |x| { let position = glam::Vec3 { x: x as f32, y: 0.0, z: z as f32, } - INSTANCE_DISPLACEMENT; let rotation = if position.length().abs() <= f32::EPSILON { // this is needed so an object at (0, 0, 0) won't get scaled to zero // as Quaternions can effect scale if they're not create correctly glam::Quat::from_axis_angle(glam::Vec3::Z, 0.0) } else { glam::Quat::from_axis_angle(position.normalize(), consts::FRAC_PI_2) }; Instance { position, rotation } }) }) .collect::>(); let instance_data = instances.iter().map(Instance::to_raw).collect::>(); let instance_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Instance Buffer"), contents: bytemuck::cast_slice(&instance_data), usage: wgpu::BufferUsages::VERTEX, }); let camera_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("camera_bind_group_layout"), entries: &[wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::VERTEX, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None, }, count: None, }], }); let camera_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("camera_bind_group"), layout: &camera_bind_group_layout, entries: &[wgpu::BindGroupEntry { binding: 0, resource: camera_buffer.as_entire_binding(), }], }); let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("Shader"), source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()), }); let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("Render Pipeline Layout"), bind_group_layouts: &[ Some(&texture_group_bind_layout), Some(&camera_bind_group_layout), ], immediate_size: 0, }); let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("Render Pipeline"), layout: Some(&render_pipeline_layout), vertex: wgpu::VertexState { module: &shader, entry_point: Some("vs_main"), compilation_options: Default::default(), buffers: &[Vertex::desc(), InstanceRaw::desc()], }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: Some("fs_main"), compilation_options: Default::default(), targets: &[Some(wgpu::ColorTargetState { format: target_format.clone(), blend: Some(wgpu::BlendState { color: wgpu::BlendComponent::REPLACE, alpha: wgpu::BlendComponent::REPLACE, }), write_mask: wgpu::ColorWrites::ALL, })], }), primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, strip_index_format: None, front_face: wgpu::FrontFace::Ccw, cull_mode: Some(wgpu::Face::Back), polygon_mode: wgpu::PolygonMode::Fill, // Requires Features::DEPTH_CLIP_CONTROL unclipped_depth: false, // Requires Features::CONSERVATIVE_RASTERIZATION conservative: false, }, depth_stencil: None, multisample: wgpu::MultisampleState { count: 1, mask: !0, alpha_to_coverage_enabled: false, }, multiview_mask: None, cache: None, }); let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Vertex Buffer"), contents: bytemuck::cast_slice(VERTICES), usage: wgpu::BufferUsages::VERTEX, }); let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Index Buffer"), contents: bytemuck::cast_slice(INDICES), usage: wgpu::BufferUsages::INDEX, }); let num_indices = INDICES.len() as u32; Self { camera, camera_uniform, camera_buffer, camera_bind_group, diffuse_texture, diffuse_bind_group, render_pipeline, vertex_buffer, index_buffer, num_indices, instance: instances, instance_buffer, cols: Some(cols), rows: Some(rows), } } // fn with_dot_matrix() fn prepare(&mut self, queue: &wgpu::Queue, aspect: f32) { self.camera.aspect = aspect; self.camera_uniform.update_view_proj(&self.camera); queue.write_buffer( &self.camera_buffer, 0, bytemuck::cast_slice(&[self.camera_uniform]), ); } fn paint(&self, render_pass: &mut wgpu::RenderPass<'_>) { // TODO: set pipeline / bind groups / buffers and draw the model viewport here. render_pass.set_pipeline(&self.render_pipeline); render_pass.set_bind_group(0, &self.diffuse_bind_group, &[]); render_pass.set_bind_group(1, &self.camera_bind_group, &[]); render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); render_pass.set_vertex_buffer(1, self.instance_buffer.slice(..)); render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16); // UPDATED! render_pass.draw_indexed(0..self.num_indices, 0, 0..self.instance.len() as _); } } #[repr(C)] #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] struct Vertex { position: [f32; 3], tex_coords: [f32; 2], } impl Vertex { fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { use core::mem; wgpu::VertexBufferLayout { array_stride: mem::size_of::() as wgpu::BufferAddress, step_mode: wgpu::VertexStepMode::Vertex, attributes: &[ wgpu::VertexAttribute { offset: 0, shader_location: 0, format: wgpu::VertexFormat::Float32x3, }, wgpu::VertexAttribute { offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, shader_location: 1, format: wgpu::VertexFormat::Float32x2, }, ], } } } const VERTICES: &[Vertex] = &[ Vertex { position: [-0.0868241, 0.49240386, 0.0], tex_coords: [0.4131759, 0.00759614], }, // A Vertex { position: [-0.49513406, 0.06958647, 0.0], tex_coords: [0.0048659444, 0.43041354], }, // B Vertex { position: [-0.21918549, -0.44939706, 0.0], tex_coords: [0.28081453, 0.949397], }, // C Vertex { position: [0.35966998, -0.3473291, 0.0], tex_coords: [0.85967, 0.84732914], }, // D Vertex { position: [0.44147372, 0.2347359, 0.0], tex_coords: [0.9414737, 0.2652641], }, // E ]; const INDICES: &[u16] = &[0, 1, 4, 1, 2, 4, 2, 3, 4]; struct Camera { eye: glam::Vec3, target: glam::Vec3, up: glam::Vec3, aspect: f32, fovy: f32, znear: f32, zfar: f32, } impl Camera { fn build_view_projection_matrix(&self) -> glam::Mat4 { let view = glam::Mat4::look_at_rh(self.eye, self.target, self.up); let projection = glam::Mat4::perspective_rh(self.fovy.to_radians(), self.aspect, self.znear, self.zfar); projection * view } } #[repr(C)] #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] struct CameraUniform { view_proj: [[f32; 4]; 4], } impl CameraUniform { fn new() -> Self { Self { view_proj: glam::Mat4::IDENTITY.to_cols_array_2d(), } } fn update_view_proj(&mut self, camera: &Camera) { self.view_proj = camera.build_view_projection_matrix().to_cols_array_2d(); } } struct Instance { position: glam::Vec3, rotation: glam::Quat, } #[repr(C)] #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] struct InstanceRaw { model: [[f32; 4]; 4], } impl Instance { fn to_raw(&self) -> InstanceRaw { InstanceRaw { model: (glam::Mat4::from_translation(self.position) * glam::Mat4::from_quat(self.rotation)) .to_cols_array_2d(), } } } impl InstanceRaw { fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { use core::mem; wgpu::VertexBufferLayout { array_stride: mem::size_of::() as wgpu::BufferAddress, step_mode: wgpu::VertexStepMode::Instance, attributes: &[ wgpu::VertexAttribute { offset: 0, shader_location: 5, format: wgpu::VertexFormat::Float32x4, }, wgpu::VertexAttribute { offset: core::mem::size_of::<[f32; 4]>() as wgpu::BufferAddress, shader_location: 6, format: wgpu::VertexFormat::Float32x4, }, wgpu::VertexAttribute { offset: core::mem::size_of::<[f32; 8]>() as wgpu::BufferAddress, shader_location: 7, format: wgpu::VertexFormat::Float32x4, }, wgpu::VertexAttribute { offset: core::mem::size_of::<[f32; 12]>() as wgpu::BufferAddress, shader_location: 8, format: wgpu::VertexFormat::Float32x4, }, ], } } }