From 57d9b036cb62cd528461cbb35631298e82424bc4 Mon Sep 17 00:00:00 2001 From: Werner Date: Tue, 2 Nov 2021 13:55:52 -0300 Subject: [PATCH] Basic triangle --- .cargo/config.toml | 2 + .gitignore | 2 + Cargo.toml | 23 ++++ Shaders/Main.wgsl | 28 +++++ Source/Main.rs | 296 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 351 insertions(+) create mode 100644 .cargo/config.toml create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 Shaders/Main.wgsl create mode 100644 Source/Main.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..0243837 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target-dir = "Binaries" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..33080b4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Binaries/ +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..171ca6a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "renderer" +version = "0.0.1" +description = "WGPU Renderer" +repository = "https://github.com/GuilhermeWerner/Renderer" +license = "MIT" +edition = "2021" +publish = false + +[[bin]] +name = "Renderer" +path = "Source/Main.rs" +doc = false + +[dependencies] +bytemuck = { version = "1.4", features = [ "derive" ] } +cgmath = "0.18" +env_logger = "0.9" +image = "0.23" +log = "0.4" +pollster = "0.2" +wgpu = "0.11" +winit = "0.25" diff --git a/Shaders/Main.wgsl b/Shaders/Main.wgsl new file mode 100644 index 0000000..162691c --- /dev/null +++ b/Shaders/Main.wgsl @@ -0,0 +1,28 @@ +// Vertex shader + +struct VertexInput { + [[location(0)]] position: vec3; + [[location(1)]] color: vec3; +}; + +struct VertexOutput { + [[builtin(position)]] clip_position: vec4; + [[location(0)]] color: vec3; +}; + +[[stage(vertex)]] +fn main( + model: VertexInput, +) -> VertexOutput { + var out: VertexOutput; + out.color = model.color; + out.clip_position = vec4(model.position, 1.0); + return out; +} + +// Fragment shader + +[[stage(fragment)]] +fn main(in: VertexOutput) -> [[location(0)]] vec4 { + return vec4(in.color, 1.0); +} diff --git a/Source/Main.rs b/Source/Main.rs new file mode 100644 index 0000000..dcdc143 --- /dev/null +++ b/Source/Main.rs @@ -0,0 +1,296 @@ +// Copyright (c) TribuFu. All Rights Reserved. + +#![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] }, +]; + +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, + num_vertices: 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/Main.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 num_vertices = VERTICES.len() as u32; + + Self { + surface, + device, + queue, + config, + size, + render_pipeline, + vertex_buffer, + num_vertices, + } + } + + 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.draw(0..self.num_vertices, 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(); + } + _ => {} + } + }); +}