diff --git a/wgpu/README.md b/wgpu/README.md index 19b41e0ab1..1d85c9b314 100644 --- a/wgpu/README.md +++ b/wgpu/README.md @@ -7,7 +7,7 @@ This is an idiomatic Rust wrapper over [wgpu-native](https://github.com/gfx-rs/w ## Gallery -![Cube](etc/example-cube.png) ![Shadow](etc/example-shadow.png) ![MipMap](etc/example-mipmap.png) +![Cube](etc/example-cube.png) ![Shadow](etc/example-shadow.png) ![MipMap](etc/example-mipmap.png) ![Skybox](etc/example-skybox.gif) ![vange-rs](etc/vange-rs.png) ![Brawl](etc/brawl-attack.gif) ![GLX map](etc/glx-map.png) ## Usage diff --git a/wgpu/etc/example-skybox.gif b/wgpu/etc/example-skybox.gif new file mode 100644 index 0000000000..47fc7833e6 Binary files /dev/null and b/wgpu/etc/example-skybox.gif differ diff --git a/wgpu/examples/framework.rs b/wgpu/examples/framework.rs index bb5cc4b97c..efdd3b52c4 100644 --- a/wgpu/examples/framework.rs +++ b/wgpu/examples/framework.rs @@ -35,16 +35,27 @@ pub fn load_glsl(code: &str, stage: ShaderStage) -> Vec { } pub trait Example: 'static + Sized { - fn init(sc_desc: &wgpu::SwapChainDescriptor, device: &wgpu::Device) -> (Self, Option); - fn resize(&mut self, sc_desc: &wgpu::SwapChainDescriptor, device: &wgpu::Device) -> Option; + fn init( + sc_desc: &wgpu::SwapChainDescriptor, + device: &wgpu::Device, + ) -> (Self, Option); + fn resize( + &mut self, + sc_desc: &wgpu::SwapChainDescriptor, + device: &wgpu::Device, + ) -> Option; fn update(&mut self, event: WindowEvent); - fn render(&mut self, frame: &wgpu::SwapChainOutput, device: &wgpu::Device) -> wgpu::CommandBuffer; + fn render( + &mut self, + frame: &wgpu::SwapChainOutput, + device: &wgpu::Device, + ) -> wgpu::CommandBuffer; } pub fn run(title: &str) { use winit::{ - event_loop::{ControlFlow, EventLoop}, event, + event_loop::{ControlFlow, EventLoop}, }; env_logger::init(); @@ -86,7 +97,8 @@ pub fn run(title: &str) { let adapter = wgpu::Adapter::request(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::Default, backends: wgpu::BackendBit::PRIMARY, - }).unwrap(); + }) + .unwrap(); let (device, mut queue) = adapter.request_device(&wgpu::DeviceDescriptor { extensions: wgpu::Extensions { diff --git a/wgpu/examples/skybox/images/negx.png b/wgpu/examples/skybox/images/negx.png new file mode 100644 index 0000000000..5153078716 Binary files /dev/null and b/wgpu/examples/skybox/images/negx.png differ diff --git a/wgpu/examples/skybox/images/negy.png b/wgpu/examples/skybox/images/negy.png new file mode 100644 index 0000000000..691c459bd0 Binary files /dev/null and b/wgpu/examples/skybox/images/negy.png differ diff --git a/wgpu/examples/skybox/images/negz.png b/wgpu/examples/skybox/images/negz.png new file mode 100644 index 0000000000..3d5e88f3d1 Binary files /dev/null and b/wgpu/examples/skybox/images/negz.png differ diff --git a/wgpu/examples/skybox/images/posx.png b/wgpu/examples/skybox/images/posx.png new file mode 100644 index 0000000000..008bb3992c Binary files /dev/null and b/wgpu/examples/skybox/images/posx.png differ diff --git a/wgpu/examples/skybox/images/posy.png b/wgpu/examples/skybox/images/posy.png new file mode 100644 index 0000000000..8180647a0d Binary files /dev/null and b/wgpu/examples/skybox/images/posy.png differ diff --git a/wgpu/examples/skybox/images/posz.png b/wgpu/examples/skybox/images/posz.png new file mode 100644 index 0000000000..bf46648b67 Binary files /dev/null and b/wgpu/examples/skybox/images/posz.png differ diff --git a/wgpu/examples/skybox/main.rs b/wgpu/examples/skybox/main.rs new file mode 100644 index 0000000000..a90bd9a995 --- /dev/null +++ b/wgpu/examples/skybox/main.rs @@ -0,0 +1,317 @@ +#[path = "../framework.rs"] +mod framework; + +const SKYBOX_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm; + +type Uniforms = [cgmath::Matrix4; 2]; + +pub struct Skybox { + aspect: f32, + pipeline: wgpu::RenderPipeline, + bind_group: wgpu::BindGroup, + uniform_buf: wgpu::Buffer, + uniforms: Uniforms, +} + +impl Skybox { + fn generate_uniforms(aspect_ratio: f32) -> Uniforms { + let mx_projection = cgmath::perspective(cgmath::Deg(45f32), aspect_ratio, 1.0, 10.0); + let mx_view = cgmath::Matrix4::look_at( + cgmath::Point3::new(1.5f32, -5.0, 3.0), + cgmath::Point3::new(0f32, 0.0, 0.0), + cgmath::Vector3::unit_z(), + ); + let mx_correction = framework::OPENGL_TO_WGPU_MATRIX; + [mx_correction * mx_projection, mx_correction * mx_view] + } +} + +impl framework::Example for Skybox { + fn init( + sc_desc: &wgpu::SwapChainDescriptor, + device: &wgpu::Device, + ) -> (Self, Option) { + let mut init_encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { todo: 0 }); + + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + bindings: &[ + wgpu::BindGroupLayoutBinding { + binding: 0, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::UniformBuffer { dynamic: false }, + }, + wgpu::BindGroupLayoutBinding { + binding: 1, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::SampledTexture { + multisampled: false, + dimension: wgpu::TextureViewDimension::Cube, + }, + }, + wgpu::BindGroupLayoutBinding { + binding: 2, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Sampler, + }, + ], + }); + + // Create the render pipeline + let vs_bytes = framework::load_glsl( + include_str!("skybox_vert.glsl"), + framework::ShaderStage::Vertex, + ); + let fs_bytes = framework::load_glsl( + include_str!("skybox_frag.glsl"), + framework::ShaderStage::Fragment, + ); + let vs_module = device.create_shader_module(&vs_bytes); + let fs_module = device.create_shader_module(&fs_bytes); + + let aspect = sc_desc.width as f32 / sc_desc.height as f32; + let uniforms = Self::generate_uniforms(aspect); + let uniform_buf = device + .create_buffer_mapped( + uniforms.len(), + wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, + ) + .fill_from_slice(&uniforms); + let uniform_buf_size = std::mem::size_of::(); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + bind_group_layouts: &[&bind_group_layout], + }); + + // Create the render pipeline + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + layout: &pipeline_layout, + vertex_stage: wgpu::ProgrammableStageDescriptor { + module: &vs_module, + entry_point: "main", + }, + fragment_stage: Some(wgpu::ProgrammableStageDescriptor { + module: &fs_module, + entry_point: "main", + }), + rasterization_state: Some(wgpu::RasterizationStateDescriptor { + front_face: wgpu::FrontFace::Cw, + cull_mode: wgpu::CullMode::None, + depth_bias: 0, + depth_bias_slope_scale: 0.0, + depth_bias_clamp: 0.0, + }), + primitive_topology: wgpu::PrimitiveTopology::TriangleList, + color_states: &[wgpu::ColorStateDescriptor { + format: sc_desc.format, + color_blend: wgpu::BlendDescriptor::REPLACE, + alpha_blend: wgpu::BlendDescriptor::REPLACE, + write_mask: wgpu::ColorWrite::ALL, + }], + depth_stencil_state: None, + index_format: wgpu::IndexFormat::Uint16, + vertex_buffers: &[], + sample_count: 1, + sample_mask: !0, + alpha_to_coverage_enabled: false, + }); + + 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::Nearest, + min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::FilterMode::Nearest, + lod_min_clamp: -100.0, + lod_max_clamp: 100.0, + compare_function: wgpu::CompareFunction::Always, + }); + + let paths: [&'static [u8]; 6] = [ + &include_bytes!("images/posx.png")[..], + &include_bytes!("images/negx.png")[..], + &include_bytes!("images/posy.png")[..], + &include_bytes!("images/negy.png")[..], + &include_bytes!("images/posz.png")[..], + &include_bytes!("images/negz.png")[..], + ]; + + // we set these multiple times, but whatever + let (mut image_width, mut image_height) = (0, 0); + let faces = paths + .iter() + .map(|png| { + let png = std::io::Cursor::new(png); + let decoder = png::Decoder::new(png); + let (info, mut reader) = decoder.read_info().expect("can read info"); + image_width = info.width; + image_height = info.height; + let mut buf = vec![0; info.buffer_size()]; + reader.next_frame(&mut buf).expect("can read png frame"); + buf + }) + .collect::>(); + + let texture_extent = wgpu::Extent3d { + width: image_width, + height: image_height, + depth: 1, + }; + + let texture = device.create_texture(&wgpu::TextureDescriptor { + size: texture_extent, + array_layer_count: 6, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: SKYBOX_FORMAT, + usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST, + }); + + for (i, image) in faces.iter().enumerate() { + println!( + "Copying skybox image {} of size {},{} to gpu", + i, image_width, image_height, + ); + let image_buf = device + .create_buffer_mapped(image.len(), wgpu::BufferUsage::COPY_SRC) + .fill_from_slice(&image); + + init_encoder.copy_buffer_to_texture( + wgpu::BufferCopyView { + buffer: &image_buf, + offset: 0, + row_pitch: 4 * image_width, + image_height, + }, + wgpu::TextureCopyView { + texture: &texture, + mip_level: 0, + array_layer: i as u32, + origin: wgpu::Origin3d { + x: 0.0, + y: 0.0, + z: 0.0, + }, + }, + texture_extent, + ); + } + + let texture_view = texture.create_view(&wgpu::TextureViewDescriptor { + format: SKYBOX_FORMAT, + dimension: wgpu::TextureViewDimension::Cube, + aspect: wgpu::TextureAspect::default(), + base_mip_level: 0, + level_count: 1, + base_array_layer: 0, + array_layer_count: 6, + }); + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &bind_group_layout, + bindings: &[ + wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::Buffer { + buffer: &uniform_buf, + range: 0..uniform_buf_size as wgpu::BufferAddress, + }, + }, + wgpu::Binding { + binding: 1, + resource: wgpu::BindingResource::TextureView(&texture_view), + }, + wgpu::Binding { + binding: 2, + resource: wgpu::BindingResource::Sampler(&sampler), + }, + ], + }); + ( + Self { + pipeline, + bind_group, + uniform_buf, + aspect, + uniforms, + }, + Some(init_encoder.finish()), + ) + } + + fn update(&mut self, _event: winit::event::WindowEvent) { + //empty + } + + fn resize( + &mut self, + sc_desc: &wgpu::SwapChainDescriptor, + device: &wgpu::Device, + ) -> Option { + self.aspect = sc_desc.width as f32 / sc_desc.height as f32; + let uniforms = Skybox::generate_uniforms(self.aspect); + let mx_total = uniforms[0] * uniforms[1]; + let mx_ref: &[f32; 16] = mx_total.as_ref(); + + let temp_buf = device + .create_buffer_mapped(16, wgpu::BufferUsage::COPY_SRC) + .fill_from_slice(mx_ref); + + let mut init_encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { todo: 0 }); + init_encoder.copy_buffer_to_buffer(&temp_buf, 0, &self.uniform_buf, 0, 64); + self.uniforms = uniforms; + Some(init_encoder.finish()) + } + + fn render( + &mut self, + frame: &wgpu::SwapChainOutput, + device: &wgpu::Device, + ) -> wgpu::CommandBuffer { + let mut init_encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { todo: 0 }); + let rotation = cgmath::Matrix4::::from_angle_x(cgmath::Deg(1.0)); + self.uniforms[1] = self.uniforms[1] * rotation; + let uniform_buf_size = std::mem::size_of::(); + let temp_buf = device + .create_buffer_mapped(2, wgpu::BufferUsage::COPY_SRC) + .fill_from_slice(&self.uniforms); + + init_encoder.copy_buffer_to_buffer( + &temp_buf, + 0, + &self.uniform_buf, + 0, + uniform_buf_size as wgpu::BufferAddress, + ); + + { + let mut rpass = init_encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { + attachment: &frame.view, + resolve_target: None, + load_op: wgpu::LoadOp::Clear, + store_op: wgpu::StoreOp::Store, + clear_color: wgpu::Color { + r: 0.1, + g: 0.2, + b: 0.3, + a: 1.0, + }, + }], + depth_stencil_attachment: None, + }); + + rpass.set_pipeline(&self.pipeline); + rpass.set_bind_group(0, &self.bind_group, &[]); + rpass.draw(0..3 as u32, 0..1); + } + init_encoder.finish() + } +} + +fn main() { + framework::run::("skybox"); +} diff --git a/wgpu/examples/skybox/skybox_frag.glsl b/wgpu/examples/skybox/skybox_frag.glsl new file mode 100644 index 0000000000..8c0cee3976 --- /dev/null +++ b/wgpu/examples/skybox/skybox_frag.glsl @@ -0,0 +1,11 @@ +#version 450 + +layout(set = 0, binding = 1) uniform textureCube t_Cubemap; +layout(set = 0, binding = 2) uniform sampler s_Cubemap; + +layout(location = 0) in vec3 v_Uv; +layout(location = 0) out vec4 f_Color; + +void main() { + f_Color = texture(samplerCube(t_Cubemap, s_Cubemap), v_Uv); +} diff --git a/wgpu/examples/skybox/skybox_vert.glsl b/wgpu/examples/skybox/skybox_vert.glsl new file mode 100644 index 0000000000..f6aabda618 --- /dev/null +++ b/wgpu/examples/skybox/skybox_vert.glsl @@ -0,0 +1,22 @@ +#version 450 + +layout(location = 0) out vec3 v_Uv; + +layout(set = 0, binding = 0) uniform Data { + mat4 proj; + mat4 view; +}; + +void main() { + vec4 pos = vec4(0.0); + switch(gl_VertexIndex) { + case 0: pos = vec4(-1.0, -1.0, 0.0, 1.0); break; + case 1: pos = vec4( 3.0, -1.0, 0.0, 1.0); break; + case 2: pos = vec4(-1.0, 3.0, 0.0, 1.0); break; + } + mat3 invModelView = transpose(mat3(view)); + vec3 unProjected = (inverse(proj) * pos).xyz; + v_Uv = invModelView * unProjected; + + gl_Position = pos; +}