From 72164c23a2aabf08d3bed55363fef60f70a6782f Mon Sep 17 00:00:00 2001 From: Dzmitry Malyshau Date: Fri, 10 May 2019 13:19:33 -0400 Subject: [PATCH] Move the library and examples from the original repo --- Cargo.toml | 33 + bors.toml | 5 + examples/cube/main.rs | 346 +++++++++ examples/cube/shader.frag | 12 + examples/cube/shader.vert | 16 + examples/framework.rs | 128 ++++ examples/hello_compute/main.rs | 94 +++ examples/hello_compute/shader.comp | 31 + examples/hello_compute/shader.comp.spv | Bin 0 -> 1576 bytes examples/hello_triangle/main.rs | 128 ++++ examples/hello_triangle/shader.frag | 7 + examples/hello_triangle/shader.frag.spv | Bin 0 -> 352 bytes examples/hello_triangle/shader.vert | 15 + examples/hello_triangle/shader.vert.spv | Bin 0 -> 904 bytes examples/shadow/bake.frag | 4 + examples/shadow/bake.vert | 18 + examples/shadow/forward.frag | 54 ++ examples/shadow/forward.vert | 24 + examples/shadow/main.rs | 807 ++++++++++++++++++++ rustfmt.toml | 3 + src/lib.rs | 965 ++++++++++++++++++++++++ tests/multithreaded_compute.rs | 100 +++ 22 files changed, 2790 insertions(+) create mode 100644 Cargo.toml create mode 100644 bors.toml create mode 100644 examples/cube/main.rs create mode 100644 examples/cube/shader.frag create mode 100644 examples/cube/shader.vert create mode 100644 examples/framework.rs create mode 100644 examples/hello_compute/main.rs create mode 100644 examples/hello_compute/shader.comp create mode 100644 examples/hello_compute/shader.comp.spv create mode 100644 examples/hello_triangle/main.rs create mode 100644 examples/hello_triangle/shader.frag create mode 100644 examples/hello_triangle/shader.frag.spv create mode 100644 examples/hello_triangle/shader.vert create mode 100644 examples/hello_triangle/shader.vert.spv create mode 100644 examples/shadow/bake.frag create mode 100644 examples/shadow/bake.vert create mode 100644 examples/shadow/forward.frag create mode 100644 examples/shadow/forward.vert create mode 100644 examples/shadow/main.rs create mode 100644 rustfmt.toml create mode 100644 src/lib.rs create mode 100644 tests/multithreaded_compute.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000000..5bbdba4ea3 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "wgpu" +version = "0.2.2" +authors = [ + "Dzmitry Malyshau ", + "Joshua Groves ", +] +edition = "2018" +description = "Rusty wgpu API wrapper" +homepage = "https://github.com/gfx-rs/wgpu-rs" +repository = "https://github.com/gfx-rs/wgpu-rs" +keywords = ["graphics"] +license = "MPL-2.0" + +[lib] + +[features] +default = [] +metal = ["wgn/gfx-backend-metal"] +dx11 = ["wgn/gfx-backend-dx11"] +dx12 = ["wgn/gfx-backend-dx12"] +vulkan = ["wgn/gfx-backend-vulkan"] + +[dependencies] +#TODO: only depend on the published version +wgn = { package = "wgpu-native", features = ["local", "window-winit"], git = "https://github.com/gfx-rs/wgpu", rev = "0edf927e5bb13d78d804e5ff58dce952f81e5832" } +arrayvec = "0.4" + +[dev-dependencies] +cgmath = "0.17" +env_logger = "0.6" +glsl-to-spirv = "0.1" +log = "0.4" diff --git a/bors.toml b/bors.toml new file mode 100644 index 0000000000..dccefd8780 --- /dev/null +++ b/bors.toml @@ -0,0 +1,5 @@ +status = [ + "continuous-integration/travis-ci/push", +] + +timeout_sec = 18000 # 5 hours diff --git a/examples/cube/main.rs b/examples/cube/main.rs new file mode 100644 index 0000000000..1f75ed7401 --- /dev/null +++ b/examples/cube/main.rs @@ -0,0 +1,346 @@ +#[path = "../framework.rs"] +mod framework; + +#[derive(Clone, Copy)] +struct Vertex { + _pos: [f32; 4], + _tex_coord: [f32; 2], +} + +fn vertex(pos: [i8; 3], tc: [i8; 2]) -> Vertex { + Vertex { + _pos: [pos[0] as f32, pos[1] as f32, pos[2] as f32, 1.0], + _tex_coord: [tc[0] as f32, tc[1] as f32], + } +} + +fn create_vertices() -> (Vec, Vec) { + let vertex_data = [ + // top (0, 0, 1) + vertex([-1, -1, 1], [0, 0]), + vertex([1, -1, 1], [1, 0]), + vertex([1, 1, 1], [1, 1]), + vertex([-1, 1, 1], [0, 1]), + // bottom (0, 0, -1) + vertex([-1, 1, -1], [1, 0]), + vertex([1, 1, -1], [0, 0]), + vertex([1, -1, -1], [0, 1]), + vertex([-1, -1, -1], [1, 1]), + // right (1, 0, 0) + vertex([1, -1, -1], [0, 0]), + vertex([1, 1, -1], [1, 0]), + vertex([1, 1, 1], [1, 1]), + vertex([1, -1, 1], [0, 1]), + // left (-1, 0, 0) + vertex([-1, -1, 1], [1, 0]), + vertex([-1, 1, 1], [0, 0]), + vertex([-1, 1, -1], [0, 1]), + vertex([-1, -1, -1], [1, 1]), + // front (0, 1, 0) + vertex([1, 1, -1], [1, 0]), + vertex([-1, 1, -1], [0, 0]), + vertex([-1, 1, 1], [0, 1]), + vertex([1, 1, 1], [1, 1]), + // back (0, -1, 0) + vertex([1, -1, 1], [0, 0]), + vertex([-1, -1, 1], [1, 0]), + vertex([-1, -1, -1], [1, 1]), + vertex([1, -1, -1], [0, 1]), + ]; + + let index_data: &[u16] = &[ + 0, 1, 2, 2, 3, 0, // top + 4, 5, 6, 6, 7, 4, // bottom + 8, 9, 10, 10, 11, 8, // right + 12, 13, 14, 14, 15, 12, // left + 16, 17, 18, 18, 19, 16, // front + 20, 21, 22, 22, 23, 20, // back + ]; + + (vertex_data.to_vec(), index_data.to_vec()) +} + +fn create_texels(size: usize) -> Vec { + use std::iter; + + (0..size * size) + .flat_map(|id| { + // get high five for recognizing this ;) + let cx = 3.0 * (id % size) as f32 / (size - 1) as f32 - 2.0; + let cy = 2.0 * (id / size) as f32 / (size - 1) as f32 - 1.0; + let (mut x, mut y, mut count) = (cx, cy, 0); + while count < 0xFF && x * x + y * y < 4.0 { + let old_x = x; + x = x * x - y * y + cx; + y = 2.0 * old_x * y + cy; + count += 1; + } + iter::once(0xFF - (count * 5) as u8) + .chain(iter::once(0xFF - (count * 15) as u8)) + .chain(iter::once(0xFF - (count * 50) as u8)) + .chain(iter::once(1)) + }) + .collect() +} + +struct Example { + vertex_buf: wgpu::Buffer, + index_buf: wgpu::Buffer, + index_count: usize, + bind_group: wgpu::BindGroup, + uniform_buf: wgpu::Buffer, + pipeline: wgpu::RenderPipeline, +} + +impl Example { + fn generate_matrix(aspect_ratio: f32) -> cgmath::Matrix4 { + 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(), + ); + mx_projection * mx_view + } +} + +impl framework::Example for Example { + fn init(sc_desc: &wgpu::SwapChainDescriptor, device: &mut wgpu::Device) -> Self { + use std::mem; + + let mut init_encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { todo: 0 }); + + // Create the vertex and index buffers + let vertex_size = mem::size_of::(); + let (vertex_data, index_data) = create_vertices(); + let vertex_buf = device + .create_buffer_mapped(vertex_data.len(), wgpu::BufferUsageFlags::VERTEX) + .fill_from_slice(&vertex_data); + + let index_buf = device + .create_buffer_mapped(index_data.len(), wgpu::BufferUsageFlags::INDEX) + .fill_from_slice(&index_data); + + // Create pipeline layout + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + bindings: &[ + wgpu::BindGroupLayoutBinding { + binding: 0, + visibility: wgpu::ShaderStageFlags::VERTEX, + ty: wgpu::BindingType::UniformBuffer, + }, + wgpu::BindGroupLayoutBinding { + binding: 1, + visibility: wgpu::ShaderStageFlags::FRAGMENT, + ty: wgpu::BindingType::SampledTexture, + }, + wgpu::BindGroupLayoutBinding { + binding: 2, + visibility: wgpu::ShaderStageFlags::FRAGMENT, + ty: wgpu::BindingType::Sampler, + }, + ], + }); + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + bind_group_layouts: &[&bind_group_layout], + }); + + // Create the texture + let size = 256u32; + let texels = create_texels(size as usize); + let texture_extent = wgpu::Extent3d { + width: size, + height: size, + depth: 1, + }; + let texture = device.create_texture(&wgpu::TextureDescriptor { + size: texture_extent, + array_size: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsageFlags::SAMPLED | wgpu::TextureUsageFlags::TRANSFER_DST, + }); + let texture_view = texture.create_default_view(); + let temp_buf = device + .create_buffer_mapped(texels.len(), wgpu::BufferUsageFlags::TRANSFER_SRC) + .fill_from_slice(&texels); + init_encoder.copy_buffer_to_texture( + wgpu::BufferCopyView { + buffer: &temp_buf, + offset: 0, + row_pitch: 4 * size, + image_height: size, + }, + wgpu::TextureCopyView { + texture: &texture, + level: 0, + slice: 0, + origin: wgpu::Origin3d { + x: 0.0, + y: 0.0, + z: 0.0, + }, + }, + texture_extent, + ); + + // Create other resources + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + r_address_mode: wgpu::AddressMode::ClampToEdge, + s_address_mode: wgpu::AddressMode::ClampToEdge, + t_address_mode: 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, + max_anisotropy: 0, + compare_function: wgpu::CompareFunction::Always, + border_color: wgpu::BorderColor::TransparentBlack, + }); + let mx_total = Self::generate_matrix(sc_desc.width as f32 / sc_desc.height as f32); + let mx_ref: &[f32; 16] = mx_total.as_ref(); + let uniform_buf = device + .create_buffer_mapped( + 16, + wgpu::BufferUsageFlags::UNIFORM | wgpu::BufferUsageFlags::TRANSFER_DST, + ) + .fill_from_slice(mx_ref); + + // Create bind group + 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..64, + }, + }, + wgpu::Binding { + binding: 1, + resource: wgpu::BindingResource::TextureView(&texture_view), + }, + wgpu::Binding { + binding: 2, + resource: wgpu::BindingResource::Sampler(&sampler), + }, + ], + }); + + // Create the render pipeline + let vs_bytes = framework::load_glsl(include_str!("shader.vert"), framework::ShaderStage::Vertex); + let fs_bytes = framework::load_glsl(include_str!("shader.frag"), framework::ShaderStage::Fragment); + let vs_module = device.create_shader_module(&vs_bytes); + let fs_module = device.create_shader_module(&fs_bytes); + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + layout: &pipeline_layout, + vertex_stage: wgpu::PipelineStageDescriptor { + module: &vs_module, + entry_point: "main", + }, + fragment_stage: wgpu::PipelineStageDescriptor { + module: &fs_module, + entry_point: "main", + }, + rasterization_state: wgpu::RasterizationStateDescriptor { + front_face: wgpu::FrontFace::Cw, + cull_mode: wgpu::CullMode::Back, + 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: wgpu::BlendDescriptor::REPLACE, + alpha: wgpu::BlendDescriptor::REPLACE, + write_mask: wgpu::ColorWriteFlags::ALL, + }], + depth_stencil_state: None, + index_format: wgpu::IndexFormat::Uint16, + vertex_buffers: &[wgpu::VertexBufferDescriptor { + stride: vertex_size as u32, + step_mode: wgpu::InputStepMode::Vertex, + attributes: &[ + wgpu::VertexAttributeDescriptor { + attribute_index: 0, + format: wgpu::VertexFormat::Float4, + offset: 0, + }, + wgpu::VertexAttributeDescriptor { + attribute_index: 1, + format: wgpu::VertexFormat::Float2, + offset: 4 * 4, + }, + ], + }], + sample_count: 1, + }); + + // Done + let init_command_buf = init_encoder.finish(); + device.get_queue().submit(&[init_command_buf]); + Example { + vertex_buf, + index_buf, + index_count: index_data.len(), + bind_group, + uniform_buf, + pipeline, + } + } + + fn update(&mut self, _event: wgpu::winit::WindowEvent) { + //empty + } + + fn resize(&mut self, sc_desc: &wgpu::SwapChainDescriptor, device: &mut wgpu::Device) { + let mx_total = Self::generate_matrix(sc_desc.width as f32 / sc_desc.height as f32); + let mx_ref: &[f32; 16] = mx_total.as_ref(); + + let temp_buf = device + .create_buffer_mapped(16, wgpu::BufferUsageFlags::TRANSFER_SRC) + .fill_from_slice(mx_ref); + + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { todo: 0 }); + encoder.copy_buffer_to_buffer(&temp_buf, 0, &self.uniform_buf, 0, 64); + device.get_queue().submit(&[encoder.finish()]); + } + + fn render(&mut self, frame: &wgpu::SwapChainOutput, device: &mut wgpu::Device) { + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { todo: 0 }); + { + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { + attachment: &frame.view, + 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.set_index_buffer(&self.index_buf, 0); + rpass.set_vertex_buffers(&[(&self.vertex_buf, 0)]); + rpass.draw_indexed(0..self.index_count as u32, 0, 0..1); + } + + device.get_queue().submit(&[encoder.finish()]); + } +} + +fn main() { + framework::run::("cube"); +} diff --git a/examples/cube/shader.frag b/examples/cube/shader.frag new file mode 100644 index 0000000000..b88e3abf0c --- /dev/null +++ b/examples/cube/shader.frag @@ -0,0 +1,12 @@ +#version 450 + +layout(location = 0) in vec2 v_TexCoord; +layout(location = 0) out vec4 o_Target; +layout(set = 0, binding = 1) uniform texture2D t_Color; +layout(set = 0, binding = 2) uniform sampler s_Color; + +void main() { + vec4 tex = texture(sampler2D(t_Color, s_Color), v_TexCoord); + float mag = length(v_TexCoord-vec2(0.5)); + o_Target = mix(tex, vec4(0.0), mag*mag); +} diff --git a/examples/cube/shader.vert b/examples/cube/shader.vert new file mode 100644 index 0000000000..badc7fc203 --- /dev/null +++ b/examples/cube/shader.vert @@ -0,0 +1,16 @@ +#version 450 + +layout(location = 0) in vec4 a_Pos; +layout(location = 1) in vec2 a_TexCoord; +layout(location = 0) out vec2 v_TexCoord; + +layout(set = 0, binding = 0) uniform Locals { + mat4 u_Transform; +}; + +void main() { + v_TexCoord = a_TexCoord; + gl_Position = u_Transform * a_Pos; + // convert from -1,1 Z to 0,1 + gl_Position.z = 0.5 * (gl_Position.z + gl_Position.w); +} diff --git a/examples/framework.rs b/examples/framework.rs new file mode 100644 index 0000000000..ce6373268f --- /dev/null +++ b/examples/framework.rs @@ -0,0 +1,128 @@ +use log::info; + +#[allow(dead_code)] +pub fn cast_slice(data: &[T]) -> &[u8] { + use std::mem::size_of; + use std::slice::from_raw_parts; + + unsafe { from_raw_parts(data.as_ptr() as *const u8, data.len() * size_of::()) } +} + +#[allow(dead_code)] +pub enum ShaderStage { + Vertex, + Fragment, + Compute, +} + +pub fn load_glsl(code: &str, stage: ShaderStage) -> Vec { + use std::io::Read; + + let ty = match stage { + ShaderStage::Vertex => glsl_to_spirv::ShaderType::Vertex, + ShaderStage::Fragment => glsl_to_spirv::ShaderType::Fragment, + ShaderStage::Compute => glsl_to_spirv::ShaderType::Compute, + }; + + let mut output = glsl_to_spirv::compile(&code, ty).unwrap(); + let mut spv = Vec::new(); + output.read_to_end(&mut spv).unwrap(); + spv +} + +pub trait Example { + fn init(sc_desc: &wgpu::SwapChainDescriptor, device: &mut wgpu::Device) -> Self; + fn resize(&mut self, sc_desc: &wgpu::SwapChainDescriptor, device: &mut wgpu::Device); + fn update(&mut self, event: wgpu::winit::WindowEvent); + fn render(&mut self, frame: &wgpu::SwapChainOutput, device: &mut wgpu::Device); +} + +pub fn run(title: &str) { + use wgpu::winit::{ + ElementState, + Event, + EventsLoop, + KeyboardInput, + VirtualKeyCode, + Window, + WindowEvent, + }; + + info!("Initializing the device..."); + env_logger::init(); + let instance = wgpu::Instance::new(); + let adapter = instance.get_adapter(&wgpu::AdapterDescriptor { + power_preference: wgpu::PowerPreference::LowPower, + }); + let mut device = adapter.create_device(&wgpu::DeviceDescriptor { + extensions: wgpu::Extensions { + anisotropic_filtering: false, + }, + }); + + info!("Initializing the window..."); + let mut events_loop = EventsLoop::new(); + let window = Window::new(&events_loop).unwrap(); + window.set_title(title); + let size = window + .get_inner_size() + .unwrap() + .to_physical(window.get_hidpi_factor()); + + let surface = instance.create_surface(&window); + let mut sc_desc = wgpu::SwapChainDescriptor { + usage: wgpu::TextureUsageFlags::OUTPUT_ATTACHMENT, + format: wgpu::TextureFormat::Bgra8Unorm, + width: size.width.round() as u32, + height: size.height.round() as u32, + }; + let mut swap_chain = device.create_swap_chain(&surface, &sc_desc); + + info!("Initializing the example..."); + let mut example = E::init(&sc_desc, &mut device); + + info!("Entering render loop..."); + let mut running = true; + while running { + events_loop.poll_events(|event| match event { + Event::WindowEvent { + event: WindowEvent::Resized(size), + .. + } => { + let physical = size.to_physical(window.get_hidpi_factor()); + info!("Resizing to {:?}", physical); + sc_desc.width = physical.width.round() as u32; + sc_desc.height = physical.height.round() as u32; + swap_chain = device.create_swap_chain(&surface, &sc_desc); + example.resize(&sc_desc, &mut device); + } + Event::WindowEvent { event, .. } => match event { + WindowEvent::KeyboardInput { + input: + KeyboardInput { + virtual_keycode: Some(VirtualKeyCode::Escape), + state: ElementState::Pressed, + .. + }, + .. + } + | WindowEvent::CloseRequested => { + running = false; + } + _ => { + example.update(event); + } + }, + _ => (), + }); + + let frame = swap_chain.get_next_texture(); + example.render(&frame, &mut device); + running &= !cfg!(feature = "metal-auto-capture"); + } +} + +// This allows treating the framework as a standalone example, +// thus avoiding listing the example names in `Cargo.toml`. +#[allow(dead_code)] +fn main() {} diff --git a/examples/hello_compute/main.rs b/examples/hello_compute/main.rs new file mode 100644 index 0000000000..4947bf3b49 --- /dev/null +++ b/examples/hello_compute/main.rs @@ -0,0 +1,94 @@ +use std::str::FromStr; + +fn main() { + env_logger::init(); + + // For now this just panics if you didn't pass numbers. Could add proper error handling. + if std::env::args().len() == 1 { + panic!("You must pass a list of positive integers!") + } + let numbers: Vec = std::env::args() + .skip(1) + .map(|s| u32::from_str(&s).expect("You must pass a list of positive integers!")) + .collect(); + + let size = (numbers.len() * std::mem::size_of::()) as u32; + + let instance = wgpu::Instance::new(); + let adapter = instance.get_adapter(&wgpu::AdapterDescriptor { + power_preference: wgpu::PowerPreference::Default, + }); + let mut device = adapter.create_device(&wgpu::DeviceDescriptor { + extensions: wgpu::Extensions { + anisotropic_filtering: false, + }, + }); + + let cs_bytes = include_bytes!("shader.comp.spv"); + let cs_module = device.create_shader_module(cs_bytes); + + let staging_buffer = device + .create_buffer_mapped( + numbers.len(), + wgpu::BufferUsageFlags::MAP_READ + | wgpu::BufferUsageFlags::TRANSFER_DST + | wgpu::BufferUsageFlags::TRANSFER_SRC, + ) + .fill_from_slice(&numbers); + + let storage_buffer = device.create_buffer(&wgpu::BufferDescriptor { + size, + usage: wgpu::BufferUsageFlags::STORAGE + | wgpu::BufferUsageFlags::TRANSFER_DST + | wgpu::BufferUsageFlags::TRANSFER_SRC, + }); + + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + bindings: &[wgpu::BindGroupLayoutBinding { + binding: 0, + visibility: wgpu::ShaderStageFlags::COMPUTE, + ty: wgpu::BindingType::StorageBuffer, + }], + }); + + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &bind_group_layout, + bindings: &[wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::Buffer { + buffer: &storage_buffer, + range: 0..size, + }, + }], + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + bind_group_layouts: &[&bind_group_layout], + }); + + let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + layout: &pipeline_layout, + compute_stage: wgpu::PipelineStageDescriptor { + module: &cs_module, + entry_point: "main", + }, + }); + + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { todo: 0 }); + encoder.copy_buffer_to_buffer(&staging_buffer, 0, &storage_buffer, 0, size); + { + let mut cpass = encoder.begin_compute_pass(); + cpass.set_pipeline(&compute_pipeline); + cpass.set_bind_group(0, &bind_group, &[]); + cpass.dispatch(numbers.len() as u32, 1, 1); + } + encoder.copy_buffer_to_buffer(&storage_buffer, 0, &staging_buffer, 0, size); + + device.get_queue().submit(&[encoder.finish()]); + + staging_buffer.map_read_async(0, size, |result: wgpu::BufferMapAsyncResult<&[u32]>| { + if let Ok(mapping) = result { + println!("Times: {:?}", mapping.data); + } + }); +} diff --git a/examples/hello_compute/shader.comp b/examples/hello_compute/shader.comp new file mode 100644 index 0000000000..d1ab4cfd2e --- /dev/null +++ b/examples/hello_compute/shader.comp @@ -0,0 +1,31 @@ +#version 450 +layout(local_size_x = 1) in; + +layout(set = 0, binding = 0) buffer PrimeIndices { + uint[] indices; +}; // this is used as both input and output for convenience + +// The Collatz Conjecture states that for any integer n: +// If n is even, n = n/2 +// If n is odd, n = 3n+1 +// And repeat this process for each new n, you will always eventually reach 1. +// Though the conjecture has not been proven, no counterexample has ever been found. +// This function returns how many times this recurrence needs to be applied to reach 1. +uint collatz_iterations(uint n) { + uint i = 0; + while(n != 1) { + if (mod(n, 2) == 0) { + n = n / 2; + } + else { + n = (3 * n) + 1; + } + i++; + } + return i; +} + +void main() { + uint index = gl_GlobalInvocationID.x; + indices[index] = collatz_iterations(indices[index]); +} \ No newline at end of file diff --git a/examples/hello_compute/shader.comp.spv b/examples/hello_compute/shader.comp.spv new file mode 100644 index 0000000000000000000000000000000000000000..972544e0f9087b83fd9fb907d1c896aecf817a1d GIT binary patch literal 1576 zcmYk6*-leY6o$8yQItU@C$I$v6oaCSA_!_yNw_fa0Z4_$G$}2CYK&K281H-%Z+!wE z$|=$KeWzz7>||yAYx?)v>#VlWG1HUMnbebd)3ek*!>I#LO8u!j)8*Y)yYnZlgZag! z`(_NJLY8RGAUR#=PUNssYZ!Qd4B^)|jF=Zvptb}1JB}_gPS-zHp!zy1Cmu{D!a#hjGPd(S}#x3-vFEQDX+f?_IIQIWA3{J*6-VcQ{QP$xr1{S?EIsC4y@1LTYMXR Y*83goNv$2<%DD#LU9f!rh>P8g|6_G!ZvX%Q literal 0 HcmV?d00001 diff --git a/examples/hello_triangle/main.rs b/examples/hello_triangle/main.rs new file mode 100644 index 0000000000..ac6ad41d30 --- /dev/null +++ b/examples/hello_triangle/main.rs @@ -0,0 +1,128 @@ +fn main() { + env_logger::init(); + + let instance = wgpu::Instance::new(); + let adapter = instance.get_adapter(&wgpu::AdapterDescriptor { + power_preference: wgpu::PowerPreference::LowPower, + }); + let mut device = adapter.create_device(&wgpu::DeviceDescriptor { + extensions: wgpu::Extensions { + anisotropic_filtering: false, + }, + }); + + let vs_bytes = include_bytes!("shader.vert.spv"); + let vs_module = device.create_shader_module(vs_bytes); + let fs_bytes = include_bytes!("shader.frag.spv"); + let fs_module = device.create_shader_module(fs_bytes); + + let bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { bindings: &[] }); + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &bind_group_layout, + bindings: &[], + }); + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + bind_group_layouts: &[&bind_group_layout], + }); + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + layout: &pipeline_layout, + vertex_stage: wgpu::PipelineStageDescriptor { + module: &vs_module, + entry_point: "main", + }, + fragment_stage: wgpu::PipelineStageDescriptor { + module: &fs_module, + entry_point: "main", + }, + rasterization_state: wgpu::RasterizationStateDescriptor { + front_face: wgpu::FrontFace::Ccw, + 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: wgpu::TextureFormat::Bgra8Unorm, + color: wgpu::BlendDescriptor::REPLACE, + alpha: wgpu::BlendDescriptor::REPLACE, + write_mask: wgpu::ColorWriteFlags::ALL, + }], + depth_stencil_state: None, + index_format: wgpu::IndexFormat::Uint16, + vertex_buffers: &[], + sample_count: 1, + }); + + use wgpu::winit::{ + ElementState, + Event, + EventsLoop, + KeyboardInput, + VirtualKeyCode, + Window, + WindowEvent, + }; + + let mut events_loop = EventsLoop::new(); + let window = Window::new(&events_loop).unwrap(); + let size = window + .get_inner_size() + .unwrap() + .to_physical(window.get_hidpi_factor()); + + let surface = instance.create_surface(&window); + let mut swap_chain = device.create_swap_chain( + &surface, + &wgpu::SwapChainDescriptor { + usage: wgpu::TextureUsageFlags::OUTPUT_ATTACHMENT, + format: wgpu::TextureFormat::Bgra8Unorm, + width: size.width.round() as u32, + height: size.height.round() as u32, + }, + ); + let mut running = true; + while running { + events_loop.poll_events(|event| match event { + Event::WindowEvent { event, .. } => match event { + WindowEvent::KeyboardInput { + input: + KeyboardInput { + virtual_keycode: Some(code), + state: ElementState::Pressed, + .. + }, + .. + } => match code { + VirtualKeyCode::Escape => running = false, + _ => {} + }, + WindowEvent::CloseRequested => running = false, + _ => {} + }, + _ => {} + }); + + let frame = swap_chain.get_next_texture(); + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { todo: 0 }); + { + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { + attachment: &frame.view, + load_op: wgpu::LoadOp::Clear, + store_op: wgpu::StoreOp::Store, + clear_color: wgpu::Color::GREEN, + }], + depth_stencil_attachment: None, + }); + rpass.set_pipeline(&render_pipeline); + rpass.set_bind_group(0, &bind_group, &[]); + rpass.draw(0..3, 0..1); + } + + device.get_queue().submit(&[encoder.finish()]); + } +} diff --git a/examples/hello_triangle/shader.frag b/examples/hello_triangle/shader.frag new file mode 100644 index 0000000000..74e14f410e --- /dev/null +++ b/examples/hello_triangle/shader.frag @@ -0,0 +1,7 @@ +#version 450 + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(1.0, 0.0, 0.0, 1.0); +} diff --git a/examples/hello_triangle/shader.frag.spv b/examples/hello_triangle/shader.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..59e491519bc95272ee281f0c70f2f758e7f55974 GIT binary patch literal 352 zcmYk2yJ`Yq6og0D^=?!I?PSGL5epLnf)*Awc6ow;ji4?hYwxr9R5pU&{3>{0_MDl2 z&V`Ng!;+L!No6(kh_>=5QccB>*PEZsrPesC^-YKl-R>WRNeXqBx`>v59)9mJ_w0mknd~dA z&!5~gt&QOJR)KV0zIZKgX-t><>cAfBhU+ix^)24x@VqNZOkTje!+kgY+&8!b;(Zb8 literal 0 HcmV?d00001 diff --git a/examples/hello_triangle/shader.vert b/examples/hello_triangle/shader.vert new file mode 100644 index 0000000000..ac6dcc7c32 --- /dev/null +++ b/examples/hello_triangle/shader.vert @@ -0,0 +1,15 @@ +#version 450 + +out gl_PerVertex { + vec4 gl_Position; +}; + +const vec2 positions[3] = vec2[3]( + vec2(0.0, -0.5), + vec2(0.5, 0.5), + vec2(-0.5, 0.5) +); + +void main() { + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); +} diff --git a/examples/hello_triangle/shader.vert.spv b/examples/hello_triangle/shader.vert.spv new file mode 100644 index 0000000000000000000000000000000000000000..9a3f5994b71471f4f282bdb2299e70ce2ebd9d62 GIT binary patch literal 904 zcmYk4OD}^_5QdLdTa;4wyN_alhy@}M8mp365Q(i++DH@Hgf1-oGl^eiBk?@nMV#i% z%)Il?oH^4kpR1KZ7zm{>6t+Whsv!pxLOB#;+qk~F-hb=2_79H^R8&Gf7OEMi(lZEN znw_rx2v`7lemU$H!UBr_ngYFt$UQwj+_Ycs+pqohJ0Wbs>znJnb^4uNm->9%SKeCt zlTQk-x-F{<{FdQ6hRw(4HvT1?_jt;Q2@QJ5Ia`CN>}75TOu@>p$I~a8S^Oeg1m@8D zVq@R4n)Z^5V1}`7$Nr~j>^|gXh`lEQtR(EF@hHB>-eXvMJA?WPHUb{?#^b<#%fu5{ zYvjyZTLqpho=n2eJ$9jX3aF9KG}HK;dZw8nkP{gUl{9v?Y@P@|gpa!UO+4Rg%`tCqH}2je{|!^XdY8TZ2GpJrJHsOI z{&UpkocV$Z?_C9QYScT!8j!z5<386@4)e!tq?~WM;WqE90k!7bZv?!@`L}@If&aC0 L7_0wbD<|L=@_;WL literal 0 HcmV?d00001 diff --git a/examples/shadow/bake.frag b/examples/shadow/bake.frag new file mode 100644 index 0000000000..f0bcb49bf9 --- /dev/null +++ b/examples/shadow/bake.frag @@ -0,0 +1,4 @@ +#version 450 + +void main() { +} diff --git a/examples/shadow/bake.vert b/examples/shadow/bake.vert new file mode 100644 index 0000000000..32e257c6b7 --- /dev/null +++ b/examples/shadow/bake.vert @@ -0,0 +1,18 @@ +#version 450 + +layout(location = 0) in ivec4 a_Pos; + +layout(set = 0, binding = 0) uniform Globals { + mat4 u_ViewProj; +}; + +layout(set = 1, binding = 0) uniform Entity { + mat4 u_World; + vec4 u_Color; +}; + +void main() { + gl_Position = u_ViewProj * u_World * vec4(a_Pos); + // convert from -1,1 Z to 0,1 + gl_Position.z = 0.5 * (gl_Position.z + gl_Position.w); +} diff --git a/examples/shadow/forward.frag b/examples/shadow/forward.frag new file mode 100644 index 0000000000..9a2bd8fc67 --- /dev/null +++ b/examples/shadow/forward.frag @@ -0,0 +1,54 @@ +#version 450 + +const int MAX_LIGHTS = 10; + +layout(location = 0) in vec3 v_Normal; +layout(location = 1) in vec4 v_Position; + +layout(location = 0) out vec4 o_Target; + +struct Light { + mat4 proj; + vec4 pos; + vec4 color; +}; + +layout(set = 0, binding = 0) uniform Globals { + mat4 u_ViewProj; + uvec4 u_NumLights; +}; +layout(set = 0, binding = 1) uniform Lights { + Light u_Lights[MAX_LIGHTS]; +}; +layout(set = 0, binding = 2) uniform texture2DArray t_Shadow; +layout(set = 0, binding = 3) uniform samplerShadow s_Shadow; + +layout(set = 1, binding = 0) uniform Entity { + mat4 u_World; + vec4 u_Color; +}; + + +void main() { + vec3 normal = normalize(v_Normal); + vec3 ambient = vec3(0.05, 0.05, 0.05); + // accumulate color + vec3 color = ambient; + for (int i=0; i Vertex { + Vertex { + _pos: [pos[0], pos[1], pos[2], 1], + _normal: [nor[0], nor[1], nor[2], 0], + } +} + +fn create_cube() -> (Vec, Vec) { + let vertex_data = [ + // top (0, 0, 1) + vertex([-1, -1, 1], [0, 0, 1]), + vertex([1, -1, 1], [0, 0, 1]), + vertex([1, 1, 1], [0, 0, 1]), + vertex([-1, 1, 1], [0, 0, 1]), + // bottom (0, 0, -1) + vertex([-1, 1, -1], [0, 0, -1]), + vertex([1, 1, -1], [0, 0, -1]), + vertex([1, -1, -1], [0, 0, -1]), + vertex([-1, -1, -1], [0, 0, -1]), + // right (1, 0, 0) + vertex([1, -1, -1], [1, 0, 0]), + vertex([1, 1, -1], [1, 0, 0]), + vertex([1, 1, 1], [1, 0, 0]), + vertex([1, -1, 1], [1, 0, 0]), + // left (-1, 0, 0) + vertex([-1, -1, 1], [-1, 0, 0]), + vertex([-1, 1, 1], [-1, 0, 0]), + vertex([-1, 1, -1], [-1, 0, 0]), + vertex([-1, -1, -1], [-1, 0, 0]), + // front (0, 1, 0) + vertex([1, 1, -1], [0, 1, 0]), + vertex([-1, 1, -1], [0, 1, 0]), + vertex([-1, 1, 1], [0, 1, 0]), + vertex([1, 1, 1], [0, 1, 0]), + // back (0, -1, 0) + vertex([1, -1, 1], [0, -1, 0]), + vertex([-1, -1, 1], [0, -1, 0]), + vertex([-1, -1, -1], [0, -1, 0]), + vertex([1, -1, -1], [0, -1, 0]), + ]; + + let index_data: &[u16] = &[ + 0, 1, 2, 2, 3, 0, // top + 4, 5, 6, 6, 7, 4, // bottom + 8, 9, 10, 10, 11, 8, // right + 12, 13, 14, 14, 15, 12, // left + 16, 17, 18, 18, 19, 16, // front + 20, 21, 22, 22, 23, 20, // back + ]; + + (vertex_data.to_vec(), index_data.to_vec()) +} + +fn create_plane(size: i8) -> (Vec, Vec) { + let vertex_data = [ + vertex([size, -size, 0], [0, 0, 1]), + vertex([size, size, 0], [0, 0, 1]), + vertex([-size, -size, 0], [0, 0, 1]), + vertex([-size, size, 0], [0, 0, 1]), + ]; + + let index_data: &[u16] = &[0, 1, 2, 2, 1, 3]; + + (vertex_data.to_vec(), index_data.to_vec()) +} + +struct Entity { + mx_world: cgmath::Matrix4, + rotation_speed: f32, + color: wgpu::Color, + vertex_buf: Rc, + index_buf: Rc, + index_count: usize, + bind_group: wgpu::BindGroup, + uniform_buf: wgpu::Buffer, +} + +struct Light { + pos: cgmath::Point3, + color: wgpu::Color, + fov: f32, + depth: Range, + target_view: wgpu::TextureView, +} + +#[repr(C)] +#[derive(Clone, Copy)] +struct LightRaw { + proj: [[f32; 4]; 4], + pos: [f32; 4], + color: [f32; 4], +} + +impl Light { + fn to_raw(&self) -> LightRaw { + use cgmath::{Deg, EuclideanSpace, Matrix4, PerspectiveFov, Point3, Vector3}; + + let mx_view = Matrix4::look_at(self.pos, Point3::origin(), -Vector3::unit_z()); + let projection = PerspectiveFov { + fovy: Deg(self.fov).into(), + aspect: 1.0, + near: self.depth.start, + far: self.depth.end, + }; + let mx_view_proj = cgmath::Matrix4::from(projection.to_perspective()) * mx_view; + LightRaw { + proj: *mx_view_proj.as_ref(), + pos: [self.pos.x, self.pos.y, self.pos.z, 1.0], + color: [self.color.r, self.color.g, self.color.b, 1.0], + } + } +} + +#[repr(C)] +#[derive(Clone, Copy)] +struct ForwardUniforms { + proj: [[f32; 4]; 4], + num_lights: [u32; 4], +} + +#[repr(C)] +#[derive(Clone, Copy)] +struct EntityUniforms { + model: cgmath::Matrix4, + color: [f32; 4], +} + +#[repr(C)] +struct ShadowUniforms { + proj: [[f32; 4]; 4], +} + +struct Pass { + pipeline: wgpu::RenderPipeline, + bind_group: wgpu::BindGroup, + uniform_buf: wgpu::Buffer, +} + +struct Example { + entities: Vec, + lights: Vec, + lights_are_dirty: bool, + shadow_pass: Pass, + forward_pass: Pass, + forward_depth: wgpu::TextureView, + light_uniform_buf: wgpu::Buffer, +} + +impl Example { + const MAX_LIGHTS: usize = 10; + const SHADOW_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::D32Float; + const SHADOW_SIZE: wgpu::Extent3d = wgpu::Extent3d { + width: 512, + height: 512, + depth: 1, + }; + const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::D32Float; + + fn generate_matrix(aspect_ratio: f32) -> cgmath::Matrix4 { + let mx_projection = cgmath::perspective(cgmath::Deg(45f32), aspect_ratio, 1.0, 20.0); + let mx_view = cgmath::Matrix4::look_at( + cgmath::Point3::new(3.0f32, -10.0, 6.0), + cgmath::Point3::new(0f32, 0.0, 0.0), + -cgmath::Vector3::unit_z(), + ); + mx_projection * mx_view + } +} + +impl framework::Example for Example { + fn init(sc_desc: &wgpu::SwapChainDescriptor, device: &mut wgpu::Device) -> Self { + // Create the vertex and index buffers + let vertex_size = mem::size_of::(); + let (cube_vertex_data, cube_index_data) = create_cube(); + let cube_vertex_buf = Rc::new( + device + .create_buffer_mapped(cube_vertex_data.len(), wgpu::BufferUsageFlags::VERTEX) + .fill_from_slice(&cube_vertex_data), + ); + + let cube_index_buf = Rc::new( + device + .create_buffer_mapped(cube_index_data.len(), wgpu::BufferUsageFlags::INDEX) + .fill_from_slice(&cube_index_data), + ); + + let (plane_vertex_data, plane_index_data) = create_plane(7); + let plane_vertex_buf = device + .create_buffer_mapped(plane_vertex_data.len(), wgpu::BufferUsageFlags::VERTEX) + .fill_from_slice(&plane_vertex_data); + + let plane_index_buf = device + .create_buffer_mapped(plane_index_data.len(), wgpu::BufferUsageFlags::INDEX) + .fill_from_slice(&plane_index_data); + + let entity_uniform_size = mem::size_of::() as u32; + let plane_uniform_buf = device.create_buffer(&wgpu::BufferDescriptor { + size: entity_uniform_size, + usage: wgpu::BufferUsageFlags::UNIFORM | wgpu::BufferUsageFlags::TRANSFER_DST, + }); + + let local_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + bindings: &[wgpu::BindGroupLayoutBinding { + binding: 0, + visibility: wgpu::ShaderStageFlags::VERTEX | wgpu::ShaderStageFlags::FRAGMENT, + ty: wgpu::BindingType::UniformBuffer, + }], + }); + + let mut entities = vec![{ + use cgmath::SquareMatrix; + + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &local_bind_group_layout, + bindings: &[wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::Buffer { + buffer: &plane_uniform_buf, + range: 0..entity_uniform_size, + }, + }], + }); + Entity { + mx_world: cgmath::Matrix4::identity(), + rotation_speed: 0.0, + color: wgpu::Color::WHITE, + vertex_buf: Rc::new(plane_vertex_buf), + index_buf: Rc::new(plane_index_buf), + index_count: plane_index_data.len(), + bind_group, + uniform_buf: plane_uniform_buf, + } + }]; + + struct CubeDesc { + offset: cgmath::Vector3, + angle: f32, + scale: f32, + rotation: f32, + } + let cube_descs = [ + CubeDesc { + offset: cgmath::vec3(-2.0, -2.0, 2.0), + angle: 10.0, + scale: 0.7, + rotation: 1.0, + }, + CubeDesc { + offset: cgmath::vec3(2.0, -2.0, 2.0), + angle: 50.0, + scale: 1.3, + rotation: 2.0, + }, + CubeDesc { + offset: cgmath::vec3(-2.0, 2.0, 2.0), + angle: 140.0, + scale: 1.1, + rotation: 3.0, + }, + CubeDesc { + offset: cgmath::vec3(2.0, 2.0, 2.0), + angle: 210.0, + scale: 0.9, + rotation: 4.0, + }, + ]; + + for cube in &cube_descs { + use cgmath::{Decomposed, Deg, InnerSpace, Quaternion, Rotation3}; + + let transform = Decomposed { + disp: cube.offset.clone(), + rot: Quaternion::from_axis_angle(cube.offset.normalize(), Deg(cube.angle)), + scale: cube.scale, + }; + let uniform_buf = device.create_buffer(&wgpu::BufferDescriptor { + size: entity_uniform_size, + usage: wgpu::BufferUsageFlags::UNIFORM | wgpu::BufferUsageFlags::TRANSFER_DST, + }); + entities.push(Entity { + mx_world: cgmath::Matrix4::from(transform), + rotation_speed: cube.rotation, + color: wgpu::Color::GREEN, + vertex_buf: Rc::clone(&cube_vertex_buf), + index_buf: Rc::clone(&cube_index_buf), + index_count: cube_index_data.len(), + bind_group: device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &local_bind_group_layout, + bindings: &[wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::Buffer { + buffer: &uniform_buf, + range: 0..entity_uniform_size, + }, + }], + }), + uniform_buf, + }); + } + + // Create other resources + let shadow_sampler = device.create_sampler(&wgpu::SamplerDescriptor { + r_address_mode: wgpu::AddressMode::ClampToEdge, + s_address_mode: wgpu::AddressMode::ClampToEdge, + t_address_mode: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::FilterMode::Nearest, + lod_min_clamp: -100.0, + lod_max_clamp: 100.0, + max_anisotropy: 0, + compare_function: wgpu::CompareFunction::LessEqual, + border_color: wgpu::BorderColor::TransparentBlack, + }); + + let shadow_texture = device.create_texture(&wgpu::TextureDescriptor { + size: Self::SHADOW_SIZE, + array_size: Self::MAX_LIGHTS as u32, + dimension: wgpu::TextureDimension::D2, + format: Self::SHADOW_FORMAT, + usage: wgpu::TextureUsageFlags::OUTPUT_ATTACHMENT | wgpu::TextureUsageFlags::SAMPLED, + }); + let shadow_view = shadow_texture.create_default_view(); + + let mut shadow_target_views = (0..2) + .map(|i| { + Some(shadow_texture.create_view(&wgpu::TextureViewDescriptor { + format: Self::SHADOW_FORMAT, + dimension: wgpu::TextureViewDimension::D2, + aspect: wgpu::TextureAspectFlags::DEPTH, + base_mip_level: 0, + level_count: 1, + base_array_layer: i as u32, + array_count: 1, + })) + }) + .collect::>(); + let lights = vec![ + Light { + pos: cgmath::Point3::new(7.0, -5.0, 10.0), + color: wgpu::Color { + r: 0.5, + g: 1.0, + b: 0.5, + a: 1.0, + }, + fov: 60.0, + depth: 1.0..20.0, + target_view: shadow_target_views[0].take().unwrap(), + }, + Light { + pos: cgmath::Point3::new(-5.0, 7.0, 10.0), + color: wgpu::Color { + r: 1.0, + g: 0.5, + b: 0.5, + a: 1.0, + }, + fov: 45.0, + depth: 1.0..20.0, + target_view: shadow_target_views[1].take().unwrap(), + }, + ]; + let light_uniform_size = (Self::MAX_LIGHTS * mem::size_of::()) as u32; + let light_uniform_buf = device.create_buffer(&wgpu::BufferDescriptor { + size: light_uniform_size, + usage: wgpu::BufferUsageFlags::UNIFORM + | wgpu::BufferUsageFlags::TRANSFER_SRC + | wgpu::BufferUsageFlags::TRANSFER_DST, + }); + + let vb_desc = wgpu::VertexBufferDescriptor { + stride: vertex_size as u32, + step_mode: wgpu::InputStepMode::Vertex, + attributes: &[ + wgpu::VertexAttributeDescriptor { + attribute_index: 0, + format: wgpu::VertexFormat::Char4, + offset: 0, + }, + wgpu::VertexAttributeDescriptor { + attribute_index: 1, + format: wgpu::VertexFormat::Char4, + offset: 4 * 1, + }, + ], + }; + + let shadow_pass = { + // Create pipeline layout + let bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + bindings: &[wgpu::BindGroupLayoutBinding { + binding: 0, // global + visibility: wgpu::ShaderStageFlags::VERTEX, + ty: wgpu::BindingType::UniformBuffer, + }], + }); + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + bind_group_layouts: &[&bind_group_layout, &local_bind_group_layout], + }); + + let uniform_size = mem::size_of::() as u32; + let uniform_buf = device.create_buffer(&wgpu::BufferDescriptor { + size: uniform_size, + usage: wgpu::BufferUsageFlags::UNIFORM | wgpu::BufferUsageFlags::TRANSFER_DST, + }); + + // Create bind group + 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_size, + }, + }], + }); + + // Create the render pipeline + let vs_bytes = framework::load_glsl( + include_str!("bake.vert"), + framework::ShaderStage::Vertex, + ); + let fs_bytes = framework::load_glsl( + include_str!("bake.frag"), + framework::ShaderStage::Fragment, + ); + let vs_module = device.create_shader_module(&vs_bytes); + let fs_module = device.create_shader_module(&fs_bytes); + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + layout: &pipeline_layout, + vertex_stage: wgpu::PipelineStageDescriptor { + module: &vs_module, + entry_point: "main", + }, + fragment_stage: wgpu::PipelineStageDescriptor { + module: &fs_module, + entry_point: "main", + }, + rasterization_state: wgpu::RasterizationStateDescriptor { + front_face: wgpu::FrontFace::Cw, + cull_mode: wgpu::CullMode::Back, + depth_bias: 2, // corresponds to bilinear filtering + depth_bias_slope_scale: 2.0, + depth_bias_clamp: 0.0, + }, + primitive_topology: wgpu::PrimitiveTopology::TriangleList, + color_states: &[], + depth_stencil_state: Some(wgpu::DepthStencilStateDescriptor { + format: Self::SHADOW_FORMAT, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::LessEqual, + stencil_front: wgpu::StencilStateFaceDescriptor::IGNORE, + stencil_back: wgpu::StencilStateFaceDescriptor::IGNORE, + stencil_read_mask: 0, + stencil_write_mask: 0, + }), + index_format: wgpu::IndexFormat::Uint16, + vertex_buffers: &[vb_desc.clone()], + sample_count: 1, + }); + + Pass { + pipeline, + bind_group, + uniform_buf, + } + }; + + let forward_pass = { + // Create pipeline layout + let bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + bindings: &[ + wgpu::BindGroupLayoutBinding { + binding: 0, // global + visibility: wgpu::ShaderStageFlags::VERTEX + | wgpu::ShaderStageFlags::FRAGMENT, + ty: wgpu::BindingType::UniformBuffer, + }, + wgpu::BindGroupLayoutBinding { + binding: 1, // lights + visibility: wgpu::ShaderStageFlags::VERTEX + | wgpu::ShaderStageFlags::FRAGMENT, + ty: wgpu::BindingType::UniformBuffer, + }, + wgpu::BindGroupLayoutBinding { + binding: 2, + visibility: wgpu::ShaderStageFlags::FRAGMENT, + ty: wgpu::BindingType::SampledTexture, + }, + wgpu::BindGroupLayoutBinding { + binding: 3, + visibility: wgpu::ShaderStageFlags::FRAGMENT, + ty: wgpu::BindingType::Sampler, + }, + ], + }); + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + bind_group_layouts: &[&bind_group_layout, &local_bind_group_layout], + }); + + let mx_total = Self::generate_matrix(sc_desc.width as f32 / sc_desc.height as f32); + let forward_uniforms = ForwardUniforms { + proj: *mx_total.as_ref(), + num_lights: [lights.len() as u32, 0, 0, 0], + }; + let uniform_size = mem::size_of::() as u32; + let uniform_buf = device + .create_buffer_mapped( + 1, + wgpu::BufferUsageFlags::UNIFORM | wgpu::BufferUsageFlags::TRANSFER_DST, + ) + .fill_from_slice(&[forward_uniforms]); + + // Create bind group + 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_size, + }, + }, + wgpu::Binding { + binding: 1, + resource: wgpu::BindingResource::Buffer { + buffer: &light_uniform_buf, + range: 0..light_uniform_size, + }, + }, + wgpu::Binding { + binding: 2, + resource: wgpu::BindingResource::TextureView(&shadow_view), + }, + wgpu::Binding { + binding: 3, + resource: wgpu::BindingResource::Sampler(&shadow_sampler), + }, + ], + }); + + // Create the render pipeline + let vs_bytes = framework::load_glsl( + include_str!("forward.vert"), + framework::ShaderStage::Vertex, + ); + let fs_bytes = framework::load_glsl( + include_str!("forward.frag"), + framework::ShaderStage::Fragment, + ); + let vs_module = device.create_shader_module(&vs_bytes); + let fs_module = device.create_shader_module(&fs_bytes); + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + layout: &pipeline_layout, + vertex_stage: wgpu::PipelineStageDescriptor { + module: &vs_module, + entry_point: "main", + }, + fragment_stage: wgpu::PipelineStageDescriptor { + module: &fs_module, + entry_point: "main", + }, + rasterization_state: wgpu::RasterizationStateDescriptor { + front_face: wgpu::FrontFace::Cw, + cull_mode: wgpu::CullMode::Back, + 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: wgpu::BlendDescriptor::REPLACE, + alpha: wgpu::BlendDescriptor::REPLACE, + write_mask: wgpu::ColorWriteFlags::ALL, + }], + depth_stencil_state: Some(wgpu::DepthStencilStateDescriptor { + format: Self::DEPTH_FORMAT, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::Less, + stencil_front: wgpu::StencilStateFaceDescriptor::IGNORE, + stencil_back: wgpu::StencilStateFaceDescriptor::IGNORE, + stencil_read_mask: 0, + stencil_write_mask: 0, + }), + index_format: wgpu::IndexFormat::Uint16, + vertex_buffers: &[vb_desc], + sample_count: 1, + }); + + Pass { + pipeline, + bind_group, + uniform_buf, + } + }; + + let depth_texture = device.create_texture(&wgpu::TextureDescriptor { + size: wgpu::Extent3d { + width: sc_desc.width, + height: sc_desc.height, + depth: 1, + }, + array_size: 1, + dimension: wgpu::TextureDimension::D2, + format: Self::DEPTH_FORMAT, + usage: wgpu::TextureUsageFlags::OUTPUT_ATTACHMENT, + }); + + Example { + entities, + lights, + lights_are_dirty: true, + shadow_pass, + forward_pass, + forward_depth: depth_texture.create_default_view(), + light_uniform_buf, + } + } + + fn update(&mut self, _event: wgpu::winit::WindowEvent) { + //empty + } + + fn resize(&mut self, sc_desc: &wgpu::SwapChainDescriptor, device: &mut wgpu::Device) { + { + let mx_total = Self::generate_matrix(sc_desc.width as f32 / sc_desc.height as f32); + let mx_ref: &[f32; 16] = mx_total.as_ref(); + let temp_buf = device + .create_buffer_mapped(16, wgpu::BufferUsageFlags::TRANSFER_SRC) + .fill_from_slice(mx_ref); + + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { todo: 0 }); + encoder.copy_buffer_to_buffer(&temp_buf, 0, &self.forward_pass.uniform_buf, 0, 64); + device.get_queue().submit(&[encoder.finish()]); + } + + let depth_texture = device.create_texture(&wgpu::TextureDescriptor { + size: wgpu::Extent3d { + width: sc_desc.width, + height: sc_desc.height, + depth: 1, + }, + array_size: 1, + dimension: wgpu::TextureDimension::D2, + format: Self::DEPTH_FORMAT, + usage: wgpu::TextureUsageFlags::OUTPUT_ATTACHMENT, + }); + self.forward_depth = depth_texture.create_default_view(); + } + + fn render(&mut self, frame: &wgpu::SwapChainOutput, device: &mut wgpu::Device) { + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { todo: 0 }); + + { + let size = mem::size_of::() as u32; + let temp_buf_data = device + .create_buffer_mapped(self.entities.len(), wgpu::BufferUsageFlags::TRANSFER_SRC); + + for (i, entity) in self.entities.iter_mut().enumerate() { + if entity.rotation_speed != 0.0 { + let rotation = + cgmath::Matrix4::from_angle_x(cgmath::Deg(entity.rotation_speed)); + entity.mx_world = entity.mx_world * rotation; + } + temp_buf_data.data[i] = EntityUniforms { + model: entity.mx_world.clone(), + color: [ + entity.color.r, + entity.color.g, + entity.color.b, + entity.color.a, + ], + }; + } + + let temp_buf = temp_buf_data.finish(); + + for (i, entity) in self.entities.iter().enumerate() { + encoder.copy_buffer_to_buffer( + &temp_buf, + i as u32 * size, + &entity.uniform_buf, + 0, + size, + ); + } + } + + if self.lights_are_dirty { + self.lights_are_dirty = false; + let size = (self.lights.len() * mem::size_of::()) as u32; + let temp_buf_data = device + .create_buffer_mapped(self.lights.len(), wgpu::BufferUsageFlags::TRANSFER_SRC); + for (i, light) in self.lights.iter().enumerate() { + temp_buf_data.data[i] = light.to_raw(); + } + encoder.copy_buffer_to_buffer( + &temp_buf_data.finish(), + 0, + &self.light_uniform_buf, + 0, + size, + ); + } + + for (i, light) in self.lights.iter().enumerate() { + // The light uniform buffer already has the projection, + // let's just copy it over to the shadow uniform buffer. + encoder.copy_buffer_to_buffer( + &self.light_uniform_buf, + (i * mem::size_of::()) as u32, + &self.shadow_pass.uniform_buf, + 0, + 64, + ); + + let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachmentDescriptor { + attachment: &light.target_view, + depth_load_op: wgpu::LoadOp::Clear, + depth_store_op: wgpu::StoreOp::Store, + stencil_load_op: wgpu::LoadOp::Clear, + stencil_store_op: wgpu::StoreOp::Store, + clear_depth: 1.0, + clear_stencil: 0, + }), + }); + pass.set_pipeline(&self.shadow_pass.pipeline); + pass.set_bind_group(0, &self.shadow_pass.bind_group, &[]); + + for entity in &self.entities { + pass.set_bind_group(1, &entity.bind_group, &[]); + pass.set_index_buffer(&entity.index_buf, 0); + pass.set_vertex_buffers(&[(&entity.vertex_buf, 0)]); + pass.draw_indexed(0..entity.index_count as u32, 0, 0..1); + } + } + + // forward pass + { + let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { + attachment: &frame.view, + 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: Some(wgpu::RenderPassDepthStencilAttachmentDescriptor { + attachment: &self.forward_depth, + depth_load_op: wgpu::LoadOp::Clear, + depth_store_op: wgpu::StoreOp::Store, + stencil_load_op: wgpu::LoadOp::Clear, + stencil_store_op: wgpu::StoreOp::Store, + clear_depth: 1.0, + clear_stencil: 0, + }), + }); + pass.set_pipeline(&self.forward_pass.pipeline); + pass.set_bind_group(0, &self.forward_pass.bind_group, &[]); + + for entity in &self.entities { + pass.set_bind_group(1, &entity.bind_group, &[]); + pass.set_index_buffer(&entity.index_buf, 0); + pass.set_vertex_buffers(&[(&entity.vertex_buf, 0)]); + pass.draw_indexed(0..entity.index_count as u32, 0, 0..1); + } + } + + device.get_queue().submit(&[encoder.finish()]); + } +} + +fn main() { + framework::run::("shadow"); +} diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000000..3ef1a4ef2e --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,3 @@ +imports_layout = "HorizontalVertical" +newline_style = "Native" +spaces_around_ranges = true diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000000..61705e057e --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,965 @@ +use arrayvec::ArrayVec; + +use std::ffi::CString; +use std::ops::Range; +use std::ptr; +use std::slice; + +pub use wgn::winit; +pub use wgn::{ + AdapterDescriptor, + AddressMode, + BindGroupLayoutBinding, + BindingType, + BlendDescriptor, + BlendFactor, + BlendOperation, + BorderColor, + BufferDescriptor, + BufferMapAsyncStatus, + BufferUsageFlags, + Color, + ColorStateDescriptor, + ColorWriteFlags, + CommandEncoderDescriptor, + CompareFunction, + CullMode, + DepthStencilStateDescriptor, + DeviceDescriptor, + Extensions, + Extent3d, + FilterMode, + FrontFace, + IndexFormat, + InputStepMode, + LoadOp, + Origin3d, + PowerPreference, + PrimitiveTopology, + RasterizationStateDescriptor, + RenderPassColorAttachmentDescriptor, + RenderPassDepthStencilAttachmentDescriptor, + SamplerDescriptor, + ShaderAttributeIndex, + ShaderModuleDescriptor, + ShaderStageFlags, + StencilOperation, + StencilStateFaceDescriptor, + StoreOp, + SwapChainDescriptor, + TextureAspectFlags, + TextureDescriptor, + TextureDimension, + TextureFormat, + TextureUsageFlags, + TextureViewDescriptor, + TextureViewDimension, + VertexAttributeDescriptor, + VertexFormat, +}; + +//TODO: avoid heap allocating vectors during resource creation. +#[derive(Default)] +struct Temp { + //bind_group_descriptors: Vec, + //vertex_buffers: Vec, + command_buffers: Vec, +} + +pub struct Instance { + id: wgn::InstanceId, +} + +pub struct Adapter { + id: wgn::AdapterId, +} + +pub struct Device { + id: wgn::DeviceId, + temp: Temp, +} + +pub struct Buffer { + id: wgn::BufferId, +} + +pub struct Texture { + id: wgn::TextureId, + owned: bool, +} + +pub struct TextureView { + id: wgn::TextureViewId, + owned: bool, +} + +pub struct Sampler { + id: wgn::SamplerId, +} + +pub struct Surface { + id: wgn::SurfaceId, +} + +pub struct SwapChain { + id: wgn::SwapChainId, +} + +pub struct BindGroupLayout { + id: wgn::BindGroupLayoutId, +} + +pub struct BindGroup { + id: wgn::BindGroupId, +} + +impl Drop for BindGroup { + fn drop(&mut self) { + wgn::wgpu_bind_group_destroy(self.id); + } +} + +pub struct ShaderModule { + id: wgn::ShaderModuleId, +} + +pub struct PipelineLayout { + id: wgn::PipelineLayoutId, +} + +pub struct RenderPipeline { + id: wgn::RenderPipelineId, +} + +pub struct ComputePipeline { + id: wgn::ComputePipelineId, +} + +pub struct CommandBuffer { + id: wgn::CommandBufferId, +} + +pub struct CommandEncoder { + id: wgn::CommandEncoderId, +} + +pub struct RenderPass<'a> { + id: wgn::RenderPassId, + _parent: &'a mut CommandEncoder, +} + +pub struct ComputePass<'a> { + id: wgn::ComputePassId, + _parent: &'a mut CommandEncoder, +} + +pub struct Queue<'a> { + id: wgn::QueueId, + temp: &'a mut Temp, +} + +pub enum BindingResource<'a> { + Buffer { + buffer: &'a Buffer, + range: Range, + }, + Sampler(&'a Sampler), + TextureView(&'a TextureView), +} + +pub struct Binding<'a> { + pub binding: u32, + pub resource: BindingResource<'a>, +} + +pub struct BindGroupLayoutDescriptor<'a> { + pub bindings: &'a [BindGroupLayoutBinding], +} + +pub struct BindGroupDescriptor<'a> { + pub layout: &'a BindGroupLayout, + pub bindings: &'a [Binding<'a>], +} + +pub struct PipelineLayoutDescriptor<'a> { + pub bind_group_layouts: &'a [&'a BindGroupLayout], +} + +pub struct PipelineStageDescriptor<'a> { + pub module: &'a ShaderModule, + pub entry_point: &'a str, +} + +#[derive(Clone, Debug)] +pub struct VertexBufferDescriptor<'a> { + pub stride: u32, + pub step_mode: InputStepMode, + pub attributes: &'a [VertexAttributeDescriptor], +} + +pub struct RenderPipelineDescriptor<'a> { + pub layout: &'a PipelineLayout, + pub vertex_stage: PipelineStageDescriptor<'a>, + pub fragment_stage: PipelineStageDescriptor<'a>, + pub rasterization_state: RasterizationStateDescriptor, + pub primitive_topology: PrimitiveTopology, + pub color_states: &'a [ColorStateDescriptor], + pub depth_stencil_state: Option, + pub index_format: IndexFormat, + pub vertex_buffers: &'a [VertexBufferDescriptor<'a>], + pub sample_count: u32, +} + +pub struct ComputePipelineDescriptor<'a> { + pub layout: &'a PipelineLayout, + pub compute_stage: PipelineStageDescriptor<'a>, +} + +pub struct RenderPassDescriptor<'a> { + pub color_attachments: &'a [RenderPassColorAttachmentDescriptor<&'a TextureView>], + pub depth_stencil_attachment: + Option>, +} + +pub struct SwapChainOutput<'a> { + pub texture: Texture, + pub view: TextureView, + swap_chain_id: &'a wgn::SwapChainId, +} + +pub struct BufferCopyView<'a> { + pub buffer: &'a Buffer, + pub offset: u32, + pub row_pitch: u32, + pub image_height: u32, +} + +impl<'a> BufferCopyView<'a> { + fn into_native(self) -> wgn::BufferCopyView { + wgn::BufferCopyView { + buffer: self.buffer.id, + offset: self.offset, + row_pitch: self.row_pitch, + image_height: self.image_height, + } + } +} + +pub struct TextureCopyView<'a> { + pub texture: &'a Texture, + pub level: u32, + pub slice: u32, + pub origin: Origin3d, +} + +impl<'a> TextureCopyView<'a> { + fn into_native(self) -> wgn::TextureCopyView { + wgn::TextureCopyView { + texture: self.texture.id, + level: self.level, + slice: self.slice, + origin: self.origin, + } + } +} + +pub struct CreateBufferMapped<'a, T> { + id: wgn::BufferId, + pub data: &'a mut [T], +} + +impl<'a, T> CreateBufferMapped<'a, T> +where + T: Copy, +{ + pub fn fill_from_slice(self, slice: &[T]) -> Buffer { + self.data.copy_from_slice(slice); + self.finish() + } + + pub fn finish(self) -> Buffer { + wgn::wgpu_buffer_unmap(self.id); + Buffer { id: self.id } + } +} + +impl Instance { + pub fn new() -> Self { + Instance { + id: wgn::wgpu_create_instance(), + } + } + + pub fn get_adapter(&self, desc: &AdapterDescriptor) -> Adapter { + Adapter { + id: wgn::wgpu_instance_get_adapter(self.id, desc), + } + } + + pub fn create_surface(&self, window: &winit::Window) -> Surface { + Surface { + id: wgn::wgpu_instance_create_surface_from_winit(self.id, window), + } + } + + #[cfg(feature = "metal")] + pub fn create_surface_with_metal_layer(&self, window: *mut std::ffi::c_void) -> Surface { + Surface { + id: wgn::wgpu_instance_create_surface_from_macos_layer(self.id, window), + } + } +} + +impl Adapter { + pub fn create_device(&self, desc: &DeviceDescriptor) -> Device { + Device { + id: wgn::wgpu_adapter_create_device(self.id, desc), + temp: Temp::default(), + } + } +} + +impl Device { + /// Check for resource cleanups and mapping callbacks. + pub fn poll(&self, force_wait: bool) { + wgn::wgpu_device_poll(self.id, force_wait); + } + + pub fn create_shader_module(&self, spv: &[u8]) -> ShaderModule { + let desc = wgn::ShaderModuleDescriptor { + code: wgn::ByteArray { + bytes: spv.as_ptr(), + length: spv.len(), + }, + }; + ShaderModule { + id: wgn::wgpu_device_create_shader_module(self.id, &desc), + } + } + + pub fn get_queue(&mut self) -> Queue { + Queue { + id: wgn::wgpu_device_get_queue(self.id), + temp: &mut self.temp, + } + } + + pub fn create_command_encoder(&self, desc: &CommandEncoderDescriptor) -> CommandEncoder { + CommandEncoder { + id: wgn::wgpu_device_create_command_encoder(self.id, desc), + } + } + + pub fn create_bind_group(&self, desc: &BindGroupDescriptor) -> BindGroup { + let bindings = desc + .bindings + .into_iter() + .map(|binding| wgn::Binding { + binding: binding.binding, + resource: match binding.resource { + BindingResource::Buffer { + ref buffer, + ref range, + } => wgn::BindingResource::Buffer(wgn::BufferBinding { + buffer: buffer.id, + offset: range.start, + size: range.end - range.start, + }), + BindingResource::Sampler(ref sampler) => { + wgn::BindingResource::Sampler(sampler.id) + } + BindingResource::TextureView(ref texture_view) => { + wgn::BindingResource::TextureView(texture_view.id) + } + }, + }) + .collect::>(); + BindGroup { + id: wgn::wgpu_device_create_bind_group( + self.id, + &wgn::BindGroupDescriptor { + layout: desc.layout.id, + bindings: bindings.as_ptr(), + bindings_length: bindings.len(), + }, + ), + } + } + + pub fn create_bind_group_layout(&self, desc: &BindGroupLayoutDescriptor) -> BindGroupLayout { + BindGroupLayout { + id: wgn::wgpu_device_create_bind_group_layout( + self.id, + &wgn::BindGroupLayoutDescriptor { + bindings: desc.bindings.as_ptr(), + bindings_length: desc.bindings.len(), + }, + ), + } + } + + pub fn create_pipeline_layout(&self, desc: &PipelineLayoutDescriptor) -> PipelineLayout { + //TODO: avoid allocation here + let temp_layouts = desc + .bind_group_layouts + .iter() + .map(|bgl| bgl.id) + .collect::>(); + PipelineLayout { + id: wgn::wgpu_device_create_pipeline_layout( + self.id, + &wgn::PipelineLayoutDescriptor { + bind_group_layouts: temp_layouts.as_ptr(), + bind_group_layouts_length: temp_layouts.len(), + }, + ), + } + } + + pub fn create_render_pipeline(&self, desc: &RenderPipelineDescriptor) -> RenderPipeline { + let vertex_entry_point = CString::new(desc.vertex_stage.entry_point).unwrap(); + let fragment_entry_point = CString::new(desc.fragment_stage.entry_point).unwrap(); + + let temp_color_states = desc.color_states.to_vec(); + let temp_vertex_buffers = desc + .vertex_buffers + .iter() + .map(|vbuf| wgn::VertexBufferDescriptor { + stride: vbuf.stride, + step_mode: vbuf.step_mode, + attributes: vbuf.attributes.as_ptr(), + attributes_count: vbuf.attributes.len(), + }) + .collect::>(); + + RenderPipeline { + id: wgn::wgpu_device_create_render_pipeline( + self.id, + &wgn::RenderPipelineDescriptor { + layout: desc.layout.id, + vertex_stage: wgn::PipelineStageDescriptor { + module: desc.vertex_stage.module.id, + entry_point: vertex_entry_point.as_ptr(), + }, + fragment_stage: wgn::PipelineStageDescriptor { + module: desc.fragment_stage.module.id, + entry_point: fragment_entry_point.as_ptr(), + }, + rasterization_state: desc.rasterization_state.clone(), + primitive_topology: desc.primitive_topology, + color_states: temp_color_states.as_ptr(), + color_states_length: temp_color_states.len(), + depth_stencil_state: desc + .depth_stencil_state + .as_ref() + .map_or(ptr::null(), |p| p as *const _), + vertex_buffer_state: wgn::VertexBufferStateDescriptor { + index_format: desc.index_format, + vertex_buffers: temp_vertex_buffers.as_ptr(), + vertex_buffers_count: temp_vertex_buffers.len(), + }, + sample_count: desc.sample_count, + }, + ), + } + } + + pub fn create_compute_pipeline(&self, desc: &ComputePipelineDescriptor) -> ComputePipeline { + let entry_point = CString::new(desc.compute_stage.entry_point).unwrap(); + + ComputePipeline { + id: wgn::wgpu_device_create_compute_pipeline( + self.id, + &wgn::ComputePipelineDescriptor { + layout: desc.layout.id, + compute_stage: wgn::PipelineStageDescriptor { + module: desc.compute_stage.module.id, + entry_point: entry_point.as_ptr(), + }, + }, + ), + } + } + + pub fn create_buffer(&self, desc: &BufferDescriptor) -> Buffer { + Buffer { + id: wgn::wgpu_device_create_buffer(self.id, desc), + } + } + + pub fn create_buffer_mapped<'a, T>( + &self, + count: usize, + usage: BufferUsageFlags, + ) -> CreateBufferMapped<'a, T> + where + T: 'static + Copy, + { + let type_size = std::mem::size_of::() as u32; + assert_ne!(type_size, 0); + + let desc = BufferDescriptor { + size: (type_size * count as u32).max(1), + usage, + }; + let mut ptr: *mut u8 = std::ptr::null_mut(); + + let id = wgn::wgpu_device_create_buffer_mapped(self.id, &desc, &mut ptr as *mut *mut u8); + + let data = unsafe { std::slice::from_raw_parts_mut(ptr as *mut T, count) }; + + CreateBufferMapped { id, data } + } + + pub fn create_texture(&self, desc: &TextureDescriptor) -> Texture { + Texture { + id: wgn::wgpu_device_create_texture(self.id, desc), + owned: true, + } + } + + pub fn create_sampler(&self, desc: &SamplerDescriptor) -> Sampler { + Sampler { + id: wgn::wgpu_device_create_sampler(self.id, desc), + } + } + + pub fn create_swap_chain(&self, surface: &Surface, desc: &SwapChainDescriptor) -> SwapChain { + SwapChain { + id: wgn::wgpu_device_create_swap_chain(self.id, surface.id, desc), + } + } +} + +impl Drop for Device { + fn drop(&mut self) { + wgn::wgpu_device_poll(self.id, true); + //TODO: make this work in general + #[cfg(feature = "metal-auto-capture")] + wgn::wgpu_device_destroy(self.id); + } +} + +pub struct BufferAsyncMapping { + pub data: T, + buffer_id: wgn::BufferId, +} +//TODO: proper error type +pub type BufferMapAsyncResult = Result, ()>; + +impl Drop for BufferAsyncMapping { + fn drop(&mut self) { + wgn::wgpu_buffer_unmap(self.buffer_id); + } +} + +struct BufferMapReadAsyncUserData +where + F: FnOnce(BufferMapAsyncResult<&[T]>), +{ + size: u32, + callback: F, + buffer_id: wgn::BufferId, + phantom: std::marker::PhantomData, +} + +struct BufferMapWriteAsyncUserData +where + F: FnOnce(BufferMapAsyncResult<&mut [T]>), +{ + size: u32, + callback: F, + buffer_id: wgn::BufferId, + phantom: std::marker::PhantomData, +} + +impl Buffer { + pub fn map_read_async(&self, start: u32, size: u32, callback: F) + where + T: 'static + Copy, + F: FnOnce(BufferMapAsyncResult<&[T]>) + 'static, + { + let type_size = std::mem::size_of::() as u32; + assert_ne!(type_size, 0); + assert_eq!(size % type_size, 0); + + extern "C" fn buffer_map_read_callback_wrapper( + status: wgn::BufferMapAsyncStatus, + data: *const u8, + user_data: *mut u8, + ) where + F: FnOnce(BufferMapAsyncResult<&[T]>), + { + let user_data = + unsafe { Box::from_raw(user_data as *mut BufferMapReadAsyncUserData) }; + let data = unsafe { + slice::from_raw_parts( + data as *const T, + user_data.size as usize / std::mem::size_of::(), + ) + }; + if let wgn::BufferMapAsyncStatus::Success = status { + (user_data.callback)(Ok(BufferAsyncMapping { + data, + buffer_id: user_data.buffer_id, + })); + } else { + (user_data.callback)(Err(())) + } + } + + let user_data = Box::new(BufferMapReadAsyncUserData { + size, + callback, + buffer_id: self.id, + phantom: std::marker::PhantomData, + }); + wgn::wgpu_buffer_map_read_async( + self.id, + start, + size, + buffer_map_read_callback_wrapper::, + Box::into_raw(user_data) as *mut u8, + ); + } + + pub fn map_write_async(&self, start: u32, size: u32, callback: F) + where + T: 'static + Copy, + F: FnOnce(BufferMapAsyncResult<&mut [T]>) + 'static, + { + let type_size = std::mem::size_of::() as u32; + assert_ne!(type_size, 0); + assert_eq!(size % type_size, 0); + + extern "C" fn buffer_map_write_callback_wrapper( + status: wgn::BufferMapAsyncStatus, + data: *mut u8, + user_data: *mut u8, + ) where + F: FnOnce(BufferMapAsyncResult<&mut [T]>), + { + let user_data = + unsafe { Box::from_raw(user_data as *mut BufferMapWriteAsyncUserData) }; + let data = unsafe { + slice::from_raw_parts_mut( + data as *mut T, + user_data.size as usize / std::mem::size_of::(), + ) + }; + if let wgn::BufferMapAsyncStatus::Success = status { + (user_data.callback)(Ok(BufferAsyncMapping { + data, + buffer_id: user_data.buffer_id, + })); + } else { + (user_data.callback)(Err(())) + } + } + + let user_data = Box::new(BufferMapWriteAsyncUserData { + size, + callback, + buffer_id: self.id, + phantom: std::marker::PhantomData, + }); + wgn::wgpu_buffer_map_write_async( + self.id, + start, + size, + buffer_map_write_callback_wrapper::, + Box::into_raw(user_data) as *mut u8, + ); + } + + pub fn unmap(&self) { + wgn::wgpu_buffer_unmap(self.id); + } +} + +impl Drop for Buffer { + fn drop(&mut self) { + wgn::wgpu_buffer_destroy(self.id); + } +} + +impl Texture { + pub fn create_view(&self, desc: &TextureViewDescriptor) -> TextureView { + TextureView { + id: wgn::wgpu_texture_create_view(self.id, desc), + owned: true, + } + } + + pub fn create_default_view(&self) -> TextureView { + TextureView { + id: wgn::wgpu_texture_create_default_view(self.id), + owned: true, + } + } +} + +impl Drop for Texture { + fn drop(&mut self) { + if self.owned { + wgn::wgpu_texture_destroy(self.id); + } + } +} + +impl Drop for TextureView { + fn drop(&mut self) { + if self.owned { + wgn::wgpu_texture_view_destroy(self.id); + } + } +} + +impl CommandEncoder { + pub fn finish(self) -> CommandBuffer { + CommandBuffer { + id: wgn::wgpu_command_encoder_finish(self.id), + } + } + + pub fn begin_render_pass(&mut self, desc: &RenderPassDescriptor) -> RenderPass { + let colors = desc + .color_attachments + .iter() + .map(|ca| RenderPassColorAttachmentDescriptor { + attachment: ca.attachment.id, + load_op: ca.load_op, + store_op: ca.store_op, + clear_color: ca.clear_color, + }) + .collect::>(); + + let depth_stencil = desc.depth_stencil_attachment.as_ref().map(|dsa| { + RenderPassDepthStencilAttachmentDescriptor { + attachment: dsa.attachment.id, + depth_load_op: dsa.depth_load_op, + depth_store_op: dsa.depth_store_op, + clear_depth: dsa.clear_depth, + stencil_load_op: dsa.stencil_load_op, + stencil_store_op: dsa.stencil_store_op, + clear_stencil: dsa.clear_stencil, + } + }); + + RenderPass { + id: wgn::wgpu_command_encoder_begin_render_pass( + self.id, + wgn::RenderPassDescriptor { + color_attachments: colors.as_ptr(), + color_attachments_length: colors.len(), + depth_stencil_attachment: depth_stencil + .as_ref() + .map(|at| at as *const _) + .unwrap_or(ptr::null()), + }, + ), + _parent: self, + } + } + + pub fn begin_compute_pass(&mut self) -> ComputePass { + ComputePass { + id: wgn::wgpu_command_encoder_begin_compute_pass(self.id), + _parent: self, + } + } + + pub fn copy_buffer_to_buffer( + &mut self, + source: &Buffer, + source_offset: u32, + destination: &Buffer, + destination_offset: u32, + copy_size: u32, + ) { + wgn::wgpu_command_buffer_copy_buffer_to_buffer( + self.id, + source.id, + source_offset, + destination.id, + destination_offset, + copy_size, + ); + } + + pub fn copy_buffer_to_texture( + &mut self, + source: BufferCopyView, + destination: TextureCopyView, + copy_size: Extent3d, + ) { + wgn::wgpu_command_buffer_copy_buffer_to_texture( + self.id, + &source.into_native(), + &destination.into_native(), + copy_size, + ); + } + + pub fn copy_texture_to_buffer( + &mut self, + source: TextureCopyView, + destination: BufferCopyView, + copy_size: Extent3d, + ) { + wgn::wgpu_command_buffer_copy_texture_to_buffer( + self.id, + &source.into_native(), + &destination.into_native(), + copy_size, + ); + } + + pub fn copy_texture_to_texture( + &mut self, + source: TextureCopyView, + destination: TextureCopyView, + copy_size: Extent3d, + ) { + wgn::wgpu_command_buffer_copy_texture_to_texture( + self.id, + &source.into_native(), + &destination.into_native(), + copy_size, + ); + } +} + +impl<'a> RenderPass<'a> { + pub fn set_bind_group(&mut self, index: u32, bind_group: &BindGroup, offsets: &[u32]) { + wgn::wgpu_render_pass_set_bind_group( + self.id, + index, + bind_group.id, + offsets.as_ptr(), + offsets.len(), + ); + } + + pub fn set_pipeline(&mut self, pipeline: &RenderPipeline) { + wgn::wgpu_render_pass_set_pipeline(self.id, pipeline.id); + } + + pub fn set_blend_color(&mut self, color: Color) { + wgn::wgpu_render_pass_set_blend_color(self.id, &color); + } + + pub fn set_index_buffer(&mut self, buffer: &Buffer, offset: u32) { + wgn::wgpu_render_pass_set_index_buffer(self.id, buffer.id, offset); + } + + pub fn set_vertex_buffers(&mut self, buffer_pairs: &[(&Buffer, u32)]) { + let mut buffers = Vec::new(); + let mut offsets = Vec::new(); + for &(buffer, offset) in buffer_pairs { + buffers.push(buffer.id); + offsets.push(offset); + } + wgn::wgpu_render_pass_set_vertex_buffers( + self.id, + buffers.as_ptr(), + offsets.as_ptr(), + buffer_pairs.len(), + ); + } + + pub fn set_scissor_rect(&mut self, x: u32, y: u32, w: u32, h: u32) { + wgn::wgpu_render_pass_set_scissor_rect(self.id, x, y, w, h) + } + + pub fn draw(&mut self, vertices: Range, instances: Range) { + wgn::wgpu_render_pass_draw( + self.id, + vertices.end - vertices.start, + instances.end - instances.start, + vertices.start, + instances.start, + ); + } + + pub fn draw_indexed(&mut self, indices: Range, base_vertex: i32, instances: Range) { + wgn::wgpu_render_pass_draw_indexed( + self.id, + indices.end - indices.start, + instances.end - instances.start, + indices.start, + base_vertex, + instances.start, + ); + } +} + +impl<'a> Drop for RenderPass<'a> { + fn drop(&mut self) { + wgn::wgpu_render_pass_end_pass(self.id); + } +} + +impl<'a> ComputePass<'a> { + pub fn set_bind_group(&mut self, index: u32, bind_group: &BindGroup, offsets: &[u32]) { + wgn::wgpu_compute_pass_set_bind_group( + self.id, + index, + bind_group.id, + offsets.as_ptr(), + offsets.len(), + ); + } + + pub fn set_pipeline(&mut self, pipeline: &ComputePipeline) { + wgn::wgpu_compute_pass_set_pipeline(self.id, pipeline.id); + } + + pub fn dispatch(&mut self, x: u32, y: u32, z: u32) { + wgn::wgpu_compute_pass_dispatch(self.id, x, y, z); + } +} + +impl<'a> Drop for ComputePass<'a> { + fn drop(&mut self) { + wgn::wgpu_compute_pass_end_pass(self.id); + } +} + +impl<'a> Queue<'a> { + pub fn submit(&mut self, command_buffers: &[CommandBuffer]) { + self.temp.command_buffers.clear(); + self.temp + .command_buffers + .extend(command_buffers.iter().map(|cb| cb.id)); + + wgn::wgpu_queue_submit( + self.id, + self.temp.command_buffers.as_ptr(), + command_buffers.len(), + ); + } +} + +impl<'a> Drop for SwapChainOutput<'a> { + fn drop(&mut self) { + wgn::wgpu_swap_chain_present(*self.swap_chain_id); + } +} + +impl SwapChain { + pub fn get_next_texture(&mut self) -> SwapChainOutput { + let output = wgn::wgpu_swap_chain_get_next_texture(self.id); + SwapChainOutput { + texture: Texture { + id: output.texture_id, + owned: false, + }, + view: TextureView { + id: output.view_id, + owned: false, + }, + swap_chain_id: &self.id, + } + } +} diff --git a/tests/multithreaded_compute.rs b/tests/multithreaded_compute.rs new file mode 100644 index 0000000000..3b943346e8 --- /dev/null +++ b/tests/multithreaded_compute.rs @@ -0,0 +1,100 @@ +#[test] +#[cfg(any(feature = "vulkan", feature = "metal", feature = "dx12"))] +fn multithreaded_compute() { + use std::thread; + use std::time::Duration; + use std::sync::mpsc; + + let thread_count = 8; + + let (tx, rx) = mpsc::channel(); + for _ in 0..thread_count { + let tx = tx.clone(); + thread::spawn(move || { + let numbers = vec!(100, 100, 100); + + let size = (numbers.len() * std::mem::size_of::()) as u32; + + let instance = wgpu::Instance::new(); + let adapter = instance.get_adapter(&wgpu::AdapterDescriptor { + power_preference: wgpu::PowerPreference::Default, + }); + let mut device = adapter.create_device(&wgpu::DeviceDescriptor { + extensions: wgpu::Extensions { + anisotropic_filtering: false, + }, + }); + + let cs_bytes = include_bytes!("../examples/hello_compute/shader.comp.spv"); + let cs_module = device.create_shader_module(cs_bytes); + + let staging_buffer = device + .create_buffer_mapped( + numbers.len(), + wgpu::BufferUsageFlags::MAP_READ + | wgpu::BufferUsageFlags::TRANSFER_DST + | wgpu::BufferUsageFlags::TRANSFER_SRC, + ) + .fill_from_slice(&numbers); + + let storage_buffer = device.create_buffer(&wgpu::BufferDescriptor { + size, + usage: wgpu::BufferUsageFlags::STORAGE + | wgpu::BufferUsageFlags::TRANSFER_DST + | wgpu::BufferUsageFlags::TRANSFER_SRC, + }); + + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + bindings: &[wgpu::BindGroupLayoutBinding { + binding: 0, + visibility: wgpu::ShaderStageFlags::COMPUTE, + ty: wgpu::BindingType::StorageBuffer, + }], + }); + + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &bind_group_layout, + bindings: &[wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::Buffer { + buffer: &storage_buffer, + range: 0..size, + }, + }], + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + bind_group_layouts: &[&bind_group_layout], + }); + + let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + layout: &pipeline_layout, + compute_stage: wgpu::PipelineStageDescriptor { + module: &cs_module, + entry_point: "main", + }, + }); + + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { todo: 0 }); + encoder.copy_buffer_to_buffer(&staging_buffer, 0, &storage_buffer, 0, size); + { + let mut cpass = encoder.begin_compute_pass(); + cpass.set_pipeline(&compute_pipeline); + cpass.set_bind_group(0, &bind_group, &[]); + cpass.dispatch(numbers.len() as u32, 1, 1); + } + encoder.copy_buffer_to_buffer(&storage_buffer, 0, &staging_buffer, 0, size); + + device.get_queue().submit(&[encoder.finish()]); + + staging_buffer.map_read_async(0, size, |result: wgpu::BufferMapAsyncResult<&[u32]>| { + assert_eq!(result.unwrap().data, [25, 25, 25]); + }); + tx.send(true).unwrap(); + }); + } + + for _ in 0..thread_count { + rx.recv_timeout(Duration::from_secs(10)).expect("A thread never completed."); + } +}