diff --git a/Cargo.lock b/Cargo.lock index 9914fc9..0cb6618 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -598,6 +598,7 @@ dependencies = [ "egui-winit", "futures-intrusive", "image", + "naga 0.14.0", "web-sys", "wgpu", "wgpu-types", @@ -1827,6 +1828,25 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "naga" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61d829abac9f5230a85d8cc83ec0879b4c09790208ae25b5ea031ef84562e071" +dependencies = [ + "bit-set", + "bitflags 2.4.0", + "codespan-reporting", + "hexf-parse", + "indexmap 2.0.0", + "log", + "num-traits", + "rustc-hash", + "termcolor", + "thiserror", + "unicode-xid", +] + [[package]] name = "ndk" version = "0.7.0" @@ -3094,7 +3114,7 @@ dependencies = [ "cfg-if", "js-sys", "log", - "naga", + "naga 0.13.0", "parking_lot", "profiling", "raw-window-handle", @@ -3119,7 +3139,7 @@ dependencies = [ "bitflags 2.4.0", "codespan-reporting", "log", - "naga", + "naga 0.13.0", "parking_lot", "profiling", "raw-window-handle", @@ -3156,7 +3176,7 @@ dependencies = [ "libloading 0.8.0", "log", "metal", - "naga", + "naga 0.13.0", "objc", "parking_lot", "profiling", diff --git a/comfy-wgpu/Cargo.toml b/comfy-wgpu/Cargo.toml index c709da5..476e602 100644 --- a/comfy-wgpu/Cargo.toml +++ b/comfy-wgpu/Cargo.toml @@ -22,6 +22,7 @@ comfy-core = { path = "../comfy-core", version = "0.2.0" } wgpu = { version = "0.17.1", features = ["expose-ids"] } wgpu-types = "0.17.0" winit = { version = "0.28.3", default-features = false, features = ["x11"] } +naga = { version = "0.14.0", features = ["wgsl-in"] } egui = "0.23.0" egui-wgpu = "0.23.0" diff --git a/comfy-wgpu/shaders/error.wgsl b/comfy-wgpu/shaders/error.wgsl new file mode 100644 index 0000000..ccf3e7f --- /dev/null +++ b/comfy-wgpu/shaders/error.wgsl @@ -0,0 +1,14 @@ + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + let tex = textureSample(t_diffuse, s_diffuse, in.tex_coords); + + var final_color: vec4 = vec4( + 1.0, + in.tex_coords.x, + in.tex_coords.y, + 1.0 + ); + + return final_color; +} diff --git a/comfy-wgpu/src/batching.rs b/comfy-wgpu/src/batching.rs index d3b76ab..03f2b74 100644 --- a/comfy-wgpu/src/batching.rs +++ b/comfy-wgpu/src/batching.rs @@ -5,6 +5,7 @@ pub fn run_batched_render_passes( surface_view: &wgpu::TextureView, params: &DrawParams, sprite_shader_id: ShaderId, + error_shader_id: ShaderId, ) { let render_passes = collect_render_passes(params); @@ -82,6 +83,7 @@ pub fn run_batched_render_passes( }, surface_view, sprite_shader_id, + error_shader_id, ); perf_counter_inc("real_mesh_draw", 1); @@ -121,187 +123,180 @@ pub fn render_meshes( pass_data: MeshDrawData, surface_view: &wgpu::TextureView, sprite_shader_id: ShaderId, + _error_shader_id: ShaderId, ) { let _span = span!("render_meshes"); - let target_view = - if c.post_processing_effects.borrow().iter().any(|x| x.enabled) { - &c.first_pass_texture.view - } else { - surface_view - }; - - let textures = c.textures.lock(); - let _span = span!("blend_mode"); + let shaders = c.shaders.borrow(); + let maybe_shader_instance = pass_data.data.get(0).and_then(|x| x.shader.clone()); - let shaders = c.shaders.borrow(); let maybe_shader = maybe_shader_instance .as_ref() .and_then(|instance| shaders.get(instance.id)); - let mesh_pipeline = { - let name = format!( - "{} {:?} {:?} {:?}", - if maybe_shader_instance.is_some() { - "USER(Mesh)" - } else { - "BUILTIN(Mesh)" - }, - pass_data.blend_mode, - maybe_shader, - c.enable_z_buffer - ); + let name = format!( + "{} {:?} {:?} {:?}", + if maybe_shader_instance.is_some() { + "USER(Mesh)" + } else { + "BUILTIN(Mesh)" + }, + pass_data.blend_mode, + maybe_shader, + c.enable_z_buffer + ); - // println!("shader: {}", default_hash(&name)); - - if let Some(shader) = maybe_shader { - RenderPipeline::User( - c.user_pipelines.entry(name.clone()).or_insert_with(|| { - info!("Creating pipeline for shader: {:?}", shader.id); - - let mut layout_entries = Vec::new(); - let mut bind_group_entries = Vec::new(); - let mut buffers = HashMap::new(); - - for (uniform_name, binding) in shader.bindings.iter() { - let uniform_def = - shader.uniform_defs.get(uniform_name).unwrap(); - - layout_entries.push(wgpu::BindGroupLayoutEntry { - binding: *binding, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }); - - let uniform_buffer_usage = wgpu::BufferUsages::UNIFORM | - wgpu::BufferUsages::COPY_DST; - - match uniform_def { - UniformDef::F32(maybe_default) => { - if let Some(value) = maybe_default { - let buffer = - c.context.device.create_buffer_init( - &wgpu::util::BufferInitDescriptor { - label: Some(&format!( - "User UB: {} (default={})", - uniform_name, value - )), - contents: bytemuck::cast_slice( - &[*value], - ), - usage: uniform_buffer_usage, - }, - ); - - buffers.insert( - uniform_name.to_string(), - buffer, - ); - } else { - let buffer = - c.context.device.create_buffer( - &wgpu::BufferDescriptor { - label: Some(&format!( - "User UB: {} (no-default)", - uniform_name - )), - size: std::mem::size_of::() - as u64, - usage: uniform_buffer_usage, - mapped_at_creation: false, - }, - ); - - buffers.insert( - uniform_name.to_string(), - buffer, + // println!("shader: {}", default_hash(&name)); + + let mesh_pipeline = if let Some(shader) = maybe_shader { + RenderPipeline::User( + c.user_pipelines.entry(name.clone()).or_insert_with(|| { + info!("Creating pipeline for shader: {:?}", shader.id); + + let mut layout_entries = Vec::new(); + let mut bind_group_entries = Vec::new(); + let mut buffers = HashMap::new(); + + for (uniform_name, binding) in shader.bindings.iter() { + let uniform_def = + shader.uniform_defs.get(uniform_name).unwrap(); + + layout_entries.push(wgpu::BindGroupLayoutEntry { + binding: *binding, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }); + + let uniform_buffer_usage = wgpu::BufferUsages::UNIFORM | + wgpu::BufferUsages::COPY_DST; + + match uniform_def { + UniformDef::F32(maybe_default) => { + if let Some(value) = maybe_default { + let buffer = + c.context.device.create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some(&format!( + "User UB: {} (default={})", + uniform_name, value + )), + contents: bytemuck::cast_slice(&[ + *value, + ]), + usage: uniform_buffer_usage, + }, ); - } - } - UniformDef::Custom { .. } => { - unimplemented!( - "custom uniforms aren't available yet" + + buffers + .insert(uniform_name.to_string(), buffer); + } else { + let buffer = c.context.device.create_buffer( + &wgpu::BufferDescriptor { + label: Some(&format!( + "User UB: {} (no-default)", + uniform_name + )), + size: std::mem::size_of::() as u64, + usage: uniform_buffer_usage, + mapped_at_creation: false, + }, ); + + buffers + .insert(uniform_name.to_string(), buffer); } - }; - } + } + UniformDef::Custom { .. } => { + unimplemented!( + "custom uniforms aren't available yet" + ); + } + }; + } - for (name, binding) in shader.bindings.iter() { - bind_group_entries.push(wgpu::BindGroupEntry { - binding: *binding, - resource: buffers - .get(name) - .unwrap() - .as_entire_binding(), - }); - } + for (name, binding) in shader.bindings.iter() { + bind_group_entries.push(wgpu::BindGroupEntry { + binding: *binding, + resource: buffers + .get(name) + .unwrap() + .as_entire_binding(), + }); + } - let user_layout = - c.context.device.create_bind_group_layout( - &wgpu::BindGroupLayoutDescriptor { - label: Some(&format!("User Layout: {}", name)), - entries: &layout_entries, - }, - ); - - let pipeline = create_render_pipeline_with_layout( - &name, - &c.context.device, - wgpu::TextureFormat::Rgba16Float, - &[ - &c.texture_layout, - &c.camera_bind_group_layout, - &user_layout, - ], - &[SpriteVertex::desc()], - shader, - pass_data.blend_mode, - c.enable_z_buffer, - ); + let user_layout = c.context.device.create_bind_group_layout( + &wgpu::BindGroupLayoutDescriptor { + label: Some(&format!("User Layout: {}", name)), + entries: &layout_entries, + }, + ); - let bind_group = c.context.device.create_bind_group( - &wgpu::BindGroupDescriptor { - label: Some("User Bind Group"), - layout: &user_layout, - entries: &bind_group_entries, - }, - ); + let pipeline = create_render_pipeline_with_layout( + &name, + &c.context.device, + wgpu::TextureFormat::Rgba16Float, + &[ + &c.texture_layout, + &c.camera_bind_group_layout, + &user_layout, + ], + &[SpriteVertex::desc()], + shader, + pass_data.blend_mode, + c.enable_z_buffer, + ) + .unwrap(); + + let bind_group = c.context.device.create_bind_group( + &wgpu::BindGroupDescriptor { + label: Some("User Bind Group"), + layout: &user_layout, + entries: &bind_group_entries, + }, + ); - UserRenderPipeline { - pipeline, - layout: user_layout, - bind_group, - buffers, - } - }), - ) - } else { - RenderPipeline::Wgpu( - c.pipelines.entry(name.clone()).or_insert_with(|| { - create_render_pipeline_with_layout( - &name, - &c.context.device, - wgpu::TextureFormat::Rgba16Float, - &[&c.texture_layout, &c.camera_bind_group_layout], - &[SpriteVertex::desc()], - c.shaders.borrow().get(sprite_shader_id).unwrap(), - pass_data.blend_mode, - c.enable_z_buffer, - ) - }), - ) - } + UserRenderPipeline { + pipeline, + layout: user_layout, + bind_group, + buffers, + } + }), + ) + } else { + RenderPipeline::Wgpu(c.pipelines.entry(name.clone()).or_insert_with( + || { + create_render_pipeline_with_layout( + &name, + &c.context.device, + wgpu::TextureFormat::Rgba16Float, + &[&c.texture_layout, &c.camera_bind_group_layout], + &[SpriteVertex::desc()], + c.shaders.borrow().get(sprite_shader_id).unwrap(), + pass_data.blend_mode, + c.enable_z_buffer, + ) + .unwrap() + }, + )) }; + let target_view = + if c.post_processing_effects.borrow().iter().any(|x| x.enabled) { + &c.first_pass_texture.view + } else { + surface_view + }; + perf_counter_inc("batch-count", 1); let mut encoder = c.context.device.simple_encoder("Mesh Render Encoder"); @@ -360,6 +355,8 @@ pub fn render_meshes( bytemuck::cast_slice(all_indices.as_slice()), ); + let textures = c.textures.lock(); + { let clear_color = if is_first { Some(clear_color) } else { None }; @@ -460,6 +457,7 @@ pub fn render_particles( pass_data.blend_mode, c.enable_z_buffer, ) + .expect("particle pipeline creation failed") }) }; diff --git a/comfy-wgpu/src/debug.rs b/comfy-wgpu/src/debug.rs index 1ef303c..e9ddd22 100644 --- a/comfy-wgpu/src/debug.rs +++ b/comfy-wgpu/src/debug.rs @@ -51,6 +51,7 @@ pub fn render_debug( BlendMode::Alpha, enable_z_buffer, ) + .expect("debug pipeline creation failed") }); diff --git a/comfy-wgpu/src/device.rs b/comfy-wgpu/src/device.rs index 5cf66e8..98f50e2 100644 --- a/comfy-wgpu/src/device.rs +++ b/comfy-wgpu/src/device.rs @@ -56,9 +56,9 @@ pub async fn create_graphics_context(window: &Window) -> GraphicsContext { .await .expect("failed to create wgpu adapter"); - #[cfg(feature = "ci-release")] device.on_uncaptured_error(Box::new(|err| { error!("WGPU ERROR: {:?}", err); + #[cfg(fature = "ci-release")] panic!("Exiting due to wgpu error: {:?}", err); })); diff --git a/comfy-wgpu/src/hot_reload.rs b/comfy-wgpu/src/hot_reload.rs index 970e751..70eaaa3 100644 --- a/comfy-wgpu/src/hot_reload.rs +++ b/comfy-wgpu/src/hot_reload.rs @@ -107,48 +107,7 @@ impl HotReload { if is_close_write && !is_temp { reload = true; - for path in event - .paths - .iter() - .filter(|x| !x.to_string_lossy().ends_with('~')) - { - if let Some(shader_id) = self.shader_paths.get(path) - { - match std::fs::read_to_string(path) { - Ok(source) => { - let fragment_source = - &sprite_shader_from_fragment( - &source, - ); - - update_shader( - shaders, - *shader_id, - fragment_source, - ); - } - - Err(error) => { - error!( - "Error loading a shader at {}: \ - {:?}", - path.to_string_lossy(), - error - ) - } - } - } else { - error!( - "Trying to reload shader at {} but no \ - ShaderId defined for that path. This \ - likely means a wrong path was passed to \ - `create_reloadable_shader`. Existing \ - paths: {:?}", - path.to_string_lossy(), - self.shader_paths - ); - } - } + self.reload_path_bufs(shaders, &event.paths); } } @@ -158,4 +117,54 @@ impl HotReload { reload } + + fn reload_path_bufs(&self, shaders: &mut ShaderMap, paths: &[PathBuf]) { + for path in paths.iter().filter(|x| !x.to_string_lossy().ends_with('~')) + { + if let Some(shader_id) = self.shader_paths.get(path) { + match std::fs::read_to_string(path) { + Ok(source) => { + let fragment_source = + &sprite_shader_from_fragment(&source); + + if check_shader_with_naga(fragment_source) + .log_err_ok() + .is_some() + { + update_shader(shaders, *shader_id, fragment_source); + } + } + + Err(error) => { + error!( + "Error loading a shader at {}: {:?}", + path.to_string_lossy(), + error + ) + } + } + } else { + error!( + "Trying to reload shader at {} but no ShaderId defined \ + for that path. This likely means a wrong path was passed \ + to `create_reloadable_shader`. Existing paths: {:?}", + path.to_string_lossy(), + self.shader_paths + ); + } + } + } +} + +pub fn check_shader_with_naga(source: &str) -> Result<()> { + let module = naga::front::wgsl::parse_str(&source)?; + + let mut validator = naga::valid::Validator::new( + naga::valid::ValidationFlags::all(), + naga::valid::Capabilities::all(), + ); + + validator.validate(&module)?; + + Ok(()) } diff --git a/comfy-wgpu/src/lib.rs b/comfy-wgpu/src/lib.rs index 842dd24..656ba1a 100644 --- a/comfy-wgpu/src/lib.rs +++ b/comfy-wgpu/src/lib.rs @@ -27,6 +27,7 @@ mod y_sort; // idk what to call this, so magic it is for now ... mod debug; mod magic; +mod pipelines; pub use crate::batching::*; pub use crate::blood_canvas::*; @@ -44,6 +45,7 @@ pub use crate::renderer::*; pub use crate::texture::*; pub use crate::utils::*; pub use crate::y_sort::*; +pub use crate::pipelines::*; pub use wgpu; pub use wgpu_types; @@ -354,8 +356,21 @@ pub fn create_render_pipeline( vertex_layouts: &[wgpu::VertexBufferLayout], shader: &Shader, blend_mode: BlendMode, -) -> wgpu::RenderPipeline { - let shader = device.create_shader_module(shader_to_wgpu(shader)); +) -> Result { + // let module = naga::front::wgsl::parse_str(&shader.source)?; + // + // let mut validator = naga::valid::Validator::new( + // naga::valid::ValidationFlags::all(), + // naga::valid::Capabilities::all(), + // ); + // + // validator.validate(&module)?; + + let wgpu_shader = shader_to_wgpu(shader); + + let shader = device.create_shader_module(wgpu_shader); + + println!("CREATED SHADER, GOT {:?}", shader); let blend_state = match blend_mode { BlendMode::Alpha => Some(wgpu::BlendState::ALPHA_BLENDING), @@ -404,56 +419,59 @@ pub fn create_render_pipeline( // }, // }); - device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some(label), - layout: Some(layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_main", - buffers: vertex_layouts, - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - targets: &[Some(wgpu::ColorTargetState { - format: color_format, - blend: blend_state, - write_mask: wgpu::ColorWrites::ALL, - })], - }), - - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - // cull_mode: Some(wgpu::Face::Back), - cull_mode: None, - // Settings this to anything other than Fill requires - // Features::NON_FILL_POLYGON_MODE - polygon_mode: wgpu::PolygonMode::Fill, - // Requires Features::DEPTH_CLIP_CONTROL - unclipped_depth: false, - // Requires Features::CONSERVATIVE_RASTERIZATION - conservative: false, - }, - - depth_stencil: depth_format.map(|format| { - wgpu::DepthStencilState { - format, - depth_write_enabled: true, - depth_compare: wgpu::CompareFunction::Less, - stencil: wgpu::StencilState::default(), - bias: wgpu::DepthBiasState::default(), - } - }), - - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - }) + let pipeline = + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some(label), + layout: Some(layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: vertex_layouts, + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format: color_format, + blend: blend_state, + write_mask: wgpu::ColorWrites::ALL, + })], + }), + + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + // cull_mode: Some(wgpu::Face::Back), + cull_mode: None, + // Settings this to anything other than Fill requires + // Features::NON_FILL_POLYGON_MODE + polygon_mode: wgpu::PolygonMode::Fill, + // Requires Features::DEPTH_CLIP_CONTROL + unclipped_depth: false, + // Requires Features::CONSERVATIVE_RASTERIZATION + conservative: false, + }, + + depth_stencil: depth_format.map(|format| { + wgpu::DepthStencilState { + format, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::Less, + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), + } + }), + + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + }); + + Ok(pipeline) } pub fn create_render_pipeline_with_layout( @@ -465,7 +483,7 @@ pub fn create_render_pipeline_with_layout( shader: &Shader, blend_mode: BlendMode, enable_z_buffer: bool, -) -> wgpu::RenderPipeline { +) -> Result { let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some(&format!("{} Pipeline Layout", name)), diff --git a/comfy-wgpu/src/pipelines.rs b/comfy-wgpu/src/pipelines.rs new file mode 100644 index 0000000..e69de29 diff --git a/comfy-wgpu/src/renderer.rs b/comfy-wgpu/src/renderer.rs index 83b45cf..c48a44f 100644 --- a/comfy-wgpu/src/renderer.rs +++ b/comfy-wgpu/src/renderer.rs @@ -106,6 +106,7 @@ pub struct WgpuRenderer { pub textures: Arc>, pub sprite_shader_id: ShaderId, + pub error_shader_id: ShaderId, } impl WgpuRenderer { @@ -455,12 +456,21 @@ impl WgpuRenderer { ) .unwrap(); + let error_shader_id = create_shader( + &mut shaders, + "error", + &sprite_shader_from_fragment(engine_shader_source!("error")), + HashMap::new(), + ) + .unwrap(); + Self { #[cfg(not(target_arch = "wasm32"))] thread_pool: rayon::ThreadPoolBuilder::new().build().unwrap(), sprite_shader_id, + error_shader_id, loaded_image_recv: rx_texture, loaded_image_send: tx_texture, @@ -891,6 +901,7 @@ impl WgpuRenderer { &surface_view, ¶ms, self.sprite_shader_id, + self.error_shader_id, ); self.render_post_processing(&surface_view, params.config);