pub enum ShaderCodeSource { Static(ShaderStump), File { path: &'static str, fallback: Option, }, // todo! add option for static fallback } impl ShaderCodeSource { pub fn as_path_to_watch(&self) -> Option { match self { ShaderCodeSource::Static(_) => None, ShaderCodeSource::File { path, .. } => { let path_buf: PathBuf = path .parse() .expect("could not parse ShaderCodeSource::File into PathBuf"); Some(path_buf) } } } /// # Warning /// /// This function does sync io with the file system. Might block the main thread. pub fn load_stump(&self) -> anyhow::Result> { match self { ShaderCodeSource::Static(stump) => Ok(Cow::Borrowed(stump)), ShaderCodeSource::File { path, fallback } => { let shader_stump_or_err: anyhow::Result> = try { let content = std::fs::read_to_string(path)?; let stump = read_from_wgsl_stump(&content)?; Cow::Owned(stump) }; shader_stump_or_err } } } /// In case load_stump fails, this can be tried. pub fn fallback_stump(&self) -> Option<(&ShaderStump, &str)> { match self { ShaderCodeSource::Static(_) => None, ShaderCodeSource::File { path, fallback } => fallback.as_ref().map(|e| (e, *path)), } } } /// A collection of 3 wgsl code segments that get assembled into a full wgsl source file on the fly, /// by combinding them with information from the associated types of the ShaderT trait implementation. /// This makes it impossible to specify /// /// Function signatures for vertex and fragment shaders are also autogenerated and NOT directly taken /// from the stump code. /// /// The stump source file is expected is expected to have a format like this: /// ```wgsl /// [other_code] /// /// fn vertex() { /// [vertex] /// } /// /// [other_code] /// /// fn fragment() { /// [fragment] /// } /// /// [other_code] /// ``` /// /// Where `[vertex]`, `[fragment]` and `[other_code]` regions are parsed into the ShaderStump struct. #[derive(Debug, Clone)] pub struct ShaderStump { /// Inner code of the vertex shader function. Has access to an input argument: `vertex: Vertex`, /// and `instance: Instance`, if they are not empty. /// Also has access to all builtin inputs of fragments shaders. vertex: Cow<'static, str>, /// Inner code of the fragment shader function. Has access to an input argument: `in: VertexOutput` /// and to all builtin inputs of fragments shaders. fragment: Cow<'static, str>, /// Code that is appended to the generated wgsl file. other_code: Cow<'static, str>, } // pub fn create_shader_skeleton(path: &str) -> String { // let shader_code = ShaderCode { // vertex: Cow::Borrowed(indoc! {" // // your code here... // "}), // fragment: Cow::Borrowed(indoc! {" // // your code here... // "}), // other_code: Cow::Borrowed(indoc! {" // // You can defined additonal functions in this section. // "}), // }; // } /// Parses a WGSL stump file into 3 sections: /// - vertex shader inner code /// - fragment shader inner code /// - other functions, struct defs /// /// The stump file is expected to have a structure like this: /// ```wgsl /// fn vertex() { /// // vertex shader inner code /// var out: VertexOutput; /// // set output fields, these are /// return out; /// } /// /// fn fragment() { /// // fragment shader inner code /// return vec4(1.0, 0.0, 0.0, 1.0); /// } /// /// // other functions, struct defs /// ``` /// /// Notice that the function signatures are not correct wgsl. /// That is because we auto generate those from the types provided in the ShaderT trait /// implementation that this wgsl stump is supposed to be used with. /// /// Bind groups will be also inserted and should not be specified manually. E.g. this is auto generated: /// /// ```wgsl /// @group(0) @binding(0) /// var camera : Camera; /// struct Camera { /// view_pos : vec4, /// view_proj : mat4x4, /// ``` /// /// Also Vertex, Instance and VertexOutput structs are autogenerated and do NOT need to be specified in the /// wgsl stump file. E.g. /// ```wgsl /// struct Vertex { /// @location(0) pos: vec3, /// @location(1) color: vec4, /// } /// struct Instance { /// @location(2) col1: vec4, /// @location(3) col2: vec4, /// @location(4) col3: vec4, /// @location(5) translation: vec4, /// } /// struct VertexOutput { /// @builtin(position) clip_position: vec4, /// @location(0) color: vec4, /// } /// ``` /// /// The function signatures are also auto generated. /// /// The vertex shader can have any of the inputs here: /// - Inputs: `vertex_index: u32`, `instance_index: u32`, `vertex: Vertex`, `instance: Instance` /// - Output: VertexOutput /// /// The fragment shader can have these inputs: /// - Inputs: `in: VertexOutput`, `position: vec4`, `front_facing: bool`, `frag_depth: f32`, `sample_index: u32`, `sample_mask: u32`, /// - Output: vec4 (The fragment color) /// pub fn read_from_wgsl_stump(stump: &str) -> anyhow::Result { // Super naive, make better later. // let mut vertex_shader_chars: Vec = vec![]; // let mut fragment_shader_chars: Vec = vec![]; let mut other_code: String = String::new(); let fnvert_idx = stump .find("fn vertex(") .ok_or(anyhow!("fn vertex( not found in wgsl stump"))?; // code before other_code.push_str(&stump[..fnvert_idx]); let (vertex_shader_inner, rest) = next_curly_bracket_region(&stump[fnvert_idx..])?; let fnfrag_idx = rest.find("fn fragment(").ok_or(anyhow!( "fn fragment( not found in wgsl stump (has to come after fn vertex(..) )" ))?; // code between vertex and fragment shader other_code.push_str(&rest[..fnfrag_idx]); let (fragment_shader_inner, rest) = next_curly_bracket_region(&rest[fnfrag_idx..])?; // code after fragment shader other_code.push_str(&rest); Ok(ShaderStump { vertex: vertex_shader_inner.into(), fragment: fragment_shader_inner.into(), other_code: other_code.into(), }) } /// Looks for the next bracket scope in a string, returning all characters in it. /// Super naive..., returns rest of the input str after the bracket region fn next_curly_bracket_region(input: &str) -> anyhow::Result<(String, &str)> { let mut res: String = String::new(); let mut level: i32 = 0; let last_bracket_index: usize; let mut iter = input.char_indices(); loop { let (i, c) = iter .next() .ok_or_else(|| anyhow!("Curly Bracket region is not fully enclosed"))?; match (level, c) { (0, '{') => { level += 1; } (0, _) => { // nothing, skip over these chars } (1, '}') => { // region is over: last_bracket_index = i; break; } (_, '{') => { level += 1; res.push(c); } (_, '}') => { level -= 1; res.push(c); } (_, _) => { res.push(c); } } } return Ok((res, &input[(last_bracket_index + 1)..])); } // #[deprecated] // pub fn generate_md_shader_skeleton(path: &str) { // let bind_groups = bind_groups_to_wgsl(::BIND_GROUP_DEFS); // let (vertex_output_struct_def, vertex_struct_def, instance_struct_def) = // vertex_instance_output_struct_defs::(); // let vertex_function_args: String = { // let mut args: Vec = vec![]; // for (name, ty) in WGPU_VERTEX_BUILTIN_INPUTS { // args.push(format!("`{name}: {ty}`")); // } // if vertex_struct_def.is_some() { // args.push("`vertex: Vertex`".into()); // } // if instance_struct_def.is_some() { // args.push("`instance: Instance`".into()); // } // args.join(", ") // }; // let fragment_function_args: String = { // let mut args: Vec = vec![]; // for (name, ty) in WGPU_FRAGMENT_BUILTIN_INPUTS { // args.push(format!("`{name}: {ty}`")); // } // args.push("`in: VertexOutput`".into()); // args.join(", ") // }; // let vertex_struct_def = vertex_struct_def.unwrap_or("// No Vertex Struct".into()); // let instance_struct_def = instance_struct_def.unwrap_or("// No Instance Struct".into()); // let shader_name = std::any::type_name::(); // let output = formatdoc! {" // # {shader_name} // Shader Source File. Code in this segment annotated with `rs,wgsl`, `rs,wgsl,vertex` or `rs,wgsl,fragment` will be assembled into a the {shader_name} Shader. // ## Bind Groups // ```rs,wgsl,ignore // {bind_groups} // ``` // ## Vertex, Instance and VertexOutput // ```rs,wgsl,ignore // {vertex_struct_def} // {instance_struct_def} // {vertex_output_struct_def} // ``` // ## Vertex Shader // - Inputs: {vertex_function_args} // - Output: VertexOutput // ```rs,wgsl,vertex // // Code here will be inserted into the vertex shader. // var output: VertexOutput; // // set output.. // return output; // ``` // ## Fragment Shader // - Inputs: {fragment_function_args} // - Output: vec4 (The fragment color) // ```rs,wgsl,fragment // // Code here will be inserted into the fragment shader. // return vec4(1.0,0.0,0.0,1.0); // ``` // ## Other Code // ```rs,wgsl // // Here you can define other structs functions that the parse will pick up on. // ``` // "}; // std::fs::write(path, output).expect("Could not write to file."); // }