diff --git a/Cargo.toml b/Cargo.toml index 0e8ed75..2bdfa4b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,13 +9,20 @@ publish = false default-run = "Renderer" +[lib] +name = "Renderer" +crate-type = ["rlib"] +path = "Source/lib.rs" + [[bin]] name = "Renderer" path = "Source/Main.rs" +doc = false [[bin]] name = "Rainbow" path = "Source/Rainbow.rs" +doc = false [dependencies] anyhow = "1.0" @@ -25,5 +32,6 @@ env_logger = "0.9" image = "0.23" log = "0.4" pollster = "0.2" +tobj = "3.0" wgpu = "0.11" winit = "0.25" diff --git a/Resources/Cube.mtl b/Resources/Cube.mtl new file mode 100644 index 0000000..7f75f49 --- /dev/null +++ b/Resources/Cube.mtl @@ -0,0 +1,14 @@ +# Blender MTL File: 'Cube.blend' +# Material Count: 1 + +newmtl Material +Ns 323.999994 +Ka 1.000000 1.000000 1.000000 +Kd 0.800000 0.800000 0.800000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.450000 +d 1.000000 +illum 2 +map_Bump T_Normal.png +map_Kd T_Stone.png diff --git a/Resources/Cube.obj b/Resources/Cube.obj new file mode 100644 index 0000000..0c1b243 --- /dev/null +++ b/Resources/Cube.obj @@ -0,0 +1,41 @@ +# Blender v2.93.1 OBJ File: 'Cube.blend' +# www.blender.org +mtllib Cube.mtl +o Cube +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v -1.000000 1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v 1.000000 -1.000000 -1.000000 +vt 0.625000 0.500000 +vt 0.875000 0.500000 +vt 0.875000 0.750000 +vt 0.625000 0.750000 +vt 0.375000 0.750000 +vt 0.625000 1.000000 +vt 0.375000 1.000000 +vt 0.375000 0.000000 +vt 0.625000 0.000000 +vt 0.625000 0.250000 +vt 0.375000 0.250000 +vt 0.125000 0.500000 +vt 0.375000 0.500000 +vt 0.125000 0.750000 +vn 0.0000 1.0000 0.0000 +vn 0.0000 -0.0000 1.0000 +vn -1.0000 -0.0000 0.0000 +vn 0.0000 -1.0000 -0.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +g Cube_Cube_Material +usemtl Material +s 1 +f 1/1/1 2/2/1 3/3/1 4/4/1 +f 5/5/2 4/4/2 3/6/2 6/7/2 +f 6/8/3 3/9/3 2/10/3 7/11/3 +f 7/12/4 8/13/4 5/5/4 6/14/4 +f 8/13/5 1/1/5 4/4/5 5/5/5 +f 7/11/6 2/10/6 1/1/6 8/13/6 diff --git a/Resources/T_Normal.png b/Resources/T_Normal.png new file mode 100644 index 0000000..f672acd Binary files /dev/null and b/Resources/T_Normal.png differ diff --git a/Resources/T_Stone.png b/Resources/T_Stone.png new file mode 100644 index 0000000..52089f4 Binary files /dev/null and b/Resources/T_Stone.png differ diff --git a/Resources/T_White.png b/Resources/T_White.png new file mode 100644 index 0000000..beb47d3 Binary files /dev/null and b/Resources/T_White.png differ diff --git a/Source/DrawModel.rs b/Source/DrawModel.rs new file mode 100644 index 0000000..deb8bf1 --- /dev/null +++ b/Source/DrawModel.rs @@ -0,0 +1,66 @@ +use crate::{Material, Mesh, Model}; +use std::ops::Range; + +pub trait DrawModel<'a> { + fn draw_mesh( + &mut self, + mesh: &'a Mesh, + material: &'a Material, + camera_bind_group: &'a wgpu::BindGroup, + ); + fn draw_mesh_instanced( + &mut self, + mesh: &'a Mesh, + material: &'a Material, + instances: Range, + camera_bind_group: &'a wgpu::BindGroup, + ); + fn draw_model(&mut self, model: &'a Model, camera_bind_group: &'a wgpu::BindGroup); + fn draw_model_instanced( + &mut self, + model: &'a Model, + instances: Range, + camera_bind_group: &'a wgpu::BindGroup, + ); +} + +impl<'a> DrawModel<'a> for wgpu::RenderPass<'a> { + fn draw_mesh( + &mut self, + mesh: &'a Mesh, + material: &'a Material, + camera_bind_group: &'a wgpu::BindGroup, + ) { + self.draw_mesh_instanced(mesh, material, 0..1, camera_bind_group); + } + + fn draw_mesh_instanced( + &mut self, + mesh: &'a Mesh, + material: &'a Material, + instances: Range, + camera_bind_group: &'a wgpu::BindGroup, + ) { + self.set_vertex_buffer(0, mesh.vertex_buffer.slice(..)); + self.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint32); + self.set_bind_group(0, &material.bind_group, &[]); + self.set_bind_group(1, camera_bind_group, &[]); + self.draw_indexed(0..mesh.num_elements, 0, instances); + } + + fn draw_model(&mut self, model: &'a Model, camera_bind_group: &'a wgpu::BindGroup) { + self.draw_model_instanced(model, 0..1, camera_bind_group); + } + + fn draw_model_instanced( + &mut self, + model: &'a Model, + instances: Range, + camera_bind_group: &'a wgpu::BindGroup, + ) { + for mesh in &model.meshes { + let material = &model.materials[mesh.material]; + self.draw_mesh_instanced(mesh, material, instances.clone(), camera_bind_group); + } + } +} diff --git a/Source/Main.rs b/Source/Main.rs index 0e82b12..6ceac9e 100644 --- a/Source/Main.rs +++ b/Source/Main.rs @@ -1,579 +1,5 @@ #![allow(non_snake_case)] -#[path = "Camera.rs"] -mod _Camera; -use _Camera::*; - -#[path = "Instance.rs"] -mod _Instance; -use _Instance::*; - -#[path = "Texture.rs"] -mod _Texture; -use _Texture::*; - -use bytemuck::{Pod, Zeroable}; -use cgmath::prelude::*; -use image::GenericImageView; -use std::mem; -use wgpu::util::DeviceExt; -use winit::dpi::LogicalSize; -use winit::event::*; -use winit::event_loop::{ControlFlow, EventLoop}; -use winit::window::Window; -use winit::window::WindowBuilder; - -#[repr(C)] -#[derive(Copy, Clone, Debug, Pod, Zeroable)] -struct Vertex { - position: [f32; 3], - tex_coords: [f32; 2], -} - -impl Vertex { - fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { - 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, // NEW! - }, - ], - } - } -} - -/// Texture coordinates. -/// -/// ```no_run -/// (0.0) (1.0) -/// V1 ----------------- V2 -/// | / | -/// | Q1 / | -/// | / | -/// | / | -/// | / | -/// | / Q2 | -/// | / | -/// V3 ----------------- V4 -/// (0.1) (1.1) -/// ``` -#[rustfmt::skip] -const VERTICES: &[Vertex] = &[ - Vertex { position: [-0.5, 0.5, 0.0], tex_coords: [0.0, 0.0] }, // A - Vertex { position: [0.5, 0.5, 0.0], tex_coords: [1.0, 0.0] }, // B - Vertex { position: [-0.5, -0.5, 0.0], tex_coords: [0.0, 1.0] }, // C - Vertex { position: [0.5, -0.5, 0.0], tex_coords: [1.0, 1.0] }, // D -]; - -#[rustfmt::skip] -const INDICES: &[u16] = &[ - 1, 0, 2, - 1, 2, 3, -]; - -struct State { - surface: wgpu::Surface, - device: wgpu::Device, - queue: wgpu::Queue, - config: wgpu::SurfaceConfiguration, - size: winit::dpi::PhysicalSize, - render_pipeline: wgpu::RenderPipeline, - vertex_buffer: wgpu::Buffer, - index_buffer: wgpu::Buffer, - num_indices: u32, - diffuse_bind_group: wgpu::BindGroup, - diffuse_texture: Texture, - camera: Camera, - camera_uniform: CameraUniform, - camera_buffer: wgpu::Buffer, - camera_bind_group: wgpu::BindGroup, - camera_controller: CameraController, - instances: Vec, - instance_buffer: wgpu::Buffer, - depth_texture: Texture, -} - -impl State { - async fn new(window: &Window) -> Self { - let size = window.inner_size(); - - // The instance is a handle to our GPU - // Backends::all => Vulkan + Metal + DX12 + Browser WebGPU - let instance = wgpu::Instance::new(wgpu::Backends::all()); - - let surface = unsafe { instance.create_surface(window) }; - - let adapter = instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::HighPerformance, - compatible_surface: Some(&surface), - force_fallback_adapter: false, - }) - .await - .unwrap(); - - let (device, queue) = adapter - .request_device( - &wgpu::DeviceDescriptor { - features: wgpu::Features::empty(), - limits: wgpu::Limits::default(), - label: None, - }, - None, // Trace path - ) - .await - .unwrap(); - - let config = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: surface.get_preferred_format(&adapter).unwrap(), - width: size.width, - height: size.height, - present_mode: wgpu::PresentMode::Fifo, - }; - - surface.configure(&device, &config); - - // Texture - - let diffuse_bytes = include_bytes!("../Resources/Tree.png"); - let diffuse_image = image::load_from_memory(diffuse_bytes).unwrap(); - let diffuse_rgba = diffuse_image.as_rgba8().unwrap(); - - let dimensions = diffuse_image.dimensions(); - - let texture_size = wgpu::Extent3d { - width: dimensions.0, - height: dimensions.1, - depth_or_array_layers: 1, - }; - - let diffuse_texture = device.create_texture(&wgpu::TextureDescriptor { - // All textures are stored as 3D, we represent our 2D texture - // by setting depth to 1. - size: texture_size, - mip_level_count: 1, // We'll talk about this a little later - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - // Most images are stored using sRGB so we need to reflect that here. - format: wgpu::TextureFormat::Rgba8UnormSrgb, - // TEXTURE_BINDING tells wgpu that we want to use this texture in shaders - // COPY_DST means that we want to copy data to this texture - usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, - label: Some("diffuse_texture"), - }); - - queue.write_texture( - // Tells wgpu where to copy the pixel data - wgpu::ImageCopyTexture { - texture: &diffuse_texture, - mip_level: 0, - origin: wgpu::Origin3d::ZERO, - aspect: wgpu::TextureAspect::All, - }, - // The actual pixel data - diffuse_rgba, - // The layout of the texture - wgpu::ImageDataLayout { - offset: 0, - bytes_per_row: std::num::NonZeroU32::new(4 * dimensions.0), - rows_per_image: std::num::NonZeroU32::new(dimensions.1), - }, - texture_size, - ); - - let diffuse_bytes = include_bytes!("../Resources/Tree.png"); - let diffuse_texture = - Texture::from_bytes(&device, &queue, diffuse_bytes, "Tree.png").unwrap(); - - let texture_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::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::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler { - // This is only for TextureSampleType::Depth - comparison: false, - // This should be true if the sample_type of the texture is: - // TextureSampleType::Float { filterable: true } - // Otherwise you'll get an error. - filtering: true, - }, - count: None, - }, - ], - label: Some("texture_bind_group_layout"), - }); - - let diffuse_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &texture_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(&diffuse_texture.view), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler), - }, - ], - label: Some("diffuse_bind_group"), - }); - - // Camera - - 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: config.width as f32 / config.height as f32, - fovy: 45.0, - znear: 0.1, - zfar: 100.0, - }; - - let camera_controller = CameraController::new(0.2); - - 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 camera_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - 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, - }], - label: Some("camera_bind_group_layout"), - }); - - let camera_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &camera_bind_group_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: camera_buffer.as_entire_binding(), - }], - label: Some("camera_bind_group"), - }); - - let depth_texture = Texture::create_depth_texture(&device, &config, "depth_texture"); - - // Shader - - let shader = device.create_shader_module(&wgpu::ShaderModuleDescriptor { - label: Some("Shader"), - source: wgpu::ShaderSource::Wgsl(include_str!("../Shaders/Texture.wgsl").into()), - }); - - // Pipeline - - let render_pipeline_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Render Pipeline Layout"), - bind_group_layouts: &[&texture_bind_group_layout, &camera_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(), InstanceRaw::desc()], - }, - fragment: Some(wgpu::FragmentState { - // 3. - module: &shader, - entry_point: "main", - targets: &[wgpu::ColorTargetState { - // 4. - format: config.format, - blend: Some(wgpu::BlendState::REPLACE), - write_mask: wgpu::ColorWrites::ALL, - }], - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, // 1. - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, // 2. - 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: Some(wgpu::DepthStencilState { - format: Texture::DEPTH_FORMAT, - depth_write_enabled: true, - depth_compare: wgpu::CompareFunction::Less, - stencil: wgpu::StencilState::default(), - bias: wgpu::DepthBiasState::default(), - }), - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - }); - - // Buffers - - 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; - - // Instances - - let instances = (0..NUM_INSTANCES_PER_ROW) - .flat_map(|z| { - (0..NUM_INSTANCES_PER_ROW).map(move |x| { - let position = cgmath::Vector3 { - x: x as f32, - y: 0.0, - z: z as f32, - } - INSTANCE_DISPLACEMENT; - - let rotation = if position.is_zero() { - // 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 created correctly - cgmath::Quaternion::from_axis_angle( - cgmath::Vector3::unit_z(), - cgmath::Deg(0.0), - ) - } else { - cgmath::Quaternion::from_axis_angle(position.normalize(), cgmath::Deg(45.0)) - }; - - 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, - }); - - Self { - surface, - device, - queue, - config, - size, - render_pipeline, - vertex_buffer, - index_buffer, - num_indices, - diffuse_bind_group, - diffuse_texture, - camera, - camera_uniform, - camera_buffer, - camera_bind_group, - camera_controller, - instances, - instance_buffer, - depth_texture, - } - } - - pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { - if new_size.width > 0 && new_size.height > 0 { - self.size = new_size; - self.config.width = new_size.width; - self.config.height = new_size.height; - self.surface.configure(&self.device, &self.config); - } - self.depth_texture = - Texture::create_depth_texture(&self.device, &self.config, "depth_texture"); - } - - fn input(&mut self, event: &WindowEvent) -> bool { - self.camera_controller.process_events(event) - } - - fn update(&mut self) { - self.camera_controller.update_camera(&mut self.camera); - self.camera_uniform.update_view_proj(&self.camera); - self.queue.write_buffer( - &self.camera_buffer, - 0, - bytemuck::cast_slice(&[self.camera_uniform]), - ); - } - - fn render(&mut self) -> Result<(), wgpu::SurfaceError> { - let output = self.surface.get_current_texture()?; - let view = output - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - - 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: &[ - // This is what [[location(0)]] in the fragment shader targets - wgpu::RenderPassColorAttachment { - view: &view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color { - r: 0.0, - g: 0.0, - b: 0.0, - a: 1.0, - }), - store: true, - }, - }, - ], - depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { - view: &self.depth_texture.view, - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Clear(1.0), - store: true, - }), - stencil_ops: None, - }), - }); - - 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); - - render_pass.draw_indexed(0..self.num_indices, 0, 0..self.instances.len() as _); - } - - // submit will accept anything that implements IntoIter - self.queue.submit(std::iter::once(encoder.finish())); - output.present(); - - Ok(()) - } -} - fn main() { - env_logger::init(); - - let event_loop = EventLoop::new(); - - let window = WindowBuilder::new() - .with_title("Renderer") - .with_inner_size(LogicalSize::new(1280, 720)) - .build(&event_loop) - .unwrap(); - - // State::new uses async code, so we're going to wait for it to finish - let mut state = pollster::block_on(State::new(&window)); - - 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 - | WindowEvent::KeyboardInput { - 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, .. } => { - state.resize(**new_inner_size); - } - _ => {} - } - } - } - Event::RedrawRequested(_) => { - state.update(); - match state.render() { - Ok(_) => {} - // Reconfigure the surface if lost - Err(wgpu::SurfaceError::Lost) => state.resize(state.size), - // The system is out of memory, we should probably quit - Err(wgpu::SurfaceError::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(); - } - _ => {} - } - }); + Renderer::Main(); } diff --git a/Source/Material.rs b/Source/Material.rs new file mode 100644 index 0000000..07df852 --- /dev/null +++ b/Source/Material.rs @@ -0,0 +1,7 @@ +use crate::Texture; + +pub struct Material { + pub name: String, + pub diffuse_texture: Texture, + pub bind_group: wgpu::BindGroup, +} diff --git a/Source/Mesh.rs b/Source/Mesh.rs new file mode 100644 index 0000000..bd452c6 --- /dev/null +++ b/Source/Mesh.rs @@ -0,0 +1,7 @@ +pub struct Mesh { + pub name: String, + pub vertex_buffer: wgpu::Buffer, + pub index_buffer: wgpu::Buffer, + pub num_elements: u32, + pub material: usize, +} diff --git a/Source/Model.rs b/Source/Model.rs new file mode 100644 index 0000000..4620dbf --- /dev/null +++ b/Source/Model.rs @@ -0,0 +1,139 @@ +use crate::Texture; +use crate::{Material, Mesh, Vertex}; +use anyhow::Result; +use bytemuck::{Pod, Zeroable}; +use std::path::Path; +use tobj::LoadOptions; +use wgpu::util::DeviceExt; + +pub struct Model { + pub meshes: Vec, + pub materials: Vec, +} + +impl Model { + pub fn load>( + device: &wgpu::Device, + queue: &wgpu::Queue, + layout: &wgpu::BindGroupLayout, + path: P, + ) -> Result { + let (obj_models, obj_materials) = tobj::load_obj( + path.as_ref(), + &LoadOptions { + triangulate: true, + single_index: true, + ..Default::default() + }, + )?; + + let obj_materials = obj_materials?; + + // We're assuming that the texture files are stored with the obj file + let containing_folder = path.as_ref().parent().expect("Directory has no parent"); + + let mut materials = Vec::new(); + for mat in obj_materials { + let diffuse_path = mat.diffuse_texture; + let diffuse_texture = + Texture::load(device, queue, containing_folder.join(diffuse_path))?; + + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&diffuse_texture.view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler), + }, + ], + label: None, + }); + + materials.push(Material { + name: mat.name, + diffuse_texture, + bind_group, + }); + } + + let mut meshes = Vec::new(); + for m in obj_models { + let mut vertices = Vec::new(); + for i in 0..m.mesh.positions.len() / 3 { + vertices.push(ModelVertex { + position: [ + m.mesh.positions[i * 3], + m.mesh.positions[i * 3 + 1], + m.mesh.positions[i * 3 + 2], + ], + tex_coords: [m.mesh.texcoords[i * 2], m.mesh.texcoords[i * 2 + 1]], + normal: [ + m.mesh.normals[i * 3], + m.mesh.normals[i * 3 + 1], + m.mesh.normals[i * 3 + 2], + ], + }); + } + + let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some(&format!("{:?} Vertex Buffer", path.as_ref())), + contents: bytemuck::cast_slice(&vertices), + usage: wgpu::BufferUsages::VERTEX, + }); + let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some(&format!("{:?} Index Buffer", path.as_ref())), + contents: bytemuck::cast_slice(&m.mesh.indices), + usage: wgpu::BufferUsages::INDEX, + }); + + meshes.push(Mesh { + name: m.name, + vertex_buffer, + index_buffer, + num_elements: m.mesh.indices.len() as u32, + material: m.mesh.material_id.unwrap_or(0), + }); + } + + Ok(Self { meshes, materials }) + } +} + +#[repr(C)] +#[derive(Copy, Clone, Debug, Pod, Zeroable)] +pub struct ModelVertex { + position: [f32; 3], + tex_coords: [f32; 2], + normal: [f32; 3], +} + +impl Vertex for ModelVertex { + fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { + use std::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, + }, + wgpu::VertexAttribute { + offset: mem::size_of::<[f32; 5]>() as wgpu::BufferAddress, + shader_location: 2, + format: wgpu::VertexFormat::Float32x3, + }, + ], + } + } +} diff --git a/Source/Texture.rs b/Source/Texture.rs index ef40ab9..66a29a9 100644 --- a/Source/Texture.rs +++ b/Source/Texture.rs @@ -1,5 +1,6 @@ use anyhow::*; use image::GenericImageView; +use std::path::Path; pub struct Texture { pub texture: wgpu::Texture, @@ -9,6 +10,18 @@ pub struct Texture { impl Texture { pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float; + pub fn load>( + device: &wgpu::Device, + queue: &wgpu::Queue, + path: P, + ) -> Result { + // Needed to appease the borrow checker + let path_copy = path.as_ref().to_path_buf(); + let label = path_copy.to_str(); + let img = image::open(path)?; + + Self::from_image(device, queue, &img, label) + } pub fn from_bytes( device: &wgpu::Device, @@ -26,7 +39,7 @@ impl Texture { img: &image::DynamicImage, label: Option<&str>, ) -> Result { - let rgba = img.as_rgba8().unwrap(); + let rgba = img.to_rgba8(); let dimensions = img.dimensions(); let size = wgpu::Extent3d { @@ -52,7 +65,7 @@ impl Texture { mip_level: 0, origin: wgpu::Origin3d::ZERO, }, - rgba, + &rgba, wgpu::ImageDataLayout { offset: 0, bytes_per_row: std::num::NonZeroU32::new(4 * dimensions.0), diff --git a/Source/Vertex.rs b/Source/Vertex.rs new file mode 100644 index 0000000..4ec298f --- /dev/null +++ b/Source/Vertex.rs @@ -0,0 +1,3 @@ +pub trait Vertex { + fn desc<'a>() -> wgpu::VertexBufferLayout<'a>; +} diff --git a/Source/lib.rs b/Source/lib.rs new file mode 100644 index 0000000..8e2fd1a --- /dev/null +++ b/Source/lib.rs @@ -0,0 +1,445 @@ +#![allow(non_snake_case)] + +#[path = "Camera.rs"] +mod _Camera; +pub use self::_Camera::*; + +#[path = "DrawModel.rs"] +mod _DrawModel; +pub use self::_DrawModel::*; + +#[path = "Instance.rs"] +mod _Instance; +pub use self::_Instance::*; + +#[path = "Material.rs"] +mod _Material; +pub use self::_Material::*; + +#[path = "Model.rs"] +mod _Model; +pub use self::_Model::*; + +#[path = "Mesh.rs"] +mod _Mesh; +pub use self::_Mesh::*; + +#[path = "Texture.rs"] +mod _Texture; +pub use self::_Texture::*; + +#[path = "Vertex.rs"] +mod _Vertex; +pub use self::_Vertex::*; + +use bytemuck::{Pod, Zeroable}; +use cgmath::prelude::*; +use image::GenericImageView; +use std::mem; +use std::path::PathBuf; +use wgpu::util::DeviceExt; +use winit::dpi::LogicalSize; +use winit::event::*; +use winit::event_loop::{ControlFlow, EventLoop}; +use winit::window::Window; +use winit::window::WindowBuilder; + +pub struct State { + surface: wgpu::Surface, + device: wgpu::Device, + queue: wgpu::Queue, + config: wgpu::SurfaceConfiguration, + size: winit::dpi::PhysicalSize, + render_pipeline: wgpu::RenderPipeline, + obj_model: Model, + camera: Camera, + camera_controller: CameraController, + camera_uniform: CameraUniform, + camera_buffer: wgpu::Buffer, + camera_bind_group: wgpu::BindGroup, + instances: Vec, + instance_buffer: wgpu::Buffer, + depth_texture: Texture, +} + +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::Backends::all()); + + let surface = unsafe { instance.create_surface(window) }; + + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::default(), + compatible_surface: Some(&surface), + force_fallback_adapter: false, + }) + .await + .unwrap(); + + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + features: wgpu::Features::empty(), + limits: wgpu::Limits::default(), + }, + None, + ) + .await + .unwrap(); + + let config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: surface.get_preferred_format(&adapter).unwrap(), + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::Fifo, + }; + + surface.configure(&device, &config); + + let texture_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::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::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler { + comparison: false, + filtering: true, + }, + count: None, + }, + ], + label: Some("texture_bind_group_layout"), + }); + + // Camera + + let camera = Camera { + eye: (0.0, 5.0, -10.0).into(), + target: (0.0, 0.0, 0.0).into(), + up: cgmath::Vector3::unit_y(), + aspect: config.width as f32 / config.height as f32, + fovy: 45.0, + znear: 0.1, + zfar: 100.0, + }; + let camera_controller = CameraController::new(0.2); + + 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, + }); + + const SPACE_BETWEEN: f32 = 3.0; + let instances = (0..NUM_INSTANCES_PER_ROW) + .flat_map(|z| { + (0..NUM_INSTANCES_PER_ROW).map(move |x| { + let x = SPACE_BETWEEN * (x as f32 - NUM_INSTANCES_PER_ROW as f32 / 2.0); + let z = SPACE_BETWEEN * (z as f32 - NUM_INSTANCES_PER_ROW as f32 / 2.0); + + let position = cgmath::Vector3 { x, y: 0.0, z }; + + let rotation = if position.is_zero() { + cgmath::Quaternion::from_axis_angle( + cgmath::Vector3::unit_z(), + cgmath::Deg(0.0), + ) + } else { + cgmath::Quaternion::from_axis_angle(position.normalize(), cgmath::Deg(45.0)) + }; + + 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 { + 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, + }], + label: Some("camera_bind_group_layout"), + }); + + let camera_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &camera_bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: camera_buffer.as_entire_binding(), + }], + label: Some("camera_bind_group"), + }); + + // Model + + let obj_model = Model::load( + &device, + &queue, + &texture_bind_group_layout, + PathBuf::from("./Resources/Cube.obj"), + ) + .unwrap(); + + // Shader + + let shader = device.create_shader_module(&wgpu::ShaderModuleDescriptor { + label: Some("shader.wgsl"), + source: wgpu::ShaderSource::Wgsl(include_str!("../Shaders/Texture.wgsl").into()), + }); + + // Texture + + let depth_texture = Texture::create_depth_texture(&device, &config, "depth_texture"); + + // Pipeline + + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Render Pipeline Layout"), + bind_group_layouts: &[&texture_bind_group_layout, &camera_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: &[ModelVertex::desc(), InstanceRaw::desc()], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "main", + targets: &[wgpu::ColorTargetState { + format: config.format, + 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), + // 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: Some(wgpu::DepthStencilState { + format: Texture::DEPTH_FORMAT, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::Less, + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), + }), + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + }); + + Self { + surface, + device, + queue, + config, + size, + render_pipeline, + obj_model, + camera, + camera_controller, + camera_buffer, + camera_bind_group, + camera_uniform, + instances, + instance_buffer, + depth_texture, + } + } + + pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { + if new_size.width > 0 && new_size.height > 0 { + self.size = new_size; + self.config.width = new_size.width; + self.config.height = new_size.height; + self.surface.configure(&self.device, &self.config); + } + self.depth_texture = + Texture::create_depth_texture(&self.device, &self.config, "depth_texture"); + } + + fn input(&mut self, event: &WindowEvent) -> bool { + self.camera_controller.process_events(event) + } + + fn update(&mut self) { + self.camera_controller.update_camera(&mut self.camera); + self.camera_uniform.update_view_proj(&self.camera); + self.queue.write_buffer( + &self.camera_buffer, + 0, + bytemuck::cast_slice(&[self.camera_uniform]), + ); + } + + fn render(&mut self) -> Result<(), wgpu::SurfaceError> { + let output = self.surface.get_current_texture()?; + let view = output + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + 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: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 0.1, + g: 0.2, + b: 0.3, + a: 1.0, + }), + store: true, + }, + }], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &self.depth_texture.view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + store: true, + }), + stencil_ops: None, + }), + }); + + render_pass.set_vertex_buffer(1, self.instance_buffer.slice(..)); + render_pass.set_pipeline(&self.render_pipeline); + render_pass.draw_model_instanced( + &self.obj_model, + 0..self.instances.len() as u32, + &self.camera_bind_group, + ); + } + + self.queue.submit(std::iter::once(encoder.finish())); + output.present(); + + Ok(()) + } +} + +#[doc(hidden)] +pub fn Main() { + env_logger::init(); + + let event_loop = EventLoop::new(); + + let window = WindowBuilder::new() + .with_title("Renderer") + .with_inner_size(LogicalSize::new(1280, 720)) + .build(&event_loop) + .unwrap(); + + // State::new uses async code, so we're going to wait for it to finish + let mut state = pollster::block_on(State::new(&window)); + + 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 + | WindowEvent::KeyboardInput { + 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, .. } => { + state.resize(**new_inner_size); + } + _ => {} + } + } + } + Event::RedrawRequested(_) => { + state.update(); + match state.render() { + Ok(_) => {} + // Reconfigure the surface if lost + Err(wgpu::SurfaceError::Lost) => state.resize(state.size), + // The system is out of memory, we should probably quit + Err(wgpu::SurfaceError::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(); + } + _ => {} + } + }); +}