mod camera; mod texture; mod uniforms; use image::GenericImageView; use std::{iter, mem::size_of, num::NonZeroU32, time::Instant}; use wgpu::{util::DeviceExt, BufferAddress}; use winit::{ event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, event_loop::{ControlFlow, EventLoop}, window::{Window, WindowBuilder}, }; use crate::{camera::Camera, texture::Texture, uniforms::Uniforms}; #[repr(C)] #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] struct Vertex { position: [f32; 3], texture_coordinates: [f32; 2], } impl Vertex { fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { wgpu::VertexBufferLayout { array_stride: size_of::() as BufferAddress, step_mode: wgpu::InputStepMode::Vertex, attributes: &[ wgpu::VertexAttribute { offset: 0, shader_location: 0, format: wgpu::VertexFormat::Float32x3, }, wgpu::VertexAttribute { offset: size_of::<[f32; 3]>() as BufferAddress, shader_location: 1, format: wgpu::VertexFormat::Float32x2, }, ], } } } const VERTICES: &[Vertex] = &[ Vertex { position: [-0.5, -0.5, -0.5], texture_coordinates: [0.0, 0.0], }, // 0 Vertex { position: [-0.5, -0.5, 0.5], texture_coordinates: [0.0, 1.0], }, // 1 Vertex { position: [-0.5, 0.5, 0.5], texture_coordinates: [1.0, 1.0], }, // 2 Vertex { position: [-0.5, 0.5, -0.5], texture_coordinates: [1.0, 0.0], }, // 3 Vertex { position: [0.5, -0.5, -0.5], texture_coordinates: [1.0, 1.0], }, // 4 Vertex { position: [0.5, -0.5, 0.5], texture_coordinates: [1.0, 0.0], }, // 5 Vertex { position: [0.5, 0.5, 0.5], texture_coordinates: [0.0, 0.0], }, // 6 Vertex { position: [0.5, 0.5, -0.5], texture_coordinates: [0.0, 1.0], }, // 7 ]; #[rustfmt::skip] const INDICES: &[u16] = &[ // left 0, 1, 2, 0, 2, 3, // bottom 1, 0, 4, 1, 4, 5, // right 5, 7, 6, 5, 4, 7, // top 3, 2, 6, 3, 6, 7, // front 2, 1, 5, 2, 5, 6, // back 0, 3, 7, 0, 7, 4, ]; struct State { surface: wgpu::Surface, device: wgpu::Device, queue: wgpu::Queue, sc_desc: wgpu::SwapChainDescriptor, swap_chain: wgpu::SwapChain, size: winit::dpi::PhysicalSize, render_pipeline: wgpu::RenderPipeline, vertex_buffer: wgpu::Buffer, index_buffer: wgpu::Buffer, num_indices: u32, dirt_diffuse_bind_group: wgpu::BindGroup, camera: Camera, uniforms: Uniforms, uniform_buffer: wgpu::Buffer, uniform_bind_group: wgpu::BindGroup, } impl State { async fn new(window: &Window) -> Self { let size = window.inner_size(); // The instance is a handle to our GPU // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY); let surface = unsafe { instance.create_surface(window) }; let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::HighPerformance, compatible_surface: Some(&surface), }) .await .unwrap(); let (device, queue) = adapter .request_device( &wgpu::DeviceDescriptor { label: None, features: wgpu::Features::empty(), limits: wgpu::Limits::default(), }, None, // Trace path ) .await .unwrap(); let swap_chain_descriptor = wgpu::SwapChainDescriptor { usage: wgpu::TextureUsage::RENDER_ATTACHMENT, format: adapter.get_swap_chain_preferred_format(&surface).unwrap(), width: size.width, height: size.height, present_mode: wgpu::PresentMode::Immediate, }; let swap_chain = device.create_swap_chain(&surface, &swap_chain_descriptor); let dirt_diffuse_texture = Texture::from_bytes( &device, &queue, include_bytes!("../assets/block/dirt.png"), "dirt_diffuse", ) .unwrap(); let texture_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("exture_bind_group_layout"), entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStage::FRAGMENT, ty: wgpu::BindingType::Texture { multisampled: false, view_dimension: wgpu::TextureViewDimension::D2, sample_type: wgpu::TextureSampleType::Float { filterable: true }, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStage::FRAGMENT, ty: wgpu::BindingType::Sampler { comparison: false, filtering: true, }, count: None, }, ], }); let dirt_diffuse_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("dirt_diffuse_bind_group"), layout: &texture_bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&dirt_diffuse_texture.view), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&dirt_diffuse_texture.sampler), }, ], }); let shader = device.create_shader_module(&wgpu::ShaderModuleDescriptor { label: Some("Shader"), flags: wgpu::ShaderFlags::all(), source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()), }); let camera = Camera { // position the camera one unit up and 2 units back // +z is out of the screen eye: (0.0, 1.0, 2.0).into(), // have it look at the origin target: (0.0, 0.0, 0.0).into(), // which way is "up" up: cgmath::Vector3::unit_y(), aspect: swap_chain_descriptor.width as f32 / swap_chain_descriptor.height as f32, fovy: 45.0, znear: 0.1, zfar: 100.0, }; let mut uniforms = Uniforms::new(); uniforms.update_view_proj(&camera); let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Uniform Buffer"), contents: bytemuck::cast_slice(&[uniforms]), usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, }); let uniform_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { entries: &[wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStage::VERTEX, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None, }, count: None, }], label: Some("uniform_bind_group_layout"), }); let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("Render Pipeline Layout"), bind_group_layouts: &[&texture_bind_group_layout, &uniform_bind_group_layout], push_constant_ranges: &[], }); 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: "main", buffers: &[Vertex::desc()], }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: "main", targets: &[wgpu::ColorTargetState { format: swap_chain_descriptor.format, blend: Some(wgpu::BlendState { color: wgpu::BlendComponent::REPLACE, alpha: wgpu::BlendComponent::REPLACE, }), write_mask: wgpu::ColorWrite::ALL, }], }), primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, strip_index_format: None, front_face: wgpu::FrontFace::Ccw, cull_mode: Some(wgpu::Face::Back), // Setting this to anything other than Fill requires Features::NON_FILL_POLYGON_MODE polygon_mode: wgpu::PolygonMode::Fill, // Requires Features::DEPTH_CLAMPING clamp_depth: false, // Requires Features::CONSERVATIVE_RASTERIZATION conservative: false, }, depth_stencil: None, multisample: wgpu::MultisampleState { count: 1, mask: !0, alpha_to_coverage_enabled: false, }, }); let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Vertex Buffer"), contents: bytemuck::cast_slice(VERTICES), usage: wgpu::BufferUsage::VERTEX, }); let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Index Buffer"), contents: bytemuck::cast_slice(INDICES), usage: wgpu::BufferUsage::INDEX, }); let num_indices = INDICES.len() as u32; let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &uniform_bind_group_layout, entries: &[wgpu::BindGroupEntry { binding: 0, resource: uniform_buffer.as_entire_binding(), }], label: Some("uniform_bind_group"), }); Self { surface, device, queue, sc_desc: swap_chain_descriptor, swap_chain, size, render_pipeline, vertex_buffer, index_buffer, num_indices, camera, uniforms, uniform_buffer, uniform_bind_group, dirt_diffuse_bind_group, } } fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { println!("resizing to {:?}", new_size); self.size = new_size; self.sc_desc.width = new_size.width; self.sc_desc.height = new_size.height; self.swap_chain = self.device.create_swap_chain(&self.surface, &self.sc_desc); } #[allow(unused_variables)] fn input(&mut self, event: &WindowEvent) -> bool { false } fn update(&mut self) {} fn render(&mut self) -> Result<(), wgpu::SwapChainError> { let frame = self.swap_chain.get_current_frame()?.output; let mut encoder = self .device .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Render Encoder"), }); { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("Render Pass"), color_attachments: &[wgpu::RenderPassColorAttachment { view: &frame.view, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), store: true, }, }], depth_stencil_attachment: None, }); render_pass.set_pipeline(&self.render_pipeline); render_pass.set_bind_group(0, &self.dirt_diffuse_bind_group, &[]); render_pass.set_bind_group(1, &self.uniform_bind_group, &[]); render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16); render_pass.draw_indexed(0..self.num_indices, 0, 0..1); } self.queue.submit(iter::once(encoder.finish())); Ok(()) } } fn main() { env_logger::init(); let event_loop = EventLoop::new(); let window = WindowBuilder::new() .with_title("\u{1f980}\u{1f980}\u{1f980}\u{1f980}\u{1f980}") .build(&event_loop) .unwrap(); use futures::executor::block_on; // Since main can't be async, we're going to need to block let mut state = block_on(State::new(&window)); let mut frames = 0; let mut instant = Instant::now(); event_loop.run(move |event, _, control_flow| { match event { Event::WindowEvent { ref event, window_id, } if window_id == window.id() => { if !state.input(event) { match event { WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, WindowEvent::KeyboardInput { input, .. } => match input { KeyboardInput { state: ElementState::Pressed, virtual_keycode: Some(VirtualKeyCode::Escape), .. } => *control_flow = ControlFlow::Exit, _ => {} }, WindowEvent::Resized(physical_size) => { state.resize(*physical_size); } WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { // new_inner_size is &mut so w have to dereference it twice state.resize(**new_inner_size); } _ => {} } } } Event::RedrawRequested(_) => { frames += 1; if frames % 1000 == 0 { let frametime = instant.elapsed() / 1000; let fps = 1_000_000 / frametime.as_micros(); println!("{:?} - {} fps", frametime, fps); instant = Instant::now(); } state.update(); match state.render() { Ok(_) => {} // Recreate the swap_chain if lost Err(wgpu::SwapChainError::Lost) => state.resize(state.size), // The system is out of memory, we should probably quit Err(wgpu::SwapChainError::OutOfMemory) => *control_flow = ControlFlow::Exit, // All other errors (Outdated, Timeout) should be resolved by the next frame Err(e) => eprintln!("{:?}", e), } } Event::MainEventsCleared => { // RedrawRequested will only trigger once, unless we manually // request it. window.request_redraw(); } _ => {} } }); }