mirror of
https://github.com/vulkano-rs/vulkano.git
synced 2024-11-21 22:34:43 +00:00
Feature shader include (#1116)
Implement support for the include directive in shader source files
This commit is contained in:
parent
e577f145eb
commit
a9704caea9
@ -1,5 +1,8 @@
|
||||
# Unreleased
|
||||
|
||||
- Add support for `#include "..."` and `#include <...>` directives within source
|
||||
files.
|
||||
|
||||
# Version 0.11.1 (2018-11-16)
|
||||
|
||||
- Expose `CopyImageError` and `DrawIndexedIndirectError`.
|
||||
|
91
examples/src/bin/shader-include/main.rs
Normal file
91
examples/src/bin/shader-include/main.rs
Normal file
@ -0,0 +1,91 @@
|
||||
// Copyright (c) 2017 The vulkano developers
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT
|
||||
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
// This example demonstrates how to use the standard and relative include directives within
|
||||
// shader source code. The boilerplate is taken from the "basic-compute-shader.rs" example, where
|
||||
// most of the boilerplate is explained.
|
||||
|
||||
extern crate vulkano;
|
||||
extern crate vulkano_shaders;
|
||||
|
||||
use vulkano::buffer::{BufferUsage, CpuAccessibleBuffer};
|
||||
use vulkano::command_buffer::AutoCommandBufferBuilder;
|
||||
use vulkano::descriptor::descriptor_set::PersistentDescriptorSet;
|
||||
use vulkano::device::{Device, DeviceExtensions};
|
||||
use vulkano::instance::{Instance, InstanceExtensions, PhysicalDevice};
|
||||
use vulkano::pipeline::ComputePipeline;
|
||||
use vulkano::sync::GpuFuture;
|
||||
use vulkano::sync;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
fn main() {
|
||||
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 <common.glsl>
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
uint multiply_by_2(in uint arg) {
|
||||
return 2 * arg;
|
||||
}
|
10
examples/src/bin/shader-include/standard-shaders/common.glsl
Normal file
10
examples/src/bin/shader-include/standard-shaders/common.glsl
Normal file
@ -0,0 +1,10 @@
|
||||
// Include the file `standard-include.glsl` from one of the standard
|
||||
// directories.
|
||||
#include <standard-include.glsl>
|
||||
// Try to locate the file `relative-include.glsl` in a sibling directory
|
||||
// and include it.
|
||||
#include "../relative-shaders/relative-include.glsl"
|
||||
|
||||
uint multiply_by_12(in uint arg) {
|
||||
return 2 * multiply_by_3(multiply_by_2(arg));
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
uint multiply_by_3(in uint arg) {
|
||||
return 3 * arg;
|
||||
}
|
@ -8,12 +8,13 @@
|
||||
// according to those terms.
|
||||
|
||||
use std::io::Error as IoError;
|
||||
use std::path::Path;
|
||||
|
||||
use syn::Ident;
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use shaderc::{Compiler, CompileOptions};
|
||||
|
||||
pub use shaderc::{CompilationArtifact, ShaderKind};
|
||||
pub use shaderc::{CompilationArtifact, ShaderKind, IncludeType, ResolvedInclude};
|
||||
pub use parse::ParseError;
|
||||
|
||||
use parse::Instruction;
|
||||
@ -24,13 +25,130 @@ use entry_point;
|
||||
use structs;
|
||||
use descriptor_sets;
|
||||
use spec_consts;
|
||||
use read_file_to_string;
|
||||
|
||||
pub fn compile(code: &str, ty: ShaderKind) -> Result<CompilationArtifact, 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<ResolvedInclude, String> {
|
||||
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 {
|
||||
let requested_source_name = requested_source_path.file_name()
|
||||
.expect("Could not get the name of the requested source file.")
|
||||
.to_string_lossy();
|
||||
let requested_source_directory = requested_source_path.parent()
|
||||
.expect("Could not get the directory of the requested source file.")
|
||||
.to_string_lossy();
|
||||
|
||||
return Err(format!("Usage of relative paths in imports in embedded GLSL is not \
|
||||
allowed, try using `#include <{}>` and adding the directory \
|
||||
`{}` to the `include` array in your `shader!` macro call \
|
||||
instead.",
|
||||
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);
|
||||
|
||||
if !resolved_requested_source_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
|
||||
},
|
||||
IncludeType::Standard => {
|
||||
let requested_source_path = Path::new(requested_source_path_raw);
|
||||
|
||||
if requested_source_path.is_absolute() {
|
||||
// This message is printed either when using a missing file with an absolute path
|
||||
// in the relative include directive or when using absolute paths in a standard
|
||||
// include directive.
|
||||
return Err(format!("No such file found, as specified by the absolute path. \
|
||||
Keep in mind, that absolute paths cannot be used with \
|
||||
inclusion from standard directories (`#include <...>`), try \
|
||||
using `#include \"...\"` instead. Requested path: {}",
|
||||
requested_source_path_raw));
|
||||
}
|
||||
|
||||
let mut found_requested_source_path = None;
|
||||
|
||||
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() {
|
||||
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()
|
||||
.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())
|
||||
.map_err(|_| format!("Could not read the contents of file `{}` to be included in the \
|
||||
shader source.",
|
||||
&canonical_file_to_include_string))?;
|
||||
|
||||
Ok(ResolvedInclude {
|
||||
resolved_name: canonical_file_to_include_string,
|
||||
content,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn compile(path: Option<String>, code: &str, ty: ShaderKind, include_directories: &[String]) -> Result<CompilationArtifact, String> {
|
||||
let mut compiler = Compiler::new().ok_or("failed to create GLSL compiler")?;
|
||||
let compile_options = CompileOptions::new().ok_or("failed to initialize compile option")?;
|
||||
let mut compile_options = CompileOptions::new()
|
||||
.ok_or("failed to initialize compile option")?;
|
||||
let root_source_path = if let &Some(ref path) = &path {
|
||||
path
|
||||
} else {
|
||||
// An arbitrary placeholder file name for embedded shaders
|
||||
"shader.glsl"
|
||||
};
|
||||
|
||||
// Specify file resolution callback for the `#include` directive
|
||||
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())
|
||||
});
|
||||
|
||||
let content = compiler
|
||||
.compile_into_spirv(&code, ty, "shader.glsl", "main", Some(&compile_options))
|
||||
.compile_into_spirv(&code, ty, root_source_path, "main", Some(&compile_options))
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(content)
|
||||
@ -265,7 +383,7 @@ 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("
|
||||
let comp = compile(None, "
|
||||
#version 450
|
||||
struct MyStruct {
|
||||
vec3 vs[2];
|
||||
@ -274,14 +392,14 @@ mod tests {
|
||||
MyStruct s;
|
||||
};
|
||||
void main() {}
|
||||
", ShaderKind::Vertex).unwrap();
|
||||
", ShaderKind::Vertex, &[]).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("
|
||||
let comp = compile(None, "
|
||||
#version 450
|
||||
struct MyStruct {
|
||||
vec4 vs[2];
|
||||
@ -290,7 +408,7 @@ mod tests {
|
||||
MyStruct s;
|
||||
};
|
||||
void main() {}
|
||||
", ShaderKind::Vertex).unwrap();
|
||||
", ShaderKind::Vertex, &[]).unwrap();
|
||||
let doc = parse::parse_spirv(comp.as_binary()).unwrap();
|
||||
structs::write_structs(&doc);
|
||||
}
|
||||
@ -298,7 +416,7 @@ 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("
|
||||
let comp = compile(None, "
|
||||
#version 450
|
||||
struct Vec3Wrap {
|
||||
vec3 v;
|
||||
@ -310,7 +428,7 @@ mod tests {
|
||||
MyStruct s;
|
||||
};
|
||||
void main() {}
|
||||
", ShaderKind::Vertex).unwrap();
|
||||
", ShaderKind::Vertex, &[]).unwrap();
|
||||
let doc = parse::parse_spirv(comp.as_binary()).unwrap();
|
||||
structs::write_structs(&doc);
|
||||
}
|
||||
|
@ -131,6 +131,14 @@
|
||||
//! Provides the path to the GLSL source to be compiled, relative to `Cargo.toml`.
|
||||
//! Cannot be used in conjunction with the `src` field.
|
||||
//!
|
||||
//! ## `include: ["...", "...", ..., "..."]`
|
||||
//!
|
||||
//! Specifies the standard include directories to be searched through when using the
|
||||
//! `#include <...>` directive within a shader source.
|
||||
//! 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.
|
||||
//!
|
||||
//! ## `dump: true`
|
||||
//!
|
||||
//! The crate fails to compile but prints the generated rust code to stdout.
|
||||
@ -158,7 +166,7 @@
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::io::{Read, Result as IoResult};
|
||||
use std::path::Path;
|
||||
|
||||
use syn::parse::{Parse, ParseStream, Result};
|
||||
@ -183,6 +191,7 @@ enum SourceKind {
|
||||
struct MacroInput {
|
||||
shader_kind: ShaderKind,
|
||||
source_kind: SourceKind,
|
||||
include_directories: Vec<String>,
|
||||
dump: bool,
|
||||
}
|
||||
|
||||
@ -191,6 +200,7 @@ impl Parse for MacroInput {
|
||||
let mut dump = None;
|
||||
let mut shader_kind = None;
|
||||
let mut source_kind = None;
|
||||
let mut include_directories = Vec::new();
|
||||
|
||||
while !input.is_empty() {
|
||||
let name: Ident = input.parse()?;
|
||||
@ -230,6 +240,20 @@ impl Parse for MacroInput {
|
||||
let path: LitStr = input.parse()?;
|
||||
source_kind = Some(SourceKind::Path(path.value()));
|
||||
}
|
||||
"include" => {
|
||||
let in_brackets;
|
||||
bracketed!(in_brackets in input);
|
||||
|
||||
while !in_brackets.is_empty() {
|
||||
let path: LitStr = in_brackets.parse()?;
|
||||
|
||||
include_directories.push(path.value());
|
||||
|
||||
if !in_brackets.is_empty() {
|
||||
in_brackets.parse::<Token![,]>()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
"dump" => {
|
||||
if dump.is_some() {
|
||||
panic!("Only one `dump` can be defined")
|
||||
@ -257,32 +281,36 @@ impl Parse for MacroInput {
|
||||
|
||||
let dump = dump.unwrap_or(false);
|
||||
|
||||
Ok(MacroInput { shader_kind, source_kind, dump })
|
||||
Ok(MacroInput { shader_kind, source_kind, include_directories, dump })
|
||||
}
|
||||
}
|
||||
|
||||
pub(self) fn read_file_to_string(full_path: &Path) -> IoResult<String> {
|
||||
let mut buf = String::new();
|
||||
File::open(full_path)
|
||||
.and_then(|mut file| file.read_to_string(&mut buf))?;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn shader(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let input = parse_macro_input!(input as MacroInput);
|
||||
|
||||
let source_code = match input.source_kind {
|
||||
SourceKind::Src(source) => source,
|
||||
SourceKind::Path(path) => {
|
||||
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);
|
||||
|
||||
if full_path.is_file() {
|
||||
let mut buf = String::new();
|
||||
File::open(full_path)
|
||||
.and_then(|mut file| file.read_to_string(&mut buf))
|
||||
.expect(&format!("Error reading source from {:?}", path));
|
||||
buf
|
||||
read_file_to_string(&full_path)
|
||||
.expect(&format!("Error reading source from {:?}", path))
|
||||
} else {
|
||||
panic!("File {:?} was not found ; note that the path must be relative to your Cargo.toml", path);
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let content = codegen::compile(&source_code, input.shader_kind).unwrap();
|
||||
let content = codegen::compile(path, &source_code, input.shader_kind, &input.include_directories).unwrap();
|
||||
codegen::reflect("Shader", content.as_binary(), input.dump).unwrap().into()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user