diff --git a/src/camera.rs b/src/camera.rs new file mode 100644 index 0000000..696d92a --- /dev/null +++ b/src/camera.rs @@ -0,0 +1,51 @@ +use crate::EPSILON; +use nalgebra as nm; +use nalgebra::Matrix4; +use nalgebra::Point3; +use nalgebra::Vector3; + +#[rustfmt::skip] +pub const OPENGL_TO_WGPU_MATRIX: Matrix4 = 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.5, + 0.0, 0.0, 0.0, 1.0, +); + +struct Camera { + eye: Point3, + target: Point3, + up: Vector3, + fovy: f32, + aspect: f32, + znear: f32, + zfar: f32, +} + +impl Camera { + fn new( + eye: Point3, + target: Point3, + up: Vector3, + fovy: f32, + aspect: f32, + ) -> Self { + let znear = EPSILON; + let zfar = 1.0 / EPSILON; + Camera { + eye, + target, + up, + fovy, + aspect, + znear, + zfar, + } + } + + fn build_mvp_matrix(&self, model: Matrix4) -> Matrix4 { + let view = Matrix4::look_at_lh(&self.eye, &self.target, &self.up); + let proj = Matrix4::new_perspective(self.aspect, self.fovy, self.znear, self.zfar); + return OPENGL_TO_WGPU_MATRIX * proj * view * model; + } +} diff --git a/src/happy-tree.png b/src/happy-tree.png new file mode 100644 index 0000000..fc86db3 Binary files /dev/null and b/src/happy-tree.png differ diff --git a/src/primitive.rs b/src/primitive.rs new file mode 100644 index 0000000..eb08d87 --- /dev/null +++ b/src/primitive.rs @@ -0,0 +1,345 @@ +use crate::ray::Ray; +use crate::EPSILON; +use nalgebra::{distance, Matrix4, Point3, Vector3}; +use roots::{find_roots_quadratic, Roots}; +// MATERIAL ----------------------------------------------------------------- +struct Material { + kd: Vector3, + ks: Vector3, + shininess: f32, +} +impl Material { + fn new(kd: Vector3, ks: Vector3, shininess: f32) -> Self { + Material { kd, ks, shininess } + } + fn magenta() -> Self { + let kd = Vector3::new(1.0, 0.0, 1.0); + let ks = Vector3::new(0.0, 1.0, 1.0); + let shininess = 0.5; + Material { kd, ks, shininess } + } +} +// INTERSECTION ----------------------------------------------------------------- +struct Intersection { + point: Point3, + normal: Vector3, + // Information about an intersection +} +impl Intersection { + fn new(point: Point3, normal: Vector3) -> Self { + Intersection { point, normal } + } +} +// BOUNDING BOX ----------------------------------------------------------------- +struct BoundingBox { + bln: Point3, + trf: Point3, +} +impl BoundingBox { + fn new(bln: Point3, trf: Point3) -> Self { + BoundingBox { bln, trf } + } + fn intersect_bounding_box( + &self, + position: &Vector3, + direction: &Vector3, + ) -> Option<&Self> { + unimplemented!() + } + fn distance_to_point(&self, point: &Vector3) -> f32 { + unimplemented!() + } + fn update(&self, bln: Point3, trf: Point3) { + unimplemented!() + } +} +// PRIMITIVE TRAIT ----------------------------------------------------------------- +trait Primitive { + fn intersect_ray(&self, ray: &Ray) -> Option; + fn get_material(self) -> Material; +} + +// SPHERE ----------------------------------------------------------------- +struct Sphere { + position: Point3, + radius: f32, + material: Material, + bounding_box: BoundingBox, +} + +impl Sphere { + fn new(position: Point3, radius: f32, material: Material) -> Self { + let radius_vec = Vector3::new(radius, radius, radius); + let bln = position - radius_vec; + let trf = position + radius_vec; + let bounding_box = BoundingBox::new(bln, trf); + Sphere { + position, + radius, + material, + bounding_box, + } + } + + fn unit() -> Self { + let position = Point3::new(0.0, 0.0, 0.0); + let radius = 1.0; + let radius_vec = Vector3::new(radius, radius, radius); + let material = Material::magenta(); + let bln = position - radius_vec; + let trf = position + radius_vec; + let bounding_box = BoundingBox { bln, trf }; + Sphere { + position, + radius, + material, + bounding_box, + } + } +} + +impl Primitive for Sphere { + fn intersect_ray(&self, ray: &Ray) -> Option { + let pos = &ray.a; + let dir = &ray.b; + + let l = pos - self.position; + + let a = dir.dot(dir); + let b = 2.0 * l.dot(dir); + let c = l.dot(&l) - self.radius * self.radius; + + let t = match find_roots_quadratic(a, b, c) { + Roots::No(_) => return None, + Roots::One([x1]) => x1, + Roots::Two([x1, x2]) => { + if x1 <= 0.0 && x2 <= 0.0 { + return None; + } else { + if x1.abs() < x2.abs() { + x1 + } else { + x2 + } + } + } + _ => return None, + }; + + let intersect = ray.at_t(t); + let normal = (intersect - self.position).normalize(); + Some(Intersection { + point: intersect, + normal, + }) + } + + fn get_material(self) -> Material { + self.material + } +} + +// CIRCLE ----------------------------------------------------------------- +struct Circle { + position: Point3, + radius: f32, + normal: Vector3, + material: Material, + bounding_box: BoundingBox, +} + +impl Circle { + fn new(position: Point3, radius: f32, normal: Vector3, material: Material) -> Self { + let normal = normal.normalize(); + let radius_vec = Vector3::new(radius, radius, radius); + let bln = position - radius_vec; + let trf = position + radius_vec; + let bounding_box = BoundingBox::new(bln, trf); + Circle { + position, + radius, + normal, + material, + bounding_box, + } + } + + fn unit() -> Self { + let position = Point3::new(0.0, 0.0, 0.0); + let normal = Vector3::new(0.0, 1.0, 0.0); + let radius = 1.0; + let material = Material::magenta(); + + let bln = Point3::new(-radius, 0.0, -EPSILON); + let trf = Point3::new(radius, 0.0, EPSILON); + let bounding_box = BoundingBox { bln, trf }; + + Circle { + position, + normal, + radius, + material, + bounding_box, + } + } +} + +impl Primitive for Circle { + fn intersect_ray(&self, ray: &Ray) -> Option { + let constant = self.position.coords.dot(&self.normal); + let denominator = ray.b.dot(&self.normal); + let t = (constant - ray.a.coords.dot(&self.normal)) / denominator; + let intersect = ray.at_t(t); + let distance = distance(&intersect, &self.position); + match distance > self.radius { + true => return None, + false => { + return Some(Intersection { + point: intersect, + normal: self.normal, + }) + } + } + } + + fn get_material(self) -> Material { + self.material + } +} + +// CYLINDER ----------------------------------------------------------------- +struct Cylinder {} + +impl Cylinder {} + +impl Primitive for Cylinder { + fn intersect_ray(&self, ray: &Ray) -> Option { + todo!() + } + + fn get_material(self) -> Material { + todo!() + } +} + +// CONE ----------------------------------------------------------------- +struct Cone { + radius: f32, + height: f32, + base: f32, + circle: Circle, +} + +impl Cone { + fn get_normal(&self, intersect: Point3) -> Vector3 { + let r = self.radius; + let h = self.height; + let (x, y, z) = (intersect.x, intersect.y, intersect.z); + let normal = Vector3::new(2.0 * x, 2.0 * r * r * (h - y), 2.0 * z).normalize(); + return normal; + } +} + +impl Primitive for Cone { + fn intersect_ray(&self, ray: &Ray) -> Option { + let point = &ray.a; + let dir = &ray.b; + let (r, h) = (self.radius, self.height); + let (a1, a2, a3) = (point.x, point.y, point.z); + let (b1, b2, b3) = (dir.x, dir.y, dir.z); + let r2 = r * r; + let a = b1 * b1 + b3 * b3 - r2 * (b2 * b2); + let b = 2.0 * (a1 * b1 + a3 * b3 + r2 * (h * b2 - a2 * b2)); + let c = a1 * a1 + a3 * a3 + r2 * (2.0 * h * a2 - h * h - a2 * a2); + + let t = match find_roots_quadratic(a, b, c) { + Roots::No(_) => None, + Roots::One([x1]) => Some(x1), + Roots::Two([x1, x2]) => { + if x1 <= 0.0 && x2 <= 0.0 { + None + } else { + if x1.abs() < x2.abs() { + Some(x1) + } else { + Some(x2) + } + } + } + _ => None, + }; + + let cone_intersect = match t { + None => None, + Some(t) => { + let intersect = ray.at_t(t); + match intersect.y >= self.base && intersect.y <= self.height { + true => Some(Intersection { + point: intersect, + normal: self.get_normal(intersect), + }), + false => None, + } + } + }; + + let circle_intersect = self.circle.intersect_ray(ray); + + match (cone_intersect, circle_intersect) { + (None, None) => return None, + (Some(cone_intersect), None) => return Some(cone_intersect), + (None, Some(circle_intersect)) => return Some(circle_intersect), + (Some(cone_intersect), Some(circle_intersect)) => { + let circle_distance = distance(&ray.a, &circle_intersect.point); + let cone_distance = distance(&ray.a, &cone_intersect.point); + match cone_distance < circle_distance { + true => return Some(cone_intersect), + false => return Some(circle_intersect), + } + } + }; + } + + fn get_material(self) -> Material { + todo!() + } +} + +// BOX ----------------------------------------------------------------- +struct Box {} +impl Box {} +impl Primitive for Box { + fn intersect_ray(&self, ray: &Ray) -> Option { + todo!() + } + + fn get_material(self) -> Material { + todo!() + } +} + +// TRIANGLE ----------------------------------------------------------------- +struct Triangle {} +impl Triangle {} +impl Primitive for Triangle { + fn intersect_ray(&self, ray: &Ray) -> Option { + todo!() + } + + fn get_material(self) -> Material { + todo!() + } +} + +// MESH ----------------------------------------------------------------- +struct Mesh {} +impl Mesh {} +impl Primitive for Mesh { + fn intersect_ray(&self, ray: &Ray) -> Option { + todo!() + } + + fn get_material(self) -> Material { + todo!() + } +} diff --git a/src/shaders/shader.wgsl b/src/shaders/shader.wgsl new file mode 100644 index 0000000..890e643 --- /dev/null +++ b/src/shaders/shader.wgsl @@ -0,0 +1,31 @@ +struct VertexInput { + @location(0) position: vec3, + @location(1) tex_coords: vec2, +} + +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) tex_coords: vec2, +} + +@vertex +fn vs_main( + model: VertexInput, +) -> VertexOutput { + var out: VertexOutput; + out.tex_coords = model.tex_coords; + out.clip_position = vec4(model.position, 1.0); + return out; +} + +// Fragment shader + +@group(0) @binding(0) +var t_diffuse: texture_2d; +@group(0)@binding(1) +var s_diffuse: sampler; + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + return textureSample(t_diffuse, s_diffuse, in.tex_coords); +} diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..3823c80 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,387 @@ +use crate::texture::Texture; +use crate::vertex::Vertex; +use std::{fs, iter}; +use wgpu::util::DeviceExt; +use winit::{ + event::*, + event_loop::{ControlFlow, EventLoop}, + window::{Window, WindowBuilder}, +}; + +struct State { + surface: wgpu::Surface, + device: wgpu::Device, + queue: wgpu::Queue, + config: wgpu::SurfaceConfiguration, + size: winit::dpi::PhysicalSize, + render_pipeline: wgpu::RenderPipeline, + window: Window, + //Vertex buffer + num_vertices: u32, + vertex_buffer: wgpu::Buffer, + //Indicies buffer + num_indices: u32, + index_buffer: wgpu::Buffer, + diffuse_bind_group: wgpu::BindGroup, + diffuse_texture: Texture, // NEW +} + +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::InstanceDescriptor { + backends: wgpu::Backends::all(), + ..Default::default() + }); + + // # Safety + // + // The surface needs to live as long as the window that created it. + // State owns the window so this should be safe. + let surface = unsafe { instance.create_surface(&window) }.unwrap(); + + 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(), + // WebGL doesn't support all of wgpu's features, so if + // we're building for the web we'll have to disable some. + limits: if cfg!(target_arch = "wasm32") { + wgpu::Limits::downlevel_webgl2_defaults() + } else { + wgpu::Limits::default() + }, + }, + None, // Trace path + ) + .await + .unwrap(); + + let surface_caps = surface.get_capabilities(&adapter); + // Shader code in this tutorial assumes an Srgb surface texture. Using a different + // one will result all the colors comming out darker. If you want to support non + // Srgb surfaces, you'll need to account for that when drawing to the frame. + let surface_format = surface_caps + .formats + .iter() + .copied() + .find(|f| f.is_srgb()) + .unwrap_or(surface_caps.formats[0]); + let config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: surface_format, + width: size.width, + height: size.height, + present_mode: surface_caps.present_modes[0], + alpha_mode: surface_caps.alpha_modes[0], + view_formats: vec![], + }; + + surface.configure(&device, &config); + + let diffuse_bytes = include_bytes!("happy-tree.png"); // CHANGED! + let diffuse_texture = + Texture::from_bytes(&device, &queue, diffuse_bytes, "happy-tree.png").unwrap(); // CHANGED! + + 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, + // This should match the filterable field of the + // corresponding Texture entry above. + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + 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), // CHANGED! + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler), // CHANGED! + }, + ], + label: Some("diffuse_bind_group"), + }); + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Shader"), + source: wgpu::ShaderSource::Wgsl(include_str!("shaders/shader.wgsl").into()), + }); + + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Render Pipeline Layout"), + bind_group_layouts: &[&texture_bind_group_layout], // NEW! + 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: "vs_main", + buffers: &[Vertex::desc()], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(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::POLYGON_MODE_LINE + // or Features::POLYGON_MODE_POINT + polygon_mode: wgpu::PolygonMode::Fill, + // Requires Features::DEPTH_CLIP_CONTROL + unclipped_depth: false, + // Requires Features::CONSERVATIVE_RASTERIZATION + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + + // If the pipeline will be used with a multiview render pass, this + // indicates how many array layers the attachments will have. + multiview: None, + }); + + const VERTICES: &[Vertex] = &[ + Vertex { + position: [-0.0868241, 0.49240386, 0.0], + tex_coords: [0.4131759, 0.99240386], + }, // A + Vertex { + position: [-0.49513406, 0.06958647, 0.0], + tex_coords: [0.0048659444, 0.56958647], + }, // B + Vertex { + position: [-0.21918549, -0.44939706, 0.0], + tex_coords: [0.28081453, 0.05060294], + }, // C + Vertex { + position: [0.35966998, -0.3473291, 0.0], + tex_coords: [0.85967, 0.1526709], + }, // D + Vertex { + position: [0.44147372, 0.2347359, 0.0], + tex_coords: [0.9414737, 0.7347359], + }, // E + ]; + const INDICES: &[u16] = &[0, 1, 4, 1, 2, 4, 2, 3, 4]; + + let num_indices = INDICES.len() as u32; + let num_vertices = VERTICES.len() as u32; + + let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Index Buffer"), + contents: bytemuck::cast_slice(INDICES), + usage: wgpu::BufferUsages::INDEX, + }); + + let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Vertex Buffer"), + contents: bytemuck::cast_slice(VERTICES), + usage: wgpu::BufferUsages::VERTEX, + }); + + Self { + surface, + device, + queue, + config, + render_pipeline, + size, + window, + num_vertices, + vertex_buffer, + num_indices, + index_buffer, + diffuse_bind_group, + diffuse_texture, + } + } + + fn window(&self) -> &Window { + &self.window + } + + 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 { + match event { + WindowEvent::KeyboardInput { + input: + KeyboardInput { + state, + virtual_keycode: Some(VirtualKeyCode::Space), + .. + }, + .. + } => true, + _ => 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: &[Some(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: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + occlusion_query_set: None, + timestamp_writes: None, + }); + + render_pass.set_pipeline(&self.render_pipeline); + render_pass.set_bind_group(0, &self.diffuse_bind_group, &[]); // NEW! + 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())); + output.present(); + + Ok(()) + } +} + +pub async fn run() { + env_logger::init(); + let event_loop = EventLoop::new(); + let window = WindowBuilder::new().build(&event_loop).unwrap(); + // State::new uses async code, so we're going to wait for it to finish + let mut state = State::new(window).await; + + event_loop.run(move |event, _, control_flow| { + match event { + Event::WindowEvent { + ref event, + window_id, + } if window_id == state.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, .. } => { + // new_inner_size is &mut so w have to dereference it twice + state.resize(**new_inner_size); + } + _ => {} + } + } + } + Event::RedrawRequested(window_id) if window_id == state.window().id() => { + state.update(); + match state.render() { + Ok(_) => {} + // Reconfigure the surface if it's lost or outdated + Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => { + state.resize(state.size) + } + // The system is out of memory, we should probably quit + Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit, + // We're ignoring timeouts + Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"), + } + } + Event::MainEventsCleared => { + // RedrawRequested will only trigger once, unless we manually + // request it. + state.window().request_redraw(); + } + _ => {} + } + }); +} diff --git a/src/texture.rs b/src/texture.rs new file mode 100644 index 0000000..f3d869c --- /dev/null +++ b/src/texture.rs @@ -0,0 +1,79 @@ +use anyhow::*; +use image::GenericImageView; + +pub struct Texture { + pub texture: wgpu::Texture, + pub view: wgpu::TextureView, + pub sampler: wgpu::Sampler, +} + +impl Texture { + pub fn from_bytes( + 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)) + } + + pub fn from_image( + device: &wgpu::Device, + queue: &wgpu::Queue, + img: &image::DynamicImage, + label: Option<&str>, + ) -> Result { + let rgba = img.to_rgba8(); + let dimensions = img.dimensions(); + + let size = wgpu::Extent3d { + width: dimensions.0, + height: dimensions.1, + depth_or_array_layers: 1, + }; + let texture = device.create_texture(&wgpu::TextureDescriptor { + label, + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8UnormSrgb, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + view_formats: &[], + }); + + queue.write_texture( + wgpu::ImageCopyTexture { + aspect: wgpu::TextureAspect::All, + texture: &texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + }, + &rgba, + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(4 * dimensions.0), + rows_per_image: Some(dimensions.1), + }, + size, + ); + + let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }); + + Ok(Self { + texture, + view, + sampler, + }) + } +} diff --git a/src/vertex.rs b/src/vertex.rs new file mode 100644 index 0000000..8037803 --- /dev/null +++ b/src/vertex.rs @@ -0,0 +1,22 @@ +#[repr(C)] +#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] +pub struct Vertex { + pub position: [f32; 3], + pub tex_coords: [f32; 2], // NEW! +} + +impl Vertex { + //Attributes of the vertexes + const ATTRIBS: [wgpu::VertexAttribute; 2] = + wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x2]; + + pub fn desc() -> wgpu::VertexBufferLayout<'static> { + use std::mem; + + wgpu::VertexBufferLayout { + array_stride: mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &Self::ATTRIBS, + } + } +}