diff --git a/CHANGELOG_VULKANO.md b/CHANGELOG_VULKANO.md index d72e766d..ab2f9e0f 100644 --- a/CHANGELOG_VULKANO.md +++ b/CHANGELOG_VULKANO.md @@ -5,6 +5,8 @@ - Update MacOS dependencies metal to 0.17 and cocoa to 0.19 - Added dynamic stencil elements to `DynamicState` - Fixed `ImageDimensions::mipmap_dimensions` and `max_mipmaps` in cases where the original size is not a power of two. +- Shader includes now work on Windows. +- **Breaking Change** Shader include directories passed to the `shader!` macro are now relative to the crates `Cargo.toml` # Version 0.14.0 (2019-08-17) diff --git a/examples/src/bin/shader-include/main.rs b/examples/src/bin/shader-include/main.rs index ccc096f0..f0f60ca0 100644 --- a/examples/src/bin/shader-include/main.rs +++ b/examples/src/bin/shader-include/main.rs @@ -23,68 +23,66 @@ use vulkano::sync; use std::sync::Arc; fn main() { -// TODO: Disabled because it fails on windows -// -// let instance = Instance::new(None, &InstanceExtensions::none(), None).unwrap(); -// let physical = PhysicalDevice::enumerate(&instance).next().unwrap(); -// let queue_family = physical.queue_families().find(|&q| q.supports_compute()).unwrap(); -// let (device, mut queues) = Device::new(physical, physical.supported_features(), -// &DeviceExtensions::none(), [(queue_family, 0.5)].iter().cloned()).unwrap(); -// let queue = queues.next().unwrap(); -// -// println!("Device initialized"); -// -// let pipeline = Arc::new({ -// mod cs { -// vulkano_shaders::shader!{ -// ty: "compute", -// // We declare what directories to search for when using the `#include <...>` -// // syntax. Specified directories have descending priorities based on their order. -// include: [ "examples/src/bin/shader-include/standard-shaders" ], -// src: " -//#version 450 -//// Substitutes this line with the contents of the file `common.glsl` found in one of the standard -//// `include` directories specified above. -//// Note, that relative inclusion (`#include \"...\"`), although it falls back to standard -//// inclusion, should not be used for **embedded** shader source, as it may be misleading and/or -//// confusing. -//#include -// -//layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; -// -//layout(set = 0, binding = 0) buffer Data { -// uint data[]; -//} data; -// -//void main() { -// uint idx = gl_GlobalInvocationID.x; -// data.data[idx] = multiply_by_12(data.data[idx]); -//}" -// } -// } -// let shader = cs::Shader::load(device.clone()).unwrap(); -// ComputePipeline::new(device.clone(), &shader.main_entry_point(), &()).unwrap() -// }); -// -// let data_buffer = { -// let data_iter = (0 .. 65536u32).map(|n| n); -// CpuAccessibleBuffer::from_iter(device.clone(), BufferUsage::all(), data_iter).unwrap() -// }; -// let set = Arc::new(PersistentDescriptorSet::start(pipeline.clone(), 0) -// .add_buffer(data_buffer.clone()).unwrap() -// .build().unwrap() -// ); -// let command_buffer = AutoCommandBufferBuilder::primary_one_time_submit(device.clone(), queue.family()).unwrap() -// .dispatch([1024, 1, 1], pipeline.clone(), set.clone(), ()).unwrap() -// .build().unwrap(); -// let future = sync::now(device.clone()) -// .then_execute(queue.clone(), command_buffer).unwrap() -// .then_signal_fence_and_flush().unwrap(); -// -// future.wait(None).unwrap(); -// -// let data_buffer_content = data_buffer.read().unwrap(); -// for n in 0 .. 65536u32 { -// assert_eq!(data_buffer_content[n as usize], n * 12); -// } + let instance = Instance::new(None, &InstanceExtensions::none(), None).unwrap(); + let physical = PhysicalDevice::enumerate(&instance).next().unwrap(); + let queue_family = physical.queue_families().find(|&q| q.supports_compute()).unwrap(); + let (device, mut queues) = Device::new(physical, physical.supported_features(), + &DeviceExtensions::none(), [(queue_family, 0.5)].iter().cloned()).unwrap(); + let queue = queues.next().unwrap(); + + println!("Device initialized"); + + let pipeline = Arc::new({ + mod cs { + vulkano_shaders::shader!{ + ty: "compute", + // We declare what directories to search for when using the `#include <...>` + // syntax. Specified directories have descending priorities based on their order. + include: [ "src/bin/shader-include/standard-shaders" ], + src: " +#version 450 +// Substitutes this line with the contents of the file `common.glsl` found in one of the standard +// `include` directories specified above. +// Note, that relative inclusion (`#include \"...\"`), although it falls back to standard +// inclusion, should not be used for **embedded** shader source, as it may be misleading and/or +// confusing. +#include + +layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; + +layout(set = 0, binding = 0) buffer Data { + uint data[]; +} data; + +void main() { + uint idx = gl_GlobalInvocationID.x; + data.data[idx] = multiply_by_12(data.data[idx]); +}" + } + } + let shader = cs::Shader::load(device.clone()).unwrap(); + ComputePipeline::new(device.clone(), &shader.main_entry_point(), &()).unwrap() + }); + + let data_buffer = { + let data_iter = (0 .. 65536u32).map(|n| n); + CpuAccessibleBuffer::from_iter(device.clone(), BufferUsage::all(), data_iter).unwrap() + }; + let set = Arc::new(PersistentDescriptorSet::start(pipeline.clone(), 0) + .add_buffer(data_buffer.clone()).unwrap() + .build().unwrap() + ); + let command_buffer = AutoCommandBufferBuilder::primary_one_time_submit(device.clone(), queue.family()).unwrap() + .dispatch([1024, 1, 1], pipeline.clone(), set.clone(), ()).unwrap() + .build().unwrap(); + let future = sync::now(device.clone()) + .then_execute(queue.clone(), command_buffer).unwrap() + .then_signal_fence_and_flush().unwrap(); + + future.wait(None).unwrap(); + + let data_buffer_content = data_buffer.read().unwrap(); + for n in 0 .. 65536u32 { + assert_eq!(data_buffer_content[n as usize], n * 12); + } } diff --git a/vulkano-shaders/src/codegen.rs b/vulkano-shaders/src/codegen.rs index eee07dc0..1d14deaf 100644 --- a/vulkano-shaders/src/codegen.rs +++ b/vulkano-shaders/src/codegen.rs @@ -29,14 +29,14 @@ use crate::read_file_to_string; fn include_callback(requested_source_path_raw: &str, directive_type: IncludeType, contained_within_path_raw: &str, recursion_depth: usize, - include_directories: &[String], root_source_has_path: bool) -> Result { + include_directories: &[impl AsRef], root_source_has_path: bool, + base_path: &impl AsRef) -> Result { let file_to_include = match directive_type { IncludeType::Relative => { let requested_source_path = Path::new(requested_source_path_raw); - // Is embedded current shader source embedded within a rust macro? - // If so, abort. - if !root_source_has_path && recursion_depth == 1 { + // If so, abort unless absolute path. + if !root_source_has_path && recursion_depth == 1 && !requested_source_path.is_absolute() { let requested_source_name = requested_source_path.file_name() .expect("Could not get the name of the requested source file.") .to_string_lossy(); @@ -51,18 +51,21 @@ fn include_callback(requested_source_path_raw: &str, directive_type: IncludeType requested_source_name, requested_source_directory)); } - let parent_of_current_source = Path::new(contained_within_path_raw).parent() - .unwrap_or_else(|| panic!("The file `{}` does not reside in a directory. This is \ - an implementation error.", - contained_within_path_raw)); - let resolved_requested_source_path = parent_of_current_source.join(requested_source_path); + let mut resolved_path = if recursion_depth == 1 { + Path::new(contained_within_path_raw).parent().map(|parent| base_path.as_ref().join(parent)) + } else { + Path::new(contained_within_path_raw).parent().map(|parent| parent.to_owned()) + }.unwrap_or_else(|| panic!("The file `{}` does not reside in a directory. This is \ + an implementation error.", + contained_within_path_raw)); + resolved_path.push(requested_source_path); - if !resolved_requested_source_path.is_file() { + if !resolved_path.is_file() { return Err(format!("Invalid inclusion path `{}`, the path does not point to a file.", requested_source_path_raw)); } - resolved_requested_source_path + resolved_path }, IncludeType::Standard => { let requested_source_path = Path::new(requested_source_path_raw); @@ -78,58 +81,35 @@ fn include_callback(requested_source_path_raw: &str, directive_type: IncludeType requested_source_path_raw)); } - let mut found_requested_source_path = None; + let found_requested_source_path = include_directories.iter().map(|include_directory| include_directory.as_ref().join(requested_source_path)).find( + |resolved_requested_source_path| resolved_requested_source_path.is_file() + ); - for include_directory in include_directories { - let include_directory_path = Path::new(include_directory).canonicalize() - .unwrap_or_else(|_| panic!("Invalid standard shader inclusion directory `{}`.", - include_directory)); - let resolved_requested_source_path_rel = include_directory_path - .join(requested_source_path); - let resolved_requested_source_path = resolved_requested_source_path_rel - .canonicalize() - .map_err(|_| format!("Invalid inclusion path `{}`.", - resolved_requested_source_path_rel.to_string_lossy()))?; - - if !resolved_requested_source_path.starts_with(include_directory_path) { - return Err(format!("Cannot use `..` with inclusion from standard directories \ - (`#include <...>`), try using `#include \"...\"` instead. \ - Requested path: {}", requested_source_path.to_string_lossy())); - } - - if resolved_requested_source_path.is_file() { - found_requested_source_path = Some(resolved_requested_source_path); - break; - } - } - - if found_requested_source_path.is_none() { + if let Some(found_requested_source_path) = found_requested_source_path { + found_requested_source_path + } else { return Err(format!("Could not include the file `{}` from any include directories.", requested_source_path_raw)); } - - found_requested_source_path.unwrap() }, }; - let canonical_file_to_include = file_to_include.canonicalize() - .unwrap_or_else(|_| file_to_include); - let canonical_file_to_include_string = canonical_file_to_include.to_str() + let file_to_include_string = file_to_include.to_str() .expect("Could not stringify the file to be included. Make sure the path consists of \ valid unicode characters.") .to_string(); - let content = read_file_to_string(canonical_file_to_include.as_path()) + let content = read_file_to_string(file_to_include.as_path()) .map_err(|_| format!("Could not read the contents of file `{}` to be included in the \ shader source.", - &canonical_file_to_include_string))?; + &file_to_include_string))?; Ok(ResolvedInclude { - resolved_name: canonical_file_to_include_string, + resolved_name: file_to_include_string, content, }) } -pub fn compile(path: Option, code: &str, ty: ShaderKind, include_directories: &[String]) -> Result { +pub fn compile(path: Option, base_path: &impl AsRef, code: &str, ty: ShaderKind, include_directories: &[impl AsRef]) -> Result { let mut compiler = Compiler::new().ok_or("failed to create GLSL compiler")?; let mut compile_options = CompileOptions::new() .ok_or("failed to initialize compile option")?; @@ -144,7 +124,7 @@ pub fn compile(path: Option, code: &str, ty: ShaderKind, include_directo compile_options.set_include_callback(|requested_source_path, directive_type, contained_within_path, recursion_depth| { include_callback(requested_source_path, directive_type, contained_within_path, - recursion_depth, include_directories, path.is_some()) + recursion_depth, include_directories, path.is_some(), base_path) }); let content = compiler @@ -374,6 +354,7 @@ fn capability_name(cap: &Capability) -> Option<&'static str> { #[cfg(test)] mod tests { use super::*; + use std::path::PathBuf; #[test] fn test_bad_alignment() { @@ -383,7 +364,8 @@ mod tests { // byte, but in a rust [[f32;3];2], the second element starts on the // 12th byte. Since we can't generate code for these types, we should // create an error instead of generating incorrect code. - let comp = compile(None, " + let includes: [PathBuf;0] = []; + let comp = compile(None, &Path::new(""), " #version 450 struct MyStruct { vec3 vs[2]; @@ -392,14 +374,15 @@ mod tests { MyStruct s; }; void main() {} - ", ShaderKind::Vertex, &[]).unwrap(); + ", ShaderKind::Vertex, &includes).unwrap(); let doc = parse::parse_spirv(comp.as_binary()).unwrap(); let res = std::panic::catch_unwind(|| structs::write_structs(&doc)); assert!(res.is_err()); } #[test] fn test_trivial_alignment() { - let comp = compile(None, " + let includes: [PathBuf;0] = []; + let comp = compile(None, &Path::new(""), " #version 450 struct MyStruct { vec4 vs[2]; @@ -408,7 +391,7 @@ mod tests { MyStruct s; }; void main() {} - ", ShaderKind::Vertex, &[]).unwrap(); + ", ShaderKind::Vertex, &includes).unwrap(); let doc = parse::parse_spirv(comp.as_binary()).unwrap(); structs::write_structs(&doc); } @@ -416,7 +399,8 @@ mod tests { fn test_wrap_alignment() { // This is a workaround suggested in the case of test_bad_alignment, // so we should make sure it works. - let comp = compile(None, " + let includes: [PathBuf;0] = []; + let comp = compile(None, &Path::new(""), " #version 450 struct Vec3Wrap { vec3 v; @@ -428,8 +412,48 @@ mod tests { MyStruct s; }; void main() {} - ", ShaderKind::Vertex, &[]).unwrap(); + ", ShaderKind::Vertex, &includes).unwrap(); let doc = parse::parse_spirv(comp.as_binary()).unwrap(); structs::write_structs(&doc); } + + #[test] + fn test_include_resolution() { + let root_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let empty_includes: [PathBuf;0] = []; + let _compile_relative = compile(Some(String::from("tests/include_test.glsl")), &root_path, " + #version 450 + #include \"include_dir_a/target_a.glsl\" + #include \"include_dir_b/target_b.glsl\" + void main() {} + ", ShaderKind::Vertex, &empty_includes).expect("Cannot resolve include files"); + + let _compile_include_paths = compile(Some(String::from("tests/include_test.glsl")), &root_path, " + #version 450 + #include + #include + void main() {} + ", ShaderKind::Vertex,&[root_path.join("tests/include_dir_a"), root_path.join("tests/include_dir_b")]).expect("Cannot resolve include files"); + + let _compile_include_paths_with_relative = compile(Some(String::from("tests/include_test.glsl")), &root_path, " + #version 450 + #include + #include <../include_dir_b/target_b.glsl> + void main() {} + ", ShaderKind::Vertex,&[root_path.join("tests/include_dir_a")]).expect("Cannot resolve include files"); + + let absolute_path = root_path.join("tests/include_dir_a/target_a.glsl"); + let absolute_path_str = absolute_path.to_str().expect("Cannot run tests in a folder with non unicode characters"); + let _compile_absolute_path = compile(Some(String::from("tests/include_test.glsl")), &root_path, &format!(" + #version 450 + #include \"{}\" + void main() {{}} + ", absolute_path_str), ShaderKind::Vertex, &empty_includes).expect("Cannot resolve include files"); + + let _compile_recursive = compile(Some(String::from("tests/include_test.glsl")), &root_path, " + #version 450 + #include + void main() {} + ", ShaderKind::Vertex,&[root_path.join("tests/include_dir_b"), root_path.join("tests/include_dir_c")]).expect("Cannot resolve include files"); + } } diff --git a/vulkano-shaders/src/lib.rs b/vulkano-shaders/src/lib.rs index eabd9d86..af8b5be8 100644 --- a/vulkano-shaders/src/lib.rs +++ b/vulkano-shaders/src/lib.rs @@ -129,7 +129,8 @@ //! ## `include: ["...", "...", ..., "..."]` //! //! Specifies the standard include directories to be searched through when using the -//! `#include <...>` directive within a shader source. +//! `#include <...>` directive within a shader source. Include directories can be absolute +//! or relative to `Cargo.toml`. //! If `path` was specified, relative paths can also be used (`#include "..."`), without the need //! to specify one or more standard include directories. Relative paths are relative to the //! directory, which contains the source file the `#include "..."` directive is declared in. @@ -287,12 +288,13 @@ pub(self) fn read_file_to_string(full_path: &Path) -> IoResult { #[proc_macro] pub fn shader(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as MacroInput); + let root = env::var("CARGO_MANIFEST_DIR").unwrap_or(".".into()); + let root_path = Path::new(&root); let (path, source_code) = match input.source_kind { SourceKind::Src(source) => (None, source), SourceKind::Path(path) => (Some(path.clone()), { - let root = env::var("CARGO_MANIFEST_DIR").unwrap_or(".".into()); - let full_path = Path::new(&root).join(&path); + let full_path = root_path.join(&path); if full_path.is_file() { read_file_to_string(&full_path) @@ -303,6 +305,13 @@ pub fn shader(input: proc_macro::TokenStream) -> proc_macro::TokenStream { }) }; - let content = codegen::compile(path, &source_code, input.shader_kind, &input.include_directories).unwrap(); + let include_paths = input.include_directories.iter().map(|include_directory| { + let include_path = Path::new(include_directory); + let mut full_include_path = root_path.to_owned(); + full_include_path.push(include_path); + full_include_path + }).collect::>(); + + let content = codegen::compile(path, &root_path, &source_code, input.shader_kind, &include_paths).unwrap(); codegen::reflect("Shader", content.as_binary(), input.dump).unwrap().into() } diff --git a/vulkano-shaders/tests/include_dir_a/target_a.glsl b/vulkano-shaders/tests/include_dir_a/target_a.glsl new file mode 100644 index 00000000..e69de29b diff --git a/vulkano-shaders/tests/include_dir_b/target_b.glsl b/vulkano-shaders/tests/include_dir_b/target_b.glsl new file mode 100644 index 00000000..e69de29b diff --git a/vulkano-shaders/tests/include_dir_c/target_c.glsl b/vulkano-shaders/tests/include_dir_c/target_c.glsl new file mode 100644 index 00000000..54c80eed --- /dev/null +++ b/vulkano-shaders/tests/include_dir_c/target_c.glsl @@ -0,0 +1,2 @@ +#include "../include_dir_a/target_a.glsl" +#include