//! Cyborg is a high-performance, modern, experimental rendering engine written //! in Rust. //! //! # Features //! //! - `legion`: Enables [Legion](https://github.com/amethyst/legion) integration //! for Cyborg. Disabled by default. use parking_lot::Mutex; use rayon::prelude::*; use std::sync::Arc; pub mod camera; pub mod pass; pub mod phase; pub mod scene; pub mod shader; pub mod staging; pub mod storage; pub mod viewport; #[cfg(feature = "legion")] pub mod legion; use camera::Camera; use pass::*; use phase::*; use viewport::Viewport; #[repr(C)] #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] pub struct ViewportUniform { pub eye: [f32; 4], pub vp: [[f32; 4]; 4], } impl ViewportUniform { pub fn from_camera(camera: &Camera) -> Self { Self { eye: camera.eye, vp: camera.vp, } } } impl ViewportUniform { pub fn binding_size() -> wgpu::BufferSize { let size = std::mem::size_of::() as u64; size.try_into().unwrap() } } pub struct RenderLayouts { pub device: Arc, pub bind_viewport: wgpu::BindGroupLayout, } impl RenderLayouts { pub fn new_arc(device: Arc) -> Arc { let bind_viewport = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("viewport"), entries: &[wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: Some(ViewportUniform::binding_size()), }, count: None, }], }); Arc::new(Self { device, bind_viewport, }) } } pub struct FrameData { pub frame_index: usize, pub bind_viewport: wgpu::BindGroup, pub viewport_uniform: wgpu::Buffer, } impl FrameData { pub fn new(frame_index: usize, layouts: &RenderLayouts) -> Self { let viewport_uniform = layouts.device.create_buffer(&wgpu::BufferDescriptor { label: Some("viewport"), size: ViewportUniform::binding_size().into(), usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); let bind_viewport = layouts .device .create_bind_group(&wgpu::BindGroupDescriptor { label: Some("viewport"), layout: &layouts.bind_viewport, entries: &[wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { buffer: &viewport_uniform, offset: 0, size: None, }), }], }); Self { frame_index, bind_viewport, viewport_uniform, } } pub fn make_phase_data<'a>( &'a self, phase: Phase, viewport_data: &'a ViewportData, ) -> IndexedPhaseData<'a> { IndexedPhaseData { phase, frame_data: self.frame_index, viewport_data, bind_viewport: &self.bind_viewport, } } } pub struct Renderer { device: Arc, queue: Arc, layouts: Arc, frames_in_flight: usize, frame_datas: Vec, frame_index: usize, render_passes: Vec>, } impl Renderer { pub fn new(device: Arc, queue: Arc) -> Self { let layouts = RenderLayouts::new_arc(device.clone()); let frames_in_flight = 1; let mut frame_datas = Vec::with_capacity(frames_in_flight); for frame_index in 0..frames_in_flight { frame_datas.push(FrameData::new(frame_index, &layouts)); } Self { device, layouts, queue, frames_in_flight, frame_datas, frame_index: 0, render_passes: Vec::new(), } } pub fn get_device(&self) -> &Arc { &self.device } pub fn get_layouts(&self) -> &Arc { &self.layouts } pub fn get_frames_in_flight(&self) -> usize { self.frames_in_flight } pub fn render<'a>( &mut self, passes: &mut [&mut dyn RenderPassBoxTrait], target: &dyn Viewport, camera: &Camera, ) { puffin::profile_function!(); self.frame_index += 1; if self.frame_index >= self.frame_datas.len() { self.frame_index = 0; } let frame_index = self.frame_index; let frame_data = &self.frame_datas[frame_index]; let viewport_uniform = ViewportUniform::from_camera(camera); self.queue.write_buffer( &frame_data.viewport_uniform, 0, bytemuck::cast_slice(&[viewport_uniform]), ); let phase_passes = multimap::MultiMap::::new(); let phase_passes = std::sync::Mutex::new(phase_passes); passes .par_iter_mut() .enumerate() .for_each(|(pass_index, rp)| { let mut phases_buf = Vec::new(); phases_buf.clear(); rp.begin_frame(frame_index, &mut phases_buf, &self.queue); let mut passes = phase_passes.lock().unwrap(); for phase in phases_buf.into_iter() { passes.insert(phase, pass_index); } }); let phase_passes = phase_passes.into_inner().unwrap(); let viewport = ViewportData; let mut encoder = self .device .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Render Encoder"), }); if let Some(upload) = phase_passes.get_vec(&Phase::Upload) { upload.iter().for_each(|pass_index| { let phase_data = frame_data.make_phase_data(Phase::Upload, &viewport); let pass = &passes[*pass_index]; pass.record_commands(phase_data, &mut encoder); }); } if let Some(skinning) = phase_passes.get_vec(&Phase::Skinning) { let mut cmds = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: Some("Skinning Phase"), }); skinning.iter().for_each(|pass_index| { let phase_data = frame_data.make_phase_data(Phase::Skinning, &viewport); let pass = &passes[*pass_index]; pass.record_compute(phase_data, &mut cmds); }) } let record_render = |phase| { let cmds = Mutex::new(Vec::new()); if let Some(phase_passes) = phase_passes.get_vec(&phase) { phase_passes.par_iter().for_each(|pass_index| { let phase_data = frame_data.make_phase_data(phase, &viewport); let pass = &passes[*pass_index]; if let Some(cmd) = pass.record_render(phase_data) { cmds.lock().push(cmd); } }) } cmds.into_inner() }; // TODO: parallelize each phase's record_render let depth_cmds = record_render(Phase::Depth); let opaque_cmds = record_render(Phase::Opaque); let overlay_cmds = record_render(Phase::Overlay); { let target_views = target.get_views(); let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("Render Pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: target_views.output, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), store: true, }, })], depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { view: target_views.depth, depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(1.0), store: true, }), stencil_ops: None, }), }); rp.execute_bundles(depth_cmds.iter()); rp.execute_bundles(opaque_cmds.iter()); rp.execute_bundles(overlay_cmds.iter()); } self.queue.submit(std::iter::once(encoder.finish())); } }