diff --git a/Cargo.toml b/Cargo.toml index 2bdfa4b..644cf82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,22 +7,18 @@ license = "MIT" edition = "2021" publish = false -default-run = "Renderer" - [lib] -name = "Renderer" +name = "Graphics" crate-type = ["rlib"] path = "Source/lib.rs" -[[bin]] -name = "Renderer" -path = "Source/Main.rs" -doc = false +[[example]] +name = "Cubes" +path = "Examples/Cubes.rs" -[[bin]] -name = "Rainbow" -path = "Source/Rainbow.rs" -doc = false +[[example]] +name = "Triangle" +path = "Examples/Triangle.rs" [dependencies] anyhow = "1.0" diff --git a/Resources/Cube.mtl b/Content/SM_Cube.mtl similarity index 93% rename from Resources/Cube.mtl rename to Content/SM_Cube.mtl index 7f75f49..11b997a 100644 --- a/Resources/Cube.mtl +++ b/Content/SM_Cube.mtl @@ -11,4 +11,4 @@ Ni 1.450000 d 1.000000 illum 2 map_Bump T_Normal.png -map_Kd T_Stone.png +map_Kd T_White.png diff --git a/Resources/Cube.obj b/Content/SM_Cube.obj similarity index 98% rename from Resources/Cube.obj rename to Content/SM_Cube.obj index 0c1b243..6dd3c0b 100644 --- a/Resources/Cube.obj +++ b/Content/SM_Cube.obj @@ -1,6 +1,6 @@ # Blender v2.93.1 OBJ File: 'Cube.blend' # www.blender.org -mtllib Cube.mtl +mtllib SM_Cube.mtl o Cube v 1.000000 1.000000 -1.000000 v -1.000000 1.000000 -1.000000 diff --git a/Resources/T_Normal.png b/Content/T_Normal.png similarity index 100% rename from Resources/T_Normal.png rename to Content/T_Normal.png diff --git a/Resources/T_White.png b/Content/T_White.png similarity index 100% rename from Resources/T_White.png rename to Content/T_White.png diff --git a/Examples/Cubes.rs b/Examples/Cubes.rs new file mode 100644 index 0000000..6dbeb65 --- /dev/null +++ b/Examples/Cubes.rs @@ -0,0 +1,316 @@ +#![allow(non_snake_case)] +#![allow(unused_variables)] + +use anyhow::Result; +use cgmath::prelude::*; +use std::path::PathBuf; +use std::time::Duration; +use wgpu::util::DeviceExt; +use winit::event::*; +use Graphics::Camera::*; +use Graphics::Render::*; +use Graphics::{Display, Runtime, State}; + +struct Cubes { + 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 for Cubes { + fn Init(display: &Display) -> Result { + let texture_bind_group_layout = + display + .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: display.config.width as f32 / display.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.UpdateViewProjection(&camera); + + let camera_buffer = display + .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::ToRaw).collect::>(); + let instance_buffer = + display + .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 = + display + .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 = display + .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( + &display.device, + &display.queue, + &texture_bind_group_layout, + PathBuf::from("./Content/SM_Cube.obj"), + ) + .unwrap(); + + // Shader + + let shader = display + .device + .create_shader_module(&wgpu::ShaderModuleDescriptor { + label: Some("shader.wgsl"), + source: wgpu::ShaderSource::Wgsl(include_str!("../Shaders/Cubes.wgsl").into()), + }); + + // Texture + + let depth_texture = + Texture::CreateDepthTexture(&display.device, &display.config, "depth_texture"); + + // Pipeline + + let render_pipeline_layout = + display + .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 = + display + .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::GetDescriptor(), InstanceRaw::GetDescriptor()], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "main", + targets: &[wgpu::ColorTargetState { + format: display.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, + }, + }); + + Ok(Self { + render_pipeline, + obj_model, + camera, + camera_controller, + camera_buffer, + camera_bind_group, + camera_uniform, + instances, + instance_buffer, + depth_texture, + }) + } + + fn Input(&mut self, display: &Display, event: &WindowEvent) -> bool { + self.camera_controller.ProcessEvents(event) + } + + fn Update(&mut self, display: &Display, delta: Duration) { + self.camera_controller.UpdateCamera(&mut self.camera); + self.camera_uniform.UpdateViewProjection(&self.camera); + display.queue.write_buffer( + &self.camera_buffer, + 0, + bytemuck::cast_slice(&[self.camera_uniform]), + ); + } + + fn Resize(&mut self, display: &Display) { + self.depth_texture = + Texture::CreateDepthTexture(&display.device, &display.config, "depth_texture"); + } + + fn Draw(&mut self, display: &mut Display) -> Result<(), wgpu::SurfaceError> { + let output = display.surface.get_current_texture().unwrap(); + let view = output + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + let mut encoder = display + .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.DrawModelInstanced( + &self.obj_model, + 0..self.instances.len() as u32, + &self.camera_bind_group, + ); + } + + display.queue.submit(std::iter::once(encoder.finish())); + output.present(); + + Ok(()) + } +} + +fn main() -> Result<()> { + Runtime::Execute::() +} diff --git a/Examples/Triangle.rs b/Examples/Triangle.rs new file mode 100644 index 0000000..d41ef71 --- /dev/null +++ b/Examples/Triangle.rs @@ -0,0 +1,206 @@ +#![allow(non_snake_case)] +#![allow(unused_variables)] + +use anyhow::Result; +use bytemuck::{Pod, Zeroable}; +use std::time::Duration; +use wgpu::util::DeviceExt; +use winit::event::*; +use Graphics::Render::*; +use Graphics::{Display, Runtime, State}; + +#[repr(C)] +#[derive(Copy, Clone, Debug, Pod, Zeroable)] +struct TriangleVertex { + position: [f32; 3], + color: [f32; 3], +} + +impl Vertex for TriangleVertex { + fn GetDescriptor<'a>() -> wgpu::VertexBufferLayout<'a> { + wgpu::VertexBufferLayout { + array_stride: std::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: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, + shader_location: 1, + format: wgpu::VertexFormat::Float32x3, + }, + ], + } + } +} + +#[rustfmt::skip] +const VERTICES: &[TriangleVertex] = &[ + TriangleVertex { position: [0.0, 0.5, 0.0], color: [1.0, 0.0, 0.0] }, + TriangleVertex { position: [-0.5, -0.5, 0.0], color: [0.0, 1.0, 0.0] }, + TriangleVertex { position: [0.5, -0.5, 0.0], color: [0.0, 0.0, 1.0] }, +]; + +#[rustfmt::skip] +const INDICES: &[u16] = &[ + 0, 1, 2, +]; + +struct Triangle { + render_pipeline: wgpu::RenderPipeline, + vertex_buffer: wgpu::Buffer, + index_buffer: wgpu::Buffer, + num_indices: u32, +} + +impl State for Triangle { + fn Init(display: &Display) -> Result { + // Shader + + let shader = display + .device + .create_shader_module(&wgpu::ShaderModuleDescriptor { + label: Some("Shader"), + source: wgpu::ShaderSource::Wgsl(include_str!("../Shaders/Triangle.wgsl").into()), + }); + + // Pipeline + + let render_pipeline_layout = + display + .device + .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Render Pipeline Layout"), + bind_group_layouts: &[], + push_constant_ranges: &[], + }); + + let render_pipeline = + display + .device + .create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Render Pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "main", + buffers: &[TriangleVertex::GetDescriptor()], + }, + fragment: Some(wgpu::FragmentState { + // 3. + module: &shader, + entry_point: "main", + targets: &[wgpu::ColorTargetState { + // 4. + format: display.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: None, // 1. + multisample: wgpu::MultisampleState { + count: 1, // 2. + mask: !0, // 3. + alpha_to_coverage_enabled: false, // 4. + }, + }); + + let vertex_buffer = display + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Vertex Buffer"), + contents: bytemuck::cast_slice(VERTICES), + usage: wgpu::BufferUsages::VERTEX, + }); + + let index_buffer = display + .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; + + Ok(Self { + render_pipeline, + vertex_buffer, + index_buffer, + num_indices, + }) + } + + fn Input(&mut self, display: &Display, event: &WindowEvent) -> bool { + false + } + + fn Update(&mut self, display: &Display, delta: Duration) {} + + fn Resize(&mut self, display: &Display) {} + + fn Draw(&mut self, display: &mut Display) -> Result<(), wgpu::SurfaceError> { + let output = display.surface.get_current_texture().unwrap(); + let view = output + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + let mut encoder = display + .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: None, + }); + + render_pass.set_pipeline(&self.render_pipeline); + 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); + } + + display.queue.submit(std::iter::once(encoder.finish())); + output.present(); + + Ok(()) + } +} + +fn main() -> Result<()> { + Runtime::Execute::() +} diff --git a/Resources/T_Stone.png b/Resources/T_Stone.png deleted file mode 100644 index 52089f4..0000000 Binary files a/Resources/T_Stone.png and /dev/null differ diff --git a/Resources/Tree.png b/Resources/Tree.png deleted file mode 100644 index fc86db3..0000000 Binary files a/Resources/Tree.png and /dev/null differ diff --git a/Shaders/Texture.wgsl b/Shaders/Cubes.wgsl similarity index 97% rename from Shaders/Texture.wgsl rename to Shaders/Cubes.wgsl index 225f93b..e5a1dc9 100644 --- a/Shaders/Texture.wgsl +++ b/Shaders/Cubes.wgsl @@ -1,4 +1,4 @@ -// Vertex shader +// Vertex struct InstanceInput { [[location(5)]] model_matrix_0: vec4; @@ -44,7 +44,7 @@ fn main( return out; } -// Fragment shader +// Fragment [[group(0), binding(0)]] var t_diffuse: texture_2d; diff --git a/Shaders/Rainbow.wgsl b/Shaders/Triangle.wgsl similarity index 93% rename from Shaders/Rainbow.wgsl rename to Shaders/Triangle.wgsl index 162691c..0a712b4 100644 --- a/Shaders/Rainbow.wgsl +++ b/Shaders/Triangle.wgsl @@ -1,4 +1,4 @@ -// Vertex shader +// Vertex struct VertexInput { [[location(0)]] position: vec3; @@ -20,7 +20,7 @@ fn main( return out; } -// Fragment shader +// Fragment [[stage(fragment)]] fn main(in: VertexOutput) -> [[location(0)]] vec4 { diff --git a/Source/Camera/Camera.rs b/Source/Camera/Camera.rs new file mode 100644 index 0000000..5302ea9 --- /dev/null +++ b/Source/Camera/Camera.rs @@ -0,0 +1,20 @@ +use super::OPENGL_TO_WGPU_MATRIX; + +pub struct Camera { + pub eye: cgmath::Point3, + pub target: cgmath::Point3, + pub up: cgmath::Vector3, + pub aspect: f32, + pub fovy: f32, + pub znear: f32, + pub zfar: f32, +} + +impl Camera { + pub fn BuildViewProjectionMatrix(&self) -> cgmath::Matrix4 { + let view = cgmath::Matrix4::look_at_rh(self.eye, self.target, self.up); + let proj = cgmath::perspective(cgmath::Deg(self.fovy), self.aspect, self.znear, self.zfar); + + OPENGL_TO_WGPU_MATRIX * proj * view + } +} diff --git a/Source/Camera.rs b/Source/Camera/CameraController.rs similarity index 68% rename from Source/Camera.rs rename to Source/Camera/CameraController.rs index 6695b1e..76a8558 100644 --- a/Source/Camera.rs +++ b/Source/Camera/CameraController.rs @@ -1,55 +1,6 @@ -use bytemuck::{Pod, Zeroable}; +use super::Camera; use winit::event::*; -#[rustfmt::skip] -pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4 = cgmath::Matrix4::new( - 1.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 0.5, 0.0, - 0.0, 0.0, 0.5, 1.0, -); - -pub struct Camera { - pub eye: cgmath::Point3, - pub target: cgmath::Point3, - pub up: cgmath::Vector3, - pub aspect: f32, - pub fovy: f32, - pub znear: f32, - pub zfar: f32, -} - -impl Camera { - pub fn build_view_projection_matrix(&self) -> cgmath::Matrix4 { - let view = cgmath::Matrix4::look_at_rh(self.eye, self.target, self.up); - let proj = cgmath::perspective(cgmath::Deg(self.fovy), self.aspect, self.znear, self.zfar); - - OPENGL_TO_WGPU_MATRIX * proj * view - } -} - -// This is so we can store this in a buffer -#[repr(C)] -#[derive(Debug, Copy, Clone, Pod, Zeroable)] -pub struct CameraUniform { - // We can't use cgmath with bytemuck directly so we'll have - // to convert the Matrix4 into a 4x4 f32 array - pub view_proj: [[f32; 4]; 4], -} - -impl CameraUniform { - pub fn new() -> Self { - use cgmath::SquareMatrix; - Self { - view_proj: cgmath::Matrix4::identity().into(), - } - } - - pub fn update_view_proj(&mut self, camera: &Camera) { - self.view_proj = camera.build_view_projection_matrix().into(); - } -} - pub struct CameraController { pub speed: f32, pub is_up_pressed: bool, @@ -61,7 +12,7 @@ pub struct CameraController { } impl CameraController { - pub fn new(speed: f32) -> Self { + pub fn New(speed: f32) -> Self { Self { speed, is_up_pressed: false, @@ -73,7 +24,7 @@ impl CameraController { } } - pub fn process_events(&mut self, event: &WindowEvent) -> bool { + pub fn ProcessEvents(&mut self, event: &WindowEvent) -> bool { match event { WindowEvent::KeyboardInput { input: @@ -117,7 +68,7 @@ impl CameraController { } } - pub fn update_camera(&self, camera: &mut Camera) { + pub fn UpdateCamera(&self, camera: &mut Camera) { use cgmath::InnerSpace; let forward = camera.target - camera.eye; diff --git a/Source/Camera/CameraUniform.rs b/Source/Camera/CameraUniform.rs new file mode 100644 index 0000000..da5a76c --- /dev/null +++ b/Source/Camera/CameraUniform.rs @@ -0,0 +1,24 @@ +use super::Camera; +use bytemuck::{Pod, Zeroable}; +use cgmath::SquareMatrix; + +// This is so we can store this in a buffer +#[repr(C)] +#[derive(Debug, Copy, Clone, Pod, Zeroable)] +pub struct CameraUniform { + // We can't use cgmath with bytemuck directly so we'll have + // to convert the Matrix4 into a 4x4 f32 array + pub view_proj: [[f32; 4]; 4], +} + +impl CameraUniform { + pub fn New() -> Self { + Self { + view_proj: cgmath::Matrix4::identity().into(), + } + } + + pub fn UpdateViewProjection(&mut self, camera: &Camera) { + self.view_proj = camera.BuildViewProjectionMatrix().into(); + } +} diff --git a/Source/Camera/mod.rs b/Source/Camera/mod.rs new file mode 100644 index 0000000..b9bbdde --- /dev/null +++ b/Source/Camera/mod.rs @@ -0,0 +1,20 @@ +/// Matrix to scale and translate from OpenGL coordinate system to WGPU. +#[rustfmt::skip] +pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4 = cgmath::Matrix4::new( + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 0.5, 0.0, + 0.0, 0.0, 0.5, 1.0, +); + +#[path = "Camera.rs"] +mod _Camera; +pub use self::_Camera::*; + +#[path = "CameraController.rs"] +mod _CameraController; +pub use self::_CameraController::*; + +#[path = "CameraUniform.rs"] +mod _CameraUniform; +pub use self::_CameraUniform::*; diff --git a/Source/Display.rs b/Source/Display.rs new file mode 100644 index 0000000..cc64e75 --- /dev/null +++ b/Source/Display.rs @@ -0,0 +1,65 @@ +use anyhow::Result; +use winit::window::Window; + +/// A handler to system window and render adapter. +pub struct Display { + pub surface: wgpu::Surface, + pub window: Window, + pub config: wgpu::SurfaceConfiguration, + pub device: wgpu::Device, + pub queue: wgpu::Queue, +} + +impl Display { + pub async fn New(window: Window) -> Result { + let size = window.inner_size(); + + 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); + + Ok(Self { + surface, + window, + config, + device, + queue, + }) + } + + pub fn Resize(&mut self, width: u32, height: u32) { + self.config.width = width; + self.config.height = height; + self.surface.configure(&self.device, &self.config); + } +} diff --git a/Source/Main.rs b/Source/Main.rs deleted file mode 100644 index 6ceac9e..0000000 --- a/Source/Main.rs +++ /dev/null @@ -1,5 +0,0 @@ -#![allow(non_snake_case)] - -fn main() { - Renderer::Main(); -} diff --git a/Source/Rainbow.rs b/Source/Rainbow.rs deleted file mode 100644 index 796aaa1..0000000 --- a/Source/Rainbow.rs +++ /dev/null @@ -1,307 +0,0 @@ -#![allow(non_snake_case)] - -use bytemuck::{Pod, Zeroable}; -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], - color: [f32; 3], -} - -impl Vertex { - fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { - wgpu::VertexBufferLayout { - array_stride: std::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: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, - shader_location: 1, - format: wgpu::VertexFormat::Float32x3, - }, - ], - } - } -} - -#[rustfmt::skip] -const VERTICES: &[Vertex] = &[ - Vertex { position: [0.0, 0.5, 0.0], color: [1.0, 0.0, 0.0] }, - Vertex { position: [-0.5, -0.5, 0.0], color: [0.0, 1.0, 0.0] }, - Vertex { position: [0.5, -0.5, 0.0], color: [0.0, 0.0, 1.0] }, -]; - -#[rustfmt::skip] -const INDICES: &[u16] = &[ - 0, 1, 2, -]; - -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, -} - -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); - - let shader = device.create_shader_module(&wgpu::ShaderModuleDescriptor { - label: Some("Shader"), - source: wgpu::ShaderSource::Wgsl(include_str!("../Shaders/Rainbow.wgsl").into()), - }); - - let render_pipeline_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Render Pipeline Layout"), - bind_group_layouts: &[], - 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 { - // 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: None, // 1. - multisample: wgpu::MultisampleState { - count: 1, // 2. - mask: !0, // 3. - alpha_to_coverage_enabled: false, // 4. - }, - }); - - 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 { - surface, - device, - queue, - config, - size, - render_pipeline, - vertex_buffer, - index_buffer, - num_indices, - } - } - - 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); - } - } - - fn input(&mut self, _event: &WindowEvent) -> bool { - false - } - - fn update(&mut self) {} - - 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: None, - }); - - render_pass.set_pipeline(&self.render_pipeline); - 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); - } - - // 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(); - } - _ => {} - } - }); -} diff --git a/Source/DrawModel.rs b/Source/Render/DrawModel.rs similarity index 70% rename from Source/DrawModel.rs rename to Source/Render/DrawModel.rs index deb8bf1..fad8920 100644 --- a/Source/DrawModel.rs +++ b/Source/Render/DrawModel.rs @@ -1,22 +1,22 @@ -use crate::{Material, Mesh, Model}; +use super::{Material, Mesh, Model}; use std::ops::Range; pub trait DrawModel<'a> { - fn draw_mesh( + fn DrawMesh( &mut self, mesh: &'a Mesh, material: &'a Material, camera_bind_group: &'a wgpu::BindGroup, ); - fn draw_mesh_instanced( + fn DrawMeshInstanced( &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( + fn DrawModel(&mut self, model: &'a Model, camera_bind_group: &'a wgpu::BindGroup); + fn DrawModelInstanced( &mut self, model: &'a Model, instances: Range, @@ -25,16 +25,16 @@ pub trait DrawModel<'a> { } impl<'a> DrawModel<'a> for wgpu::RenderPass<'a> { - fn draw_mesh( + fn DrawMesh( &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); + self.DrawMeshInstanced(mesh, material, 0..1, camera_bind_group); } - fn draw_mesh_instanced( + fn DrawMeshInstanced( &mut self, mesh: &'a Mesh, material: &'a Material, @@ -48,11 +48,11 @@ impl<'a> DrawModel<'a> for wgpu::RenderPass<'a> { 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 DrawModel(&mut self, model: &'a Model, camera_bind_group: &'a wgpu::BindGroup) { + self.DrawModelInstanced(model, 0..1, camera_bind_group); } - fn draw_model_instanced( + fn DrawModelInstanced( &mut self, model: &'a Model, instances: Range, @@ -60,7 +60,7 @@ impl<'a> DrawModel<'a> for wgpu::RenderPass<'a> { ) { for mesh in &model.meshes { let material = &model.materials[mesh.material]; - self.draw_mesh_instanced(mesh, material, instances.clone(), camera_bind_group); + self.DrawMeshInstanced(mesh, material, instances.clone(), camera_bind_group); } } } diff --git a/Source/Instance.rs b/Source/Render/Instance.rs similarity index 96% rename from Source/Instance.rs rename to Source/Render/Instance.rs index 3b65423..398fcc6 100644 --- a/Source/Instance.rs +++ b/Source/Render/Instance.rs @@ -16,7 +16,7 @@ pub struct Instance { } impl Instance { - pub fn to_raw(&self) -> InstanceRaw { + pub fn ToRaw(&self) -> InstanceRaw { InstanceRaw { model: (cgmath::Matrix4::from_translation(self.position) * cgmath::Matrix4::from(self.rotation)) @@ -32,7 +32,7 @@ pub struct InstanceRaw { } impl InstanceRaw { - pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { + pub fn GetDescriptor<'a>() -> wgpu::VertexBufferLayout<'a> { wgpu::VertexBufferLayout { array_stride: mem::size_of::() as wgpu::BufferAddress, // We need to switch from using a step mode of Vertex to Instance diff --git a/Source/Material.rs b/Source/Render/Material.rs similarity index 85% rename from Source/Material.rs rename to Source/Render/Material.rs index 07df852..0e03bbc 100644 --- a/Source/Material.rs +++ b/Source/Render/Material.rs @@ -1,4 +1,4 @@ -use crate::Texture; +use super::Texture; pub struct Material { pub name: String, diff --git a/Source/Mesh.rs b/Source/Render/Mesh.rs similarity index 100% rename from Source/Mesh.rs rename to Source/Render/Mesh.rs diff --git a/Source/Model.rs b/Source/Render/Model.rs similarity index 95% rename from Source/Model.rs rename to Source/Render/Model.rs index 4620dbf..637e964 100644 --- a/Source/Model.rs +++ b/Source/Render/Model.rs @@ -1,5 +1,5 @@ -use crate::Texture; -use crate::{Material, Mesh, Vertex}; +use super::Texture; +use super::{Material, Mesh, Vertex}; use anyhow::Result; use bytemuck::{Pod, Zeroable}; use std::path::Path; @@ -12,7 +12,7 @@ pub struct Model { } impl Model { - pub fn load>( + pub fn Load>( device: &wgpu::Device, queue: &wgpu::Queue, layout: &wgpu::BindGroupLayout, @@ -36,7 +36,7 @@ impl Model { for mat in obj_materials { let diffuse_path = mat.diffuse_texture; let diffuse_texture = - Texture::load(device, queue, containing_folder.join(diffuse_path))?; + Texture::Load(device, queue, containing_folder.join(diffuse_path))?; let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { layout, @@ -112,7 +112,7 @@ pub struct ModelVertex { } impl Vertex for ModelVertex { - fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { + fn GetDescriptor<'a>() -> wgpu::VertexBufferLayout<'a> { use std::mem; wgpu::VertexBufferLayout { array_stride: mem::size_of::() as wgpu::BufferAddress, diff --git a/Source/Texture.rs b/Source/Render/Texture.rs similarity index 87% rename from Source/Texture.rs rename to Source/Render/Texture.rs index 66a29a9..9eb1c7c 100644 --- a/Source/Texture.rs +++ b/Source/Render/Texture.rs @@ -2,6 +2,21 @@ use anyhow::*; use image::GenericImageView; use std::path::Path; +/// Texture coordinates. +/// +/// ```no_run +/// (0.0) (1.0) +/// V1 ----------------- V2 +/// | / | +/// | Q1 / | +/// | / | +/// | / | +/// | / | +/// | / Q2 | +/// | / | +/// V3 ----------------- V4 +/// (0.1) (1.1) +/// ``` pub struct Texture { pub texture: wgpu::Texture, pub view: wgpu::TextureView, @@ -10,7 +25,8 @@ pub struct Texture { impl Texture { pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float; - pub fn load>( + + pub fn Load>( device: &wgpu::Device, queue: &wgpu::Queue, path: P, @@ -20,20 +36,20 @@ impl Texture { let label = path_copy.to_str(); let img = image::open(path)?; - Self::from_image(device, queue, &img, label) + Self::FromImage(device, queue, &img, label) } - pub fn from_bytes( + pub fn FromBytes( device: &wgpu::Device, queue: &wgpu::Queue, bytes: &[u8], label: &str, ) -> Result { let img = image::load_from_memory(bytes)?; - Self::from_image(device, queue, &img, Some(label)) + Self::FromImage(device, queue, &img, Some(label)) } - pub fn from_image( + pub fn FromImage( device: &wgpu::Device, queue: &wgpu::Queue, img: &image::DynamicImage, @@ -93,7 +109,7 @@ impl Texture { }) } - pub fn create_depth_texture( + pub fn CreateDepthTexture( device: &wgpu::Device, config: &wgpu::SurfaceConfiguration, label: &str, diff --git a/Source/Render/Vertex.rs b/Source/Render/Vertex.rs new file mode 100644 index 0000000..d563843 --- /dev/null +++ b/Source/Render/Vertex.rs @@ -0,0 +1,3 @@ +pub trait Vertex { + fn GetDescriptor<'a>() -> wgpu::VertexBufferLayout<'a>; +} diff --git a/Source/Render/mod.rs b/Source/Render/mod.rs new file mode 100644 index 0000000..9745912 --- /dev/null +++ b/Source/Render/mod.rs @@ -0,0 +1,27 @@ +#[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::*; diff --git a/Source/Runtime.rs b/Source/Runtime.rs new file mode 100644 index 0000000..24d060d --- /dev/null +++ b/Source/Runtime.rs @@ -0,0 +1,109 @@ +use crate::{Display, State}; +use anyhow::Result; +use std::time::Instant; +use winit::dpi::LogicalSize; +use winit::event::*; +use winit::event_loop::{ControlFlow, EventLoop}; +use winit::window::WindowBuilder; + +/// Runtime state executor and event loop. +pub struct Runtime; + +impl Runtime { + pub fn Execute() -> Result<()> { + env_logger::init(); + + let event_loop = EventLoop::new(); + + let window = WindowBuilder::new() + .with_title("Graphics") + .with_inner_size(LogicalSize::new(1280, 720)) + .build(&event_loop)?; + + let mut display = pollster::block_on(Display::New(window))?; + + let mut app = T::Init(&display)?; + + let mut last_update = Instant::now(); + + let mut is_resumed = true; + let mut is_focused = true; + let mut is_redraw_requested = true; + + event_loop.run(move |event, _, control_flow| { + *control_flow = if is_resumed && is_focused { + ControlFlow::Poll + } else { + ControlFlow::Wait + }; + + match event { + Event::Resumed => is_resumed = true, + Event::Suspended => is_resumed = false, + Event::RedrawRequested(window_id) => { + if window_id == display.window.id() { + let now = Instant::now(); + let delta = now - last_update; + last_update = now; + + app.Update(&display, delta); + + match app.Draw(&mut display) { + Ok(_) => {} + // Reconfigure the surface if lost + Err(wgpu::SurfaceError::Lost) => { + let size = display.window.inner_size(); + display.Resize(size.width, size.height); + app.Resize(&display); + } + // 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), + } + + is_redraw_requested = false; + } + } + Event::MainEventsCleared => { + if is_focused && is_resumed && !is_redraw_requested { + display.window.request_redraw(); + is_redraw_requested = true; + } else { + // Freeze time while the app is not in the foreground + last_update = Instant::now(); + } + } + Event::WindowEvent { event, window_id } if window_id == display.window.id() => { + if !app.Input(&display, &event) { + match event { + WindowEvent::CloseRequested + | WindowEvent::KeyboardInput { + input: + KeyboardInput { + state: ElementState::Pressed, + virtual_keycode: Some(VirtualKeyCode::Escape), + .. + }, + .. + } => *control_flow = ControlFlow::Exit, + WindowEvent::Focused(focused) => is_focused = focused, + WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { + display.Resize(new_inner_size.width, new_inner_size.height); + app.Resize(&display); + } + WindowEvent::Resized(physical_size) => { + display.Resize(physical_size.width, physical_size.height); + app.Resize(&display); + } + _ => {} + } + } + } + _ => {} + } + }) + } +} diff --git a/Source/State.rs b/Source/State.rs new file mode 100644 index 0000000..24164ff --- /dev/null +++ b/Source/State.rs @@ -0,0 +1,13 @@ +use crate::Display; +use anyhow::Result; +use std::time::Duration; +use winit::event::*; + +/// Represents a application with reactive state. +pub trait State: Sized + 'static { + fn Init(display: &Display) -> Result; + fn Input(&mut self, display: &Display, event: &WindowEvent) -> bool; + fn Update(&mut self, display: &Display, delta: Duration); + fn Resize(&mut self, display: &Display); + fn Draw(&mut self, display: &mut Display) -> Result<(), wgpu::SurfaceError>; +} diff --git a/Source/Vertex.rs b/Source/Vertex.rs deleted file mode 100644 index 4ec298f..0000000 --- a/Source/Vertex.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub trait Vertex { - fn desc<'a>() -> wgpu::VertexBufferLayout<'a>; -} diff --git a/Source/lib.rs b/Source/lib.rs index 8e2fd1a..0a98d61 100644 --- a/Source/lib.rs +++ b/Source/lib.rs @@ -1,445 +1,16 @@ #![allow(non_snake_case)] -#[path = "Camera.rs"] -mod _Camera; -pub use self::_Camera::*; +pub mod Camera; +pub mod Render; -#[path = "DrawModel.rs"] -mod _DrawModel; -pub use self::_DrawModel::*; +#[path = "Display.rs"] +mod _Display; +pub use self::_Display::*; -#[path = "Instance.rs"] -mod _Instance; -pub use self::_Instance::*; +#[path = "Runtime.rs"] +mod _Runtime; +pub use self::_Runtime::*; -#[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(); - } - _ => {} - } - }); -} +#[path = "State.rs"] +mod _State; +pub use self::_State::*;