diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b11c4e8..20abe14a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased (Breaking) +- `vulkano_shaders::reflect` now returns `Result` instead of `Result` - Removed mir support, as it is being removed from the vulkan spec. - Remove vulkano_shaders::build_glsl_shaders - Split `PersistentDescriptorSetError::MissingUsage` into `MissingImageUsage` and `MissingBufferUsage` diff --git a/examples/src/bin/basic-compute-shader.rs b/examples/src/bin/basic-compute-shader.rs index f7252a42..7deeaaa3 100644 --- a/examples/src/bin/basic-compute-shader.rs +++ b/examples/src/bin/basic-compute-shader.rs @@ -153,7 +153,7 @@ void main() { // We need to signal a fence here because below we want to block the CPU until the GPU has // reached that point in the execution. .then_signal_fence_and_flush().unwrap(); - + // Blocks execution until the GPU has finished the operation. This method only exists on the // future that corresponds to a signalled fence. In other words, this method wouldn't be // available if we didn't call `.then_signal_fence_and_flush()` earlier. diff --git a/examples/src/bin/specialization-constants.rs b/examples/src/bin/specialization-constants.rs new file mode 100644 index 00000000..86026210 --- /dev/null +++ b/examples/src/bin/specialization-constants.rs @@ -0,0 +1,102 @@ +// Copyright (c) 2017 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +// TODO: Give a paragraph about what specialization are and what problems they solve +extern crate vulkano; +#[macro_use] +extern crate vulkano_shader_derive; + +use vulkano::buffer::BufferUsage; +use vulkano::buffer::CpuAccessibleBuffer; +use vulkano::command_buffer::AutoCommandBufferBuilder; +use vulkano::descriptor::descriptor_set::PersistentDescriptorSet; +use vulkano::device::Device; +use vulkano::device::DeviceExtensions; +use vulkano::instance::Instance; +use vulkano::instance::InstanceExtensions; +use vulkano::pipeline::ComputePipeline; +use vulkano::sync::now; +use vulkano::sync::GpuFuture; + +use std::sync::Arc; + +fn main() { + let instance = Instance::new(None, &InstanceExtensions::none(), None).unwrap(); + let physical = vulkano::instance::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()).expect("failed to create device") + }; + let queue = queues.next().unwrap(); + + mod cs { + #[derive(VulkanoShader)] + #[ty = "compute"] + #[src = " +#version 450 + +layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; +layout(constant_id = 0) const int multiple = 64; +layout(constant_id = 1) const float addend = 64; +layout(constant_id = 2) const bool enable = true; +const vec2 foo = vec2(0, 0); // TODO: How do I hit Instruction::SpecConstantComposite + +layout(set = 0, binding = 0) buffer Data { + uint data[]; +} data; + +void main() { + uint idx = gl_GlobalInvocationID.x; + if (enable) { + data.data[idx] *= multiple; + data.data[idx] += uint(addend); + } +}" +] + #[allow(dead_code)] + struct Dummy; + } + + let shader = cs::Shader::load(device.clone()) + .expect("failed to create shader module"); + let spec_consts = cs::SpecializationConstants { + enable: 1, + multiple: 1, + addend: 1.0, + }; + let pipeline = Arc::new(ComputePipeline::new(device.clone(), &shader.main_entry_point(), &spec_consts).unwrap()); + + let data_buffer = { + let data_iter = (0 .. 65536u32).map(|n| n); + CpuAccessibleBuffer::from_iter(device.clone(), BufferUsage::all(), + data_iter).expect("failed to create buffer") + }; + + 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 = 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().expect("failed to lock buffer for reading"); + for n in 0 .. 65536u32 { + assert_eq!(data_buffer_content[n as usize], n * 1 + 1); + } + println!("Success"); +} diff --git a/vulkano-shader-derive/Cargo.toml b/vulkano-shader-derive/Cargo.toml index 2d018678..4cbc7084 100644 --- a/vulkano-shader-derive/Cargo.toml +++ b/vulkano-shader-derive/Cargo.toml @@ -14,6 +14,8 @@ proc-macro = true [dependencies] syn = "0.15" +quote = "0.6" +proc-macro2 = "0.4" vulkano-shaders = { version = "0.10", path = "../vulkano-shaders" } [dev-dependencies] diff --git a/vulkano-shader-derive/src/lib.rs b/vulkano-shader-derive/src/lib.rs index 8403f158..7b780077 100644 --- a/vulkano-shader-derive/src/lib.rs +++ b/vulkano-shader-derive/src/lib.rs @@ -156,6 +156,8 @@ //! [pipeline]: https://docs.rs/vulkano/*/vulkano/pipeline/index.html extern crate proc_macro; +extern crate proc_macro2; +extern crate quote; extern crate syn; extern crate vulkano_shaders; @@ -164,15 +166,13 @@ use std::fs::File; use std::io::Read; use std::path::Path; -use proc_macro::TokenStream; - enum SourceKind { Src(String), Path(String), } #[proc_macro_derive(VulkanoShader, attributes(src, path, ty))] -pub fn derive(input: TokenStream) -> TokenStream { +pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let syn_item: syn::DeriveInput = syn::parse(input).unwrap(); let source_code = { @@ -244,8 +244,5 @@ pub fn derive(input: TokenStream) -> TokenStream { }; let content = vulkano_shaders::compile(&source_code, ty).unwrap(); - vulkano_shaders::reflect("Shader", content.as_binary()) - .unwrap() - .parse() - .unwrap() + vulkano_shaders::reflect("Shader", content.as_binary()).unwrap().into() } diff --git a/vulkano-shaders/Cargo.toml b/vulkano-shaders/Cargo.toml index d059b37c..ec1a9ca9 100644 --- a/vulkano-shaders/Cargo.toml +++ b/vulkano-shaders/Cargo.toml @@ -10,3 +10,6 @@ categories = ["rendering::graphics-api"] [dependencies] shaderc = "0.3" +syn = "0.15" +quote = "0.6" +proc-macro2 = "0.4" diff --git a/vulkano-shaders/examples/example.rs b/vulkano-shaders/examples/example.rs deleted file mode 100644 index fedcff2f..00000000 --- a/vulkano-shaders/examples/example.rs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) 2016 The vulkano developers -// Licensed under the Apache License, Version 2.0 -// or the MIT -// license , -// at your option. All files in the project carrying such -// notice may not be copied, modified, or distributed except -// according to those terms. - -extern crate vulkano_shaders; - -fn main() { - let shader = r#" -#version 450 - -layout(constant_id = 5) const int index = 2; - -struct S { - vec3 val1; - bool val2[5]; -}; - -layout(set = 0, binding = 0) uniform sampler2D u_texture; - -layout(set = 0, binding = 1) uniform Block { - S u_data; -} block; - -layout(location = 0) in vec2 v_texcoords; -layout(location = 0) out vec4 f_color; - -void main() { - if (block.u_data.val2[index]) { - f_color = texture(u_texture, v_texcoords); - } else { - f_color = vec4(1.0); - } -} - -"#; - - let content = vulkano_shaders::compile(shader, vulkano_shaders::ShaderKind::Fragment).unwrap(); - let output = vulkano_shaders::reflect("Shader", content.as_binary()).unwrap(); - println!("{}", output); -} diff --git a/vulkano-shaders/src/descriptor_sets.rs b/vulkano-shaders/src/descriptor_sets.rs index 261d378e..b8c5a46c 100644 --- a/vulkano-shaders/src/descriptor_sets.rs +++ b/vulkano-shaders/src/descriptor_sets.rs @@ -9,10 +9,13 @@ use std::cmp; -use enums; -use parse; +use proc_macro2::TokenStream; -pub fn write_descriptor_sets(doc: &parse::Spirv) -> String { +use enums::{Dim, Decoration, StorageClass, ImageFormat}; +use parse::{Instruction, Spirv}; +use spirv_search; + +pub fn write_descriptor_sets(doc: &Spirv) -> TokenStream { // TODO: not implemented correctly // Finding all the descriptors. @@ -20,42 +23,35 @@ pub fn write_descriptor_sets(doc: &parse::Spirv) -> String { struct Descriptor { set: u32, binding: u32, - desc_ty: String, + desc_ty: TokenStream, array_count: u64, readonly: bool, } // Looping to find all the elements that have the `DescriptorSet` decoration. for instruction in doc.instructions.iter() { - let (variable_id, descriptor_set) = match instruction { - &parse::Instruction::Decorate { - target_id, - decoration: enums::Decoration::DecorationDescriptorSet, - ref params, - } => { - (target_id, params[0]) - }, + let (variable_id, set) = match instruction { + &Instruction::Decorate { target_id, decoration: Decoration::DecorationDescriptorSet, ref params } + => (target_id, params[0]), _ => continue, }; // Find which type is pointed to by this variable. let pointed_ty = pointer_variable_ty(doc, variable_id); // Name of the variable. - let name = ::name_from_id(doc, variable_id); + let name = spirv_search::name_from_id(doc, variable_id); // Find the binding point of this descriptor. let binding = doc.instructions .iter() .filter_map(|i| { match i { - &parse::Instruction::Decorate { + &Instruction::Decorate { target_id, - decoration: enums::Decoration::DecorationBinding, + decoration: Decoration::DecorationBinding, ref params, - } if target_id == variable_id => { - Some(params[0]) - }, - _ => None, // TODO: other types + } if target_id == variable_id => Some(params[0]), + _ => None, // TODO: other types } }) .next() @@ -63,31 +59,20 @@ pub fn write_descriptor_sets(doc: &parse::Spirv) -> String { // Find information about the kind of binding for this descriptor. let (desc_ty, readonly, array_count) = descriptor_infos(doc, pointed_ty, false) - .expect(&format!("Couldn't find relevant type for uniform `{}` (type {}, maybe \ - unimplemented)", - name, - pointed_ty)); - - descriptors.push(Descriptor { - desc_ty: desc_ty, - set: descriptor_set, - binding: binding, - array_count: array_count, - readonly: readonly, - }); + .expect(&format!( + "Couldn't find relevant type for uniform `{}` (type {}, maybe unimplemented)", + name, + pointed_ty + )); + descriptors.push(Descriptor { desc_ty, set, binding, array_count, readonly }); } // Looping to find all the push constant structs. let mut push_constants_size = 0; for instruction in doc.instructions.iter() { let type_id = match instruction { - &parse::Instruction::TypePointer { - type_id, - storage_class: enums::StorageClass::StorageClassPushConstant, - .. - } => { - type_id - }, + &Instruction::TypePointer { type_id, storage_class: StorageClass::StorageClassPushConstant, .. } + => type_id, _ => continue, }; @@ -100,127 +85,106 @@ pub fn write_descriptor_sets(doc: &parse::Spirv) -> String { let descriptor_body = descriptors .iter() .map(|d| { - format!( - "({set}, {binding}) => Some(DescriptorDesc {{ - ty: {desc_ty}, - array_count: {array_count}, - stages: self.0.clone(), - readonly: {readonly}, - }}),", - set = d.set, - binding = d.binding, - desc_ty = d.desc_ty, - array_count = d.array_count, - readonly = if d.readonly { "true" } else { "false" } - ) - + let set = d.set as usize; + let binding = d.binding as usize; + let desc_ty = &d.desc_ty; + let array_count = d.array_count as u32; + let readonly = d.readonly; + quote!{ + (#set, #binding) => Some(DescriptorDesc { + ty: #desc_ty, + array_count: #array_count, + stages: self.0.clone(), + readonly: #readonly, + }), + } }) - .collect::>() - .concat(); + .collect::>(); - let num_sets = descriptors.iter().fold(0, |s, d| cmp::max(s, d.set + 1)); + let num_sets = descriptors.iter().fold(0, |s, d| cmp::max(s, d.set + 1)) as usize; // Writing the body of the `num_bindings_in_set` method. - let num_bindings_in_set_body = { - (0 .. num_sets) - .map(|set| { - let num = descriptors - .iter() - .filter(|d| d.set == set) - .fold(0, |s, d| cmp::max(s, 1 + d.binding)); - format!("{set} => Some({num}),", set = set, num = num) - }) - .collect::>() - .concat() - }; + let num_bindings_in_set_body = (0 .. num_sets) + .map(|set| { + let num = descriptors + .iter() + .filter(|d| d.set == set as u32) + .fold(0, |s, d| cmp::max(s, 1 + d.binding)) as usize; + quote!{ #set => Some(#num), } + }) + .collect::>(); // Writing the body of the `num_push_constants_ranges` method. - let num_push_constants_ranges_body = { - if push_constants_size == 0 { "0" } else { "1" } - }; + let num_push_constants_ranges_body = if push_constants_size == 0 { 0 } else { 1 } as usize; // Writing the body of the `push_constants_range` method. - let push_constants_range_body = format!( - r#" - if num != 0 || {pc_size} == 0 {{ return None; }} - Some(PipelineLayoutDescPcRange {{ - offset: 0, // FIXME: not necessarily true - size: {pc_size}, - stages: ShaderStages::all(), // FIXME: wrong - }}) - "#, - pc_size = push_constants_size + let push_constants_range_body = quote!( + if num != 0 || #push_constants_size == 0 { + None + } else { + Some(PipelineLayoutDescPcRange { + offset: 0, // FIXME: not necessarily true + size: #push_constants_size, + stages: ShaderStages::all(), // FIXME: wrong + }) + } ); - format!( - r#" + quote!{ #[derive(Debug, Clone)] pub struct Layout(pub ShaderStages); #[allow(unsafe_code)] - unsafe impl PipelineLayoutDesc for Layout {{ - fn num_sets(&self) -> usize {{ - {num_sets} - }} + unsafe impl PipelineLayoutDesc for Layout { + fn num_sets(&self) -> usize { + #num_sets + } - fn num_bindings_in_set(&self, set: usize) -> Option {{ - match set {{ - {num_bindings_in_set_body} + fn num_bindings_in_set(&self, set: usize) -> Option { + match set { + #( #num_bindings_in_set_body )* _ => None - }} - }} + } + } - fn descriptor(&self, set: usize, binding: usize) -> Option {{ - match (set, binding) {{ - {descriptor_body} + fn descriptor(&self, set: usize, binding: usize) -> Option { + match (set, binding) { + #( #descriptor_body )* _ => None - }} - }} + } + } - fn num_push_constants_ranges(&self) -> usize {{ - {num_push_constants_ranges_body} - }} + fn num_push_constants_ranges(&self) -> usize { + #num_push_constants_ranges_body + } - fn push_constants_range(&self, num: usize) -> Option {{ - {push_constants_range_body} - }} - }} - "#, - num_sets = num_sets, - num_bindings_in_set_body = num_bindings_in_set_body, - descriptor_body = descriptor_body, - num_push_constants_ranges_body = num_push_constants_ranges_body, - push_constants_range_body = push_constants_range_body - ) + fn push_constants_range(&self, num: usize) -> Option { + #push_constants_range_body + } + } + } } /// Assumes that `variable` is a variable with a `TypePointer` and returns the id of the pointed /// type. -fn pointer_variable_ty(doc: &parse::Spirv, variable: u32) -> u32 { +fn pointer_variable_ty(doc: &Spirv, variable: u32) -> u32 { let var_ty = doc.instructions .iter() .filter_map(|i| match i { - &parse::Instruction::Variable { - result_type_id, - result_id, - .. - } if result_id == variable => { - Some(result_type_id) - }, - _ => None, - }) + &Instruction::Variable { result_type_id, result_id, .. } + if result_id == variable => Some(result_type_id), + _ => None, + }) .next() .unwrap(); doc.instructions .iter() .filter_map(|i| match i { - &parse::Instruction::TypePointer { result_id, type_id, .. } - if result_id == var_ty => { - Some(type_id) - }, - _ => None, - }) + &Instruction::TypePointer { result_id, type_id, .. } + if result_id == var_ty => Some(type_id), + _ => None, + }) .next() .unwrap() } @@ -229,26 +193,21 @@ fn pointer_variable_ty(doc: &parse::Spirv, variable: u32) -> u32 { /// read-only, and the number of array elements. /// /// See also section 14.5.2 of the Vulkan specs: Descriptor Set Interface -fn descriptor_infos(doc: &parse::Spirv, pointed_ty: u32, force_combined_image_sampled: bool) - -> Option<(String, bool, u64)> { +fn descriptor_infos(doc: &Spirv, pointed_ty: u32, force_combined_image_sampled: bool) + -> Option<(TokenStream, bool, u64)> +{ doc.instructions.iter().filter_map(|i| { match i { - &parse::Instruction::TypeStruct { result_id, .. } if result_id == pointed_ty => { + &Instruction::TypeStruct { result_id, .. } if result_id == pointed_ty => { // Determine whether there's a Block or BufferBlock decoration. let is_ssbo = doc.instructions.iter().filter_map(|i| { match i { - &parse::Instruction::Decorate - { target_id, decoration: enums::Decoration::DecorationBufferBlock, .. } - if target_id == pointed_ty => - { - Some(true) - }, - &parse::Instruction::Decorate - { target_id, decoration: enums::Decoration::DecorationBlock, .. } - if target_id == pointed_ty => - { - Some(false) - }, + &Instruction::Decorate + { target_id, decoration: Decoration::DecorationBufferBlock, .. } + if target_id == pointed_ty => Some(true), + &Instruction::Decorate + { target_id, decoration: Decoration::DecorationBlock, .. } + if target_id == pointed_ty => Some(false), _ => None, } }).next().expect("Found a buffer uniform with neither the Block nor BufferBlock \ @@ -257,103 +216,111 @@ fn descriptor_infos(doc: &parse::Spirv, pointed_ty: u32, force_combined_image_sa // Determine whether there's a NonWritable decoration. //let non_writable = false; // TODO: tricky because the decoration is on struct members - let desc = format!("DescriptorDescTy::Buffer(DescriptorBufferDesc {{ - dynamic: Some(false), - storage: {}, - }})", if is_ssbo { "true" } else { "false "}); + let desc = quote!{ + DescriptorDescTy::Buffer(DescriptorBufferDesc { + dynamic: Some(false), + storage: #is_ssbo, + }) + }; Some((desc, true, 1)) - }, - - &parse::Instruction::TypeImage { result_id, ref dim, arrayed, ms, sampled, - ref format, .. } if result_id == pointed_ty => + } + &Instruction::TypeImage { result_id, ref dim, arrayed, ms, sampled, ref format, .. } + if result_id == pointed_ty => { let sampled = sampled.expect("Vulkan requires that variables of type OpTypeImage \ have a Sampled operand of 1 or 2"); - let ms = if ms { "true" } else { "false" }; - let arrayed = if arrayed { - "DescriptorImageDescArray::Arrayed { max_layers: None }" - } else { - "DescriptorImageDescArray::NonArrayed" + let arrayed = match arrayed { + true => quote!{ DescriptorImageDescArray::Arrayed { max_layers: None } }, + false => quote!{ DescriptorImageDescArray::NonArrayed } }; - if let &enums::Dim::DimSubpassData = dim { - // We are an input attachment. - assert!(!force_combined_image_sampled, "An OpTypeSampledImage can't point to \ - an OpTypeImage whose dimension is \ - SubpassData"); - assert!(if let &enums::ImageFormat::ImageFormatUnknown = format { true } - else { false }, "If Dim is SubpassData, Image Format must be Unknown"); - assert!(!sampled, "If Dim is SubpassData, Sampled must be 2"); + match dim { + Dim::DimSubpassData => { + // We are an input attachment. + assert!(!force_combined_image_sampled, "An OpTypeSampledImage can't point to \ + an OpTypeImage whose dimension is \ + SubpassData"); + assert!(if let &ImageFormat::ImageFormatUnknown = format { true } + else { false }, "If Dim is SubpassData, Image Format must be Unknown"); + assert!(!sampled, "If Dim is SubpassData, Sampled must be 2"); - let desc = format!("DescriptorDescTy::InputAttachment {{ - multisampled: {}, - array_layers: {} - }}", ms, arrayed); + let desc = quote!{ + DescriptorDescTy::InputAttachment { + multisampled: #ms, + array_layers: #arrayed + } + }; - Some((desc, true, 1)) + Some((desc, true, 1)) + } + Dim::DimBuffer => { + // We are a texel buffer. + let not_sampled = !sampled; + let desc = quote!{ + DescriptorDescTy::TexelBuffer { + storage: #not_sampled, + format: None, // TODO: specify format if known + } + }; - } else if let &enums::Dim::DimBuffer = dim { - // We are a texel buffer. - let desc = format!("DescriptorDescTy::TexelBuffer {{ - storage: {}, - format: None, // TODO: specify format if known - }}", !sampled); + Some((desc, true, 1)) + } + _ => { + // We are a sampled or storage image. + let ty = match force_combined_image_sampled { + true => quote!{ DescriptorDescTy::CombinedImageSampler }, + false => quote!{ DescriptorDescTy::Image } + }; + let dim = match *dim { + Dim::Dim1D => quote!{ DescriptorImageDescDimensions::OneDimensional }, + Dim::Dim2D => quote!{ DescriptorImageDescDimensions::TwoDimensional }, + Dim::Dim3D => quote!{ DescriptorImageDescDimensions::ThreeDimensional }, + Dim::DimCube => quote!{ DescriptorImageDescDimensions::Cube }, + Dim::DimRect => panic!("Vulkan doesn't support rectangle textures"), + _ => unreachable!() + }; - Some((desc, true, 1)) + let desc = quote!{ + #ty(DescriptorImageDesc { + sampled: #sampled, + dimensions: #dim, + format: None, // TODO: specify format if known + multisampled: #ms, + array_layers: #arrayed, + }) + }; - } else { - // We are a sampled or storage image. - let sampled = if sampled { "true" } else { "false" }; - let ty = if force_combined_image_sampled { "CombinedImageSampler" } - else { "Image" }; - let dim = match *dim { - enums::Dim::Dim1D => "DescriptorImageDescDimensions::OneDimensional", - enums::Dim::Dim2D => "DescriptorImageDescDimensions::TwoDimensional", - enums::Dim::Dim3D => "DescriptorImageDescDimensions::ThreeDimensional", - enums::Dim::DimCube => "DescriptorImageDescDimensions::Cube", - enums::Dim::DimRect => panic!("Vulkan doesn't support rectangle textures"), - _ => unreachable!() - }; - - let desc = format!("DescriptorDescTy::{}(DescriptorImageDesc {{ - sampled: {}, - dimensions: {}, - format: None, // TODO: specify format if known - multisampled: {}, - array_layers: {}, - }})", ty, sampled, dim, ms, arrayed); - - Some((desc, true, 1)) + Some((desc, true, 1)) + } } - }, + } - &parse::Instruction::TypeSampledImage { result_id, image_type_id } - if result_id == pointed_ty => - { - descriptor_infos(doc, image_type_id, true) - }, + &Instruction::TypeSampledImage { result_id, image_type_id } if result_id == pointed_ty + => descriptor_infos(doc, image_type_id, true), - &parse::Instruction::TypeSampler { result_id } if result_id == pointed_ty => { - let desc = format!("DescriptorDescTy::Sampler"); + &Instruction::TypeSampler { result_id } if result_id == pointed_ty => { + let desc = quote!{ DescriptorDescTy::Sampler }; Some((desc, true, 1)) - }, - - &parse::Instruction::TypeArray { result_id, type_id, length_id } if result_id == pointed_ty => { + } + &Instruction::TypeArray { result_id, type_id, length_id } if result_id == pointed_ty => { let (desc, readonly, arr) = match descriptor_infos(doc, type_id, false) { None => return None, Some(v) => v, }; assert_eq!(arr, 1); // TODO: implement? let len = doc.instructions.iter().filter_map(|e| { - match e { &parse::Instruction::Constant { result_id, ref data, .. } if result_id == length_id => Some(data.clone()), _ => None } + match e { + &Instruction::Constant { result_id, ref data, .. } + if result_id == length_id => Some(data.clone()), + _ => None + } }).next().expect("failed to find array length"); - let len = len.iter().rev().fold(0u64, |a, &b| (a << 32) | b as u64); + let len = len.iter().rev().fold(0, |a, &b| (a << 32) | b as u64); Some((desc, readonly, len)) - }, - - _ => None, // TODO: other types + } + _ => None, // TODO: other types } }).next() } diff --git a/vulkano-shaders/src/entry_point.rs b/vulkano-shaders/src/entry_point.rs index c9a89e9e..fe923a95 100644 --- a/vulkano-shaders/src/entry_point.rs +++ b/vulkano-shaders/src/entry_point.rs @@ -7,25 +7,17 @@ // notice may not be copied, modified, or distributed except // according to those terms. -use enums; -use parse; +use syn::Ident; +use proc_macro2::{Span, TokenStream}; -use format_from_id; -use is_builtin; -use location_decoration; -use name_from_id; +use enums::{StorageClass, ExecutionModel, ExecutionMode}; +use parse::{Instruction, Spirv}; +use spirv_search; -pub fn write_entry_point(doc: &parse::Spirv, instruction: &parse::Instruction) -> (String, String) { +pub fn write_entry_point(doc: &Spirv, instruction: &Instruction) -> (TokenStream, TokenStream) { let (execution, id, ep_name, interface) = match instruction { - &parse::Instruction::EntryPoint { - ref execution, - id, - ref name, - ref interface, - .. - } => { - (execution, id, name, interface) - }, + &Instruction::EntryPoint { ref execution, id, ref name, ref interface, .. } => + (execution, id, name, interface), _ => unreachable!(), }; @@ -36,230 +28,235 @@ pub fn write_entry_point(doc: &parse::Spirv, instruction: &parse::Instruction) - .chain(ep_name.chars().skip(1)) .collect(); - let interface_structs = - write_interface_structs(doc, - &capitalized_ep_name, - interface, - match *execution { - enums::ExecutionModel::ExecutionModelTessellationControl => - true, - enums::ExecutionModel::ExecutionModelTessellationEvaluation => - true, - enums::ExecutionModel::ExecutionModelGeometry => true, - _ => false, - }, - match *execution { - enums::ExecutionModel::ExecutionModelTessellationControl => - true, - _ => false, - }); + let ignore_first_array_in = match *execution { + ExecutionModel::ExecutionModelTessellationControl => true, + ExecutionModel::ExecutionModelTessellationEvaluation => true, + ExecutionModel::ExecutionModelGeometry => true, + _ => false, + }; + let ignore_first_array_out = match *execution { + ExecutionModel::ExecutionModelTessellationControl => true, + _ => false, + }; + + let interface_structs = write_interface_structs( + doc, + &capitalized_ep_name, + interface, + ignore_first_array_in, + ignore_first_array_out + ); let spec_consts_struct = if ::spec_consts::has_specialization_constants(doc) { - "SpecializationConstants" + quote!{ SpecializationConstants } } else { - "()" + quote!{ () } }; let (ty, f_call) = { - if let enums::ExecutionModel::ExecutionModelGLCompute = *execution { - (format!("::vulkano::pipeline::shader::ComputeEntryPoint<{}, Layout>", - spec_consts_struct), - format!("compute_entry_point(::std::ffi::CStr::from_ptr(NAME.as_ptr() as *const _), \ - Layout(ShaderStages {{ compute: true, .. ShaderStages::none() }}))")) - + if let ExecutionModel::ExecutionModelGLCompute = *execution { + ( + quote!{ ::vulkano::pipeline::shader::ComputeEntryPoint<#spec_consts_struct, Layout> }, + quote!{ compute_entry_point( + ::std::ffi::CStr::from_ptr(NAME.as_ptr() as *const _), + Layout(ShaderStages { compute: true, .. ShaderStages::none() }) + )} + ) } else { - let ty = match *execution { - enums::ExecutionModel::ExecutionModelVertex => { - "::vulkano::pipeline::shader::GraphicsShaderType::Vertex".to_owned() - }, + let entry_ty = match *execution { + ExecutionModel::ExecutionModelVertex => + quote!{ ::vulkano::pipeline::shader::GraphicsShaderType::Vertex }, - enums::ExecutionModel::ExecutionModelTessellationControl => { - "::vulkano::pipeline::shader::GraphicsShaderType::TessellationControl" - .to_owned() - }, + ExecutionModel::ExecutionModelTessellationControl => + quote!{ ::vulkano::pipeline::shader::GraphicsShaderType::TessellationControl }, - enums::ExecutionModel::ExecutionModelTessellationEvaluation => { - "::vulkano::pipeline::shader::GraphicsShaderType::TessellationEvaluation" - .to_owned() - }, + ExecutionModel::ExecutionModelTessellationEvaluation => + quote!{ ::vulkano::pipeline::shader::GraphicsShaderType::TessellationEvaluation }, - enums::ExecutionModel::ExecutionModelGeometry => { + ExecutionModel::ExecutionModelGeometry => { let mut execution_mode = None; for instruction in doc.instructions.iter() { - if let &parse::Instruction::ExecutionMode { - target_id, - ref mode, - .. - } = instruction - { - if target_id != id { - continue; + if let &Instruction::ExecutionMode { target_id, ref mode, .. } = instruction { + if target_id == id { + execution_mode = match mode { + &ExecutionMode::ExecutionModeInputPoints => Some(quote!{ Points }), + &ExecutionMode::ExecutionModeInputLines => Some(quote!{ Lines }), + &ExecutionMode::ExecutionModeInputLinesAdjacency => + Some(quote!{ LinesWithAdjacency }), + &ExecutionMode::ExecutionModeTriangles => Some(quote!{ Triangles }), + &ExecutionMode::ExecutionModeInputTrianglesAdjacency => + Some(quote!{ TrianglesWithAdjacency }), + _ => continue, + }; + break; } - execution_mode = match mode { - &enums::ExecutionMode::ExecutionModeInputPoints => Some("Points"), - &enums::ExecutionMode::ExecutionModeInputLines => Some("Lines"), - &enums::ExecutionMode::ExecutionModeInputLinesAdjacency => - Some("LinesWithAdjacency"), - &enums::ExecutionMode::ExecutionModeTriangles => Some("Triangles"), - &enums::ExecutionMode::ExecutionModeInputTrianglesAdjacency => - Some("TrianglesWithAdjacency"), - _ => continue, - }; - break; } } - format!( - "::vulkano::pipeline::shader::GraphicsShaderType::Geometry( - \ - ::vulkano::pipeline::shader::GeometryShaderExecutionMode::{0} - \ - )", - execution_mode.unwrap() - ) - }, + quote!{ + ::vulkano::pipeline::shader::GraphicsShaderType::Geometry( + ::vulkano::pipeline::shader::GeometryShaderExecutionMode::#execution_mode + ) + } + } - enums::ExecutionModel::ExecutionModelFragment => { - "::vulkano::pipeline::shader::GraphicsShaderType::Fragment".to_owned() - }, + ExecutionModel::ExecutionModelFragment => + quote!{ ::vulkano::pipeline::shader::GraphicsShaderType::Fragment }, - enums::ExecutionModel::ExecutionModelGLCompute => { - unreachable!() - }, + ExecutionModel::ExecutionModelGLCompute => unreachable!(), - enums::ExecutionModel::ExecutionModelKernel => panic!("Kernels are not supported"), + ExecutionModel::ExecutionModelKernel => panic!("Kernels are not supported"), }; let stage = match *execution { - enums::ExecutionModel::ExecutionModelVertex => { - "ShaderStages { vertex: true, .. ShaderStages::none() }" - }, - enums::ExecutionModel::ExecutionModelTessellationControl => { - "ShaderStages { tessellation_control: true, .. ShaderStages::none() }" - }, - enums::ExecutionModel::ExecutionModelTessellationEvaluation => { - "ShaderStages { tessellation_evaluation: true, .. ShaderStages::none() }" - }, - enums::ExecutionModel::ExecutionModelGeometry => { - "ShaderStages { geometry: true, .. ShaderStages::none() }" - }, - enums::ExecutionModel::ExecutionModelFragment => { - "ShaderStages { fragment: true, .. ShaderStages::none() }" - }, - enums::ExecutionModel::ExecutionModelGLCompute => unreachable!(), - enums::ExecutionModel::ExecutionModelKernel => unreachable!(), + ExecutionModel::ExecutionModelVertex => + quote!{ ShaderStages { vertex: true, .. ShaderStages::none() } }, + + ExecutionModel::ExecutionModelTessellationControl => + quote!{ ShaderStages { tessellation_control: true, .. ShaderStages::none() } }, + + ExecutionModel::ExecutionModelTessellationEvaluation => + quote!{ ShaderStages { tessellation_evaluation: true, .. ShaderStages::none() } }, + + ExecutionModel::ExecutionModelGeometry => + quote!{ ShaderStages { geometry: true, .. ShaderStages::none() } }, + + ExecutionModel::ExecutionModelFragment => + quote!{ ShaderStages { fragment: true, .. ShaderStages::none() } }, + + ExecutionModel::ExecutionModelGLCompute => unreachable!(), + ExecutionModel::ExecutionModelKernel => unreachable!(), }; - let t = format!("::vulkano::pipeline::shader::GraphicsEntryPoint<{0}, {1}Input, \ - {1}Output, Layout>", - spec_consts_struct, - capitalized_ep_name); - let f = format!("graphics_entry_point(::std::ffi::CStr::from_ptr(NAME.as_ptr() \ - as *const _), {0}Input, {0}Output, Layout({2}), {1})", - capitalized_ep_name, - ty, - stage); + let mut capitalized_ep_name_input = capitalized_ep_name.clone(); + capitalized_ep_name_input.push_str("Input"); + let capitalized_ep_name_input = Ident::new(&capitalized_ep_name_input, Span::call_site()); - (t, f) + let mut capitalized_ep_name_output = capitalized_ep_name.clone(); + capitalized_ep_name_output.push_str("Output"); + let capitalized_ep_name_output = Ident::new(&capitalized_ep_name_output, Span::call_site()); + + let ty = quote!{ + ::vulkano::pipeline::shader::GraphicsEntryPoint< + #spec_consts_struct, + #capitalized_ep_name_input, + #capitalized_ep_name_output, + Layout> + }; + let f_call = quote!{ + graphics_entry_point( + ::std::ffi::CStr::from_ptr(NAME.as_ptr() as *const _), + #capitalized_ep_name_input, + #capitalized_ep_name_output, + Layout(#stage), + #entry_ty + ) + }; + + (ty, f_call) } }; - let entry_point = format!( - r#" - /// Returns a logical struct describing the entry point named `{ep_name}`. - #[inline] - #[allow(unsafe_code)] - pub fn {ep_name}_entry_point(&self) -> {ty} {{ - unsafe {{ - #[allow(dead_code)] - static NAME: [u8; {ep_name_lenp1}] = [{encoded_ep_name}, 0]; // "{ep_name}" - self.shader.{f_call} - }} - }} - "#, - ep_name = ep_name, - ep_name_lenp1 = ep_name.chars().count() + 1, - ty = ty, - encoded_ep_name = ep_name - .chars() - .map(|c| (c as u32).to_string()) - .collect::>() - .join(", "), - f_call = f_call - ); + let mut method_name = ep_name.clone(); + method_name.push_str("_entry_point"); + let method_ident = Ident::new(&method_name, Span::call_site()); + + let ep_name_lenp1 = ep_name.chars().count() + 1; + let encoded_ep_name = ep_name.chars().map(|c| (c as u8)).collect::>(); + + let entry_point = quote!{ + /// Returns a logical struct describing the entry point named `{ep_name}`. + #[inline] + #[allow(unsafe_code)] + pub fn #method_ident(&self) -> #ty { + unsafe { + #[allow(dead_code)] + static NAME: [u8; #ep_name_lenp1] = [ #( #encoded_ep_name ),* , 0]; + self.shader.#f_call + } + } + }; (interface_structs, entry_point) } -fn write_interface_structs(doc: &parse::Spirv, capitalized_ep_name: &str, interface: &[u32], +struct Element { + location: u32, + name: String, + format: String, + location_len: usize, +} + +fn write_interface_structs(doc: &Spirv, capitalized_ep_name: &str, interface: &[u32], ignore_first_array_in: bool, ignore_first_array_out: bool) - -> String { - let mut input_elements = Vec::new(); - let mut output_elements = Vec::new(); + -> TokenStream { + let mut input_elements = vec!(); + let mut output_elements = vec!(); // Filling `input_elements` and `output_elements`. for interface in interface.iter() { for i in doc.instructions.iter() { match i { - &parse::Instruction::Variable { + &Instruction::Variable { result_type_id, result_id, ref storage_class, .. } if &result_id == interface => { - if is_builtin(doc, result_id) { + if spirv_search::is_builtin(doc, result_id) { continue; } let (to_write, ignore_first_array) = match storage_class { - &enums::StorageClass::StorageClassInput => (&mut input_elements, - ignore_first_array_in), - &enums::StorageClass::StorageClassOutput => (&mut output_elements, - ignore_first_array_out), + &StorageClass::StorageClassInput => + (&mut input_elements, ignore_first_array_in), + &StorageClass::StorageClassOutput => + (&mut output_elements, ignore_first_array_out), _ => continue, }; - let name = name_from_id(doc, result_id); + let name = spirv_search::name_from_id(doc, result_id); if name == "__unnamed" { continue; } // FIXME: hack - let loc = match location_decoration(doc, result_id) { + let location = match spirv_search::location_decoration(doc, result_id) { Some(l) => l, None => panic!("Attribute `{}` (id {}) is missing a location", name, result_id), }; - to_write - .push((loc, name, format_from_id(doc, result_type_id, ignore_first_array))); + let (format, location_len) = spirv_search::format_from_id(doc, result_type_id, ignore_first_array); + to_write.push(Element { location, name, format, location_len }); }, _ => (), } } } - write_interface_struct(&format!("{}Input", capitalized_ep_name), &input_elements) + - &write_interface_struct(&format!("{}Output", capitalized_ep_name), &output_elements) + let input: TokenStream = write_interface_struct(&format!("{}Input", capitalized_ep_name), &input_elements); + let output: TokenStream = write_interface_struct(&format!("{}Output", capitalized_ep_name), &output_elements); + quote!{ #input #output } } -fn write_interface_struct(struct_name: &str, attributes: &[(u32, String, (String, usize))]) - -> String { +fn write_interface_struct(struct_name_str: &str, attributes: &[Element]) -> TokenStream { // Checking for overlapping elements. - for (offset, &(loc, ref name, (_, loc_len))) in attributes.iter().enumerate() { - for &(loc2, ref name2, (_, loc_len2)) in attributes.iter().skip(offset + 1) { - if loc == loc2 || (loc < loc2 && loc + loc_len as u32 > loc2) || - (loc2 < loc && loc2 + loc_len2 as u32 > loc) + for (offset, element1) in attributes.iter().enumerate() { + for element2 in attributes.iter().skip(offset + 1) { + if element1.location == element2.location || + (element1.location < element2.location && element1.location + element1.location_len as u32 > element2.location) || + (element2.location < element1.location && element2.location + element2.location_len as u32 > element1.location) { panic!("The locations of attributes `{}` (start={}, size={}) \ - and `{}` (start={}, size={}) overlap", - name, - loc, - loc_len, - name2, - loc2, - loc_len2); + and `{}` (start={}, size={}) overlap", + element1.name, + element1.location, + element1.location_len, + element2.name, + element2.location, + element2.location_len); } } } @@ -267,73 +264,67 @@ fn write_interface_struct(struct_name: &str, attributes: &[(u32, String, (String let body = attributes .iter() .enumerate() - .map(|(num, &(loc, ref name, (ref ty, num_locs)))| { - assert!(num_locs >= 1); + .map(|(num, element)| { + assert!(element.location_len >= 1); + let loc = element.location; + let loc_end = element.location + element.location_len as u32; + let format = Ident::new(&element.format, Span::call_site()); + let name = &element.name; + let num = num as u16; - format!( - "if self.num == {} {{ - self.num += 1; + quote!{ + if self.num == #num { + self.num += 1; - return Some(::vulkano::pipeline::shader::ShaderInterfaceDefEntry {{ - location: {} .. {}, - format: ::vulkano::format::Format::{}, - name: Some(::std::borrow::Cow::Borrowed(\"{}\")) - }}); - }}", - num, - loc, - loc as usize + num_locs, - ty, - name - ) + return Some(::vulkano::pipeline::shader::ShaderInterfaceDefEntry { + location: #loc .. #loc_end, + format: ::vulkano::format::Format::#format, + name: Some(::std::borrow::Cow::Borrowed(#name)) + }); + } + } }) - .collect::>() - .join(""); + .collect::>(); - format!( - " + let struct_name = Ident::new(struct_name_str, Span::call_site()); + + let mut iter_name = struct_name.to_string(); + iter_name.push_str("Iter"); + let iter_name = Ident::new(&iter_name, Span::call_site()); + + let len = attributes.len(); + + quote!{ #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] - pub struct {name}; + pub struct #struct_name; - \ - #[allow(unsafe_code)] - unsafe impl ::vulkano::pipeline::shader::ShaderInterfaceDef for \ - {name} {{ - type Iter = {name}Iter; - fn elements(&self) -> {name}Iter {{ - \ - {name}Iter {{ num: 0 }} - }} - }} + #[allow(unsafe_code)] + unsafe impl ::vulkano::pipeline::shader::ShaderInterfaceDef for #struct_name { + type Iter = #iter_name; + fn elements(&self) -> #iter_name { + #iter_name { num: 0 } + } + } #[derive(Debug, Copy, Clone)] - \ - pub struct {name}Iter {{ num: u16 }} - impl Iterator for {name}Iter {{ - type \ - Item = ::vulkano::pipeline::shader::ShaderInterfaceDefEntry; + pub struct #iter_name { num: u16 } + + impl Iterator for #iter_name { + type Item = ::vulkano::pipeline::shader::ShaderInterfaceDefEntry; #[inline] - \ - fn next(&mut self) -> Option {{ - {body} + fn next(&mut self) -> Option { + #( #body )* None - \ - }} + } #[inline] - fn size_hint(&self) -> (usize, Option) {{ - \ - let len = ({len} - self.num) as usize; + fn size_hint(&self) -> (usize, Option) { + let len = #len - self.num as usize; (len, Some(len)) - }} - \ - }} + } + } - impl ExactSizeIterator for {name}Iter {{}} - ", - name = struct_name, - body = body, - len = attributes.len() - ) + impl ExactSizeIterator for #iter_name {} + } } diff --git a/vulkano-shaders/src/lib.rs b/vulkano-shaders/src/lib.rs index cfd9b0eb..596a6d12 100644 --- a/vulkano-shaders/src/lib.rs +++ b/vulkano-shaders/src/lib.rs @@ -7,21 +7,31 @@ // notice may not be copied, modified, or distributed except // according to those terms. -extern crate shaderc; +#![recursion_limit = "1024"] +#[macro_use] extern crate quote; + extern crate shaderc; + extern crate proc_macro2; + extern crate syn; use std::io::Error as IoError; +use syn::Ident; +use proc_macro2::{Span, TokenStream}; use shaderc::{Compiler, CompileOptions}; pub use shaderc::{CompilationArtifact, ShaderKind}; pub use parse::ParseError; +use parse::Instruction; +use enums::Capability; + mod descriptor_sets; mod entry_point; mod enums; mod parse; mod spec_consts; mod structs; +mod spirv_search; pub fn compile(code: &str, ty: ShaderKind) -> Result { let mut compiler = Compiler::new().ok_or("failed to create GLSL compiler")?; @@ -34,12 +44,41 @@ pub fn compile(code: &str, ty: ShaderKind) -> Result Result { +pub fn reflect(name: &str, spirv: &[u32]) -> Result { + let struct_name = Ident::new(&name, Span::call_site()); let doc = parse::parse_spirv(spirv)?; - let mut output = String::new(); - output.push_str( - r#" + // checking whether each required capability is enabled in the Vulkan device + let mut cap_checks: Vec = vec!(); + for i in doc.instructions.iter() { + if let &Instruction::Capability(ref cap) = i { + if let Some(cap_string) = capability_name(cap) { + let cap = Ident::new(cap_string, Span::call_site()); + cap_checks.push(quote!{ + if !device.enabled_features().#cap { + panic!("capability {:?} not enabled", #cap_string); // TODO: error + //return Err(CapabilityNotEnabled); + } + }); + } + } + } + + // writing one method for each entry point of this module + let mut entry_points_inside_impl: Vec = vec!(); + let mut entry_points_outside_impl: Vec = vec!(); + for instruction in doc.instructions.iter() { + if let &Instruction::EntryPoint { .. } = instruction { + let (outside, entry_point) = entry_point::write_entry_point(&doc, instruction); + entry_points_inside_impl.push(entry_point); + entry_points_outside_impl.push(outside); + } + } + + let structs = structs::write_structs(&doc); + let descriptor_sets = descriptor_sets::write_descriptor_sets(&doc); + let specialization_constants = spec_consts::write_specialization_constants(&doc); + let ast = quote!{ #[allow(unused_imports)] use std::sync::Arc; #[allow(unused_imports)] @@ -77,106 +116,51 @@ pub fn reflect(name: &str, spirv: &[u32]) -> Result { use vulkano::pipeline::shader::SpecializationConstants as SpecConstsTrait; #[allow(unused_imports)] use vulkano::pipeline::shader::SpecializationMapEntry; - "#, - ); - { - // contains the data that was passed as input to this function - let spirv_words = spirv.iter() - .map(|&word| word.to_string()) - .collect::>() - .join(", "); + pub struct #struct_name { + shader: ::std::sync::Arc<::vulkano::pipeline::shader::ShaderModule>, + } - // writing the header - output.push_str(&format!( - r#" -pub struct {name} {{ - shader: ::std::sync::Arc<::vulkano::pipeline::shader::ShaderModule>, -}} + impl #struct_name { + /// Loads the shader in Vulkan as a `ShaderModule`. + #[inline] + #[allow(unsafe_code)] + pub fn load(device: ::std::sync::Arc<::vulkano::device::Device>) + -> Result<#struct_name, ::vulkano::OomError> + { + #( #cap_checks )* + let words = [ #( #spirv ),* ]; -impl {name} {{ - /// Loads the shader in Vulkan as a `ShaderModule`. - #[inline] - #[allow(unsafe_code)] - pub fn load(device: ::std::sync::Arc<::vulkano::device::Device>) - -> Result<{name}, ::vulkano::OomError> - {{ - - "#, - name = name - )); - - // checking whether each required capability is enabled in the Vulkan device - for i in doc.instructions.iter() { - if let &parse::Instruction::Capability(ref cap) = i { - if let Some(cap) = capability_name(cap) { - output.push_str(&format!( - r#" - if !device.enabled_features().{cap} {{ - panic!("capability {{:?}} not enabled", "{cap}") // FIXME: error - //return Err(CapabilityNotEnabled); - }}"#, - cap = cap - )); + unsafe { + Ok(#struct_name { + shader: try!(::vulkano::pipeline::shader::ShaderModule::from_words(device, &words)) + }) } } - } - // follow-up of the header - output.push_str(&format!( - r#" - unsafe {{ - let words = [{spirv_words}]; - - Ok({name} {{ - shader: try!(::vulkano::pipeline::shader::ShaderModule::from_words(device, &words)) - }}) - }} - }} - - /// Returns the module that was created. - #[allow(dead_code)] - #[inline] - pub fn module(&self) -> &::std::sync::Arc<::vulkano::pipeline::shader::ShaderModule> {{ - &self.shader - }} - "#, - name = name, - spirv_words = spirv_words - )); - - // writing one method for each entry point of this module - let mut outside_impl = String::new(); - for instruction in doc.instructions.iter() { - if let &parse::Instruction::EntryPoint { .. } = instruction { - let (outside, entry_point) = entry_point::write_entry_point(&doc, instruction); - output.push_str(&entry_point); - outside_impl.push_str(&outside); + /// Returns the module that was created. + #[allow(dead_code)] + #[inline] + pub fn module(&self) -> &::std::sync::Arc<::vulkano::pipeline::shader::ShaderModule> { + &self.shader } + + #( #entry_points_inside_impl )* } - // footer - output.push_str(&format!( - r#" -}} - "# - )); + #( #entry_points_outside_impl )* - output.push_str(&outside_impl); + pub mod ty { + #structs + } - // struct definitions - output.push_str("pub mod ty {"); - output.push_str(&structs::write_structs(&doc)); - output.push_str("}"); + #descriptor_sets + #specialization_constants + }; - // descriptor sets - output.push_str(&descriptor_sets::write_descriptor_sets(&doc)); + //println!("{}", ast.to_string()); - // specialization constants - output.push_str(&spec_consts::write_specialization_constants(&doc)); - } - - Ok(output) + Ok(ast) } #[derive(Debug)] @@ -199,301 +183,78 @@ impl From for Error { } } -/// Returns the vulkano `Format` and number of occupied locations from an id. -/// -/// If `ignore_first_array` is true, the function expects the outermost instruction to be -/// `OpTypeArray`. If it's the case, the OpTypeArray will be ignored. If not, the function will -/// panic. -fn format_from_id(doc: &parse::Spirv, searched: u32, ignore_first_array: bool) -> (String, usize) { - for instruction in doc.instructions.iter() { - match instruction { - &parse::Instruction::TypeInt { - result_id, - width, - signedness, - } if result_id == searched => { - assert!(!ignore_first_array); - return (match (width, signedness) { - (8, true) => "R8Sint", - (8, false) => "R8Uint", - (16, true) => "R16Sint", - (16, false) => "R16Uint", - (32, true) => "R32Sint", - (32, false) => "R32Uint", - (64, true) => "R64Sint", - (64, false) => "R64Uint", - _ => panic!(), - }.to_owned(), - 1); - }, - &parse::Instruction::TypeFloat { result_id, width } if result_id == searched => { - assert!(!ignore_first_array); - return (match width { - 32 => "R32Sfloat", - 64 => "R64Sfloat", - _ => panic!(), - }.to_owned(), - 1); - }, - &parse::Instruction::TypeVector { - result_id, - component_id, - count, - } if result_id == searched => { - assert!(!ignore_first_array); - let (format, sz) = format_from_id(doc, component_id, false); - assert!(format.starts_with("R32")); - assert_eq!(sz, 1); - let format = if count == 1 { - format - } else if count == 2 { - format!("R32G32{}", &format[3 ..]) - } else if count == 3 { - format!("R32G32B32{}", &format[3 ..]) - } else if count == 4 { - format!("R32G32B32A32{}", &format[3 ..]) - } else { - panic!("Found vector type with more than 4 elements") - }; - return (format, sz); - }, - &parse::Instruction::TypeMatrix { - result_id, - column_type_id, - column_count, - } if result_id == searched => { - assert!(!ignore_first_array); - let (format, sz) = format_from_id(doc, column_type_id, false); - return (format, sz * column_count as usize); - }, - &parse::Instruction::TypeArray { - result_id, - type_id, - length_id, - } if result_id == searched => { - if ignore_first_array { - return format_from_id(doc, type_id, false); - } - - let (format, sz) = format_from_id(doc, type_id, false); - let len = doc.instructions - .iter() - .filter_map(|e| match e { - &parse::Instruction::Constant { - result_id, - ref data, - .. - } if result_id == length_id => Some(data.clone()), - _ => None, - }) - .next() - .expect("failed to find array length"); - let len = len.iter().rev().fold(0u64, |a, &b| (a << 32) | b as u64); - return (format, sz * len as usize); - }, - &parse::Instruction::TypePointer { result_id, type_id, .. } - if result_id == searched => { - return format_from_id(doc, type_id, ignore_first_array); - }, - _ => (), - } - } - - panic!("Type #{} not found or invalid", searched) -} - -fn name_from_id(doc: &parse::Spirv, searched: u32) -> String { - doc.instructions - .iter() - .filter_map(|i| if let &parse::Instruction::Name { - target_id, - ref name, - } = i - { - if target_id == searched { - Some(name.clone()) - } else { - None - } - } else { - None - }) - .next() - .and_then(|n| if !n.is_empty() { Some(n) } else { None }) - .unwrap_or("__unnamed".to_owned()) -} - -fn member_name_from_id(doc: &parse::Spirv, searched: u32, searched_member: u32) -> String { - doc.instructions - .iter() - .filter_map(|i| if let &parse::Instruction::MemberName { - target_id, - member, - ref name, - } = i - { - if target_id == searched && member == searched_member { - Some(name.clone()) - } else { - None - } - } else { - None - }) - .next() - .and_then(|n| if !n.is_empty() { Some(n) } else { None }) - .unwrap_or("__unnamed".to_owned()) -} - -fn location_decoration(doc: &parse::Spirv, searched: u32) -> Option { - doc.instructions - .iter() - .filter_map(|i| if let &parse::Instruction::Decorate { - target_id, - decoration: enums::Decoration::DecorationLocation, - ref params, - } = i - { - if target_id == searched { - Some(params[0]) - } else { - None - } - } else { - None - }) - .next() -} - -/// Returns true if a `BuiltIn` decorator is applied on an id. -fn is_builtin(doc: &parse::Spirv, id: u32) -> bool { - for instruction in &doc.instructions { - match *instruction { - parse::Instruction::Decorate { - target_id, - decoration: enums::Decoration::DecorationBuiltIn, - .. - } if target_id == id => { - return true; - }, - parse::Instruction::MemberDecorate { - target_id, - decoration: enums::Decoration::DecorationBuiltIn, - .. - } if target_id == id => { - return true; - }, - _ => (), - } - } - - for instruction in &doc.instructions { - match *instruction { - parse::Instruction::Variable { - result_type_id, - result_id, - .. - } if result_id == id => { - return is_builtin(doc, result_type_id); - }, - parse::Instruction::TypeArray { result_id, type_id, .. } if result_id == id => { - return is_builtin(doc, type_id); - }, - parse::Instruction::TypeRuntimeArray { result_id, type_id } if result_id == id => { - return is_builtin(doc, type_id); - }, - parse::Instruction::TypeStruct { - result_id, - ref member_types, - } if result_id == id => { - for &mem in member_types { - if is_builtin(doc, mem) { - return true; - } - } - }, - parse::Instruction::TypePointer { result_id, type_id, .. } if result_id == id => { - return is_builtin(doc, type_id); - }, - _ => (), - } - } - - false -} - /// Returns the name of the Vulkan something that corresponds to an `OpCapability`. /// /// Returns `None` if irrelevant. // TODO: this function is a draft, as the actual names may not be the same -fn capability_name(cap: &enums::Capability) -> Option<&'static str> { +fn capability_name(cap: &Capability) -> Option<&'static str> { match *cap { - enums::Capability::CapabilityMatrix => None, // always supported - enums::Capability::CapabilityShader => None, // always supported - enums::Capability::CapabilityGeometry => Some("geometry_shader"), - enums::Capability::CapabilityTessellation => Some("tessellation_shader"), - enums::Capability::CapabilityAddresses => panic!(), // not supported - enums::Capability::CapabilityLinkage => panic!(), // not supported - enums::Capability::CapabilityKernel => panic!(), // not supported - enums::Capability::CapabilityVector16 => panic!(), // not supported - enums::Capability::CapabilityFloat16Buffer => panic!(), // not supported - enums::Capability::CapabilityFloat16 => panic!(), // not supported - enums::Capability::CapabilityFloat64 => Some("shader_f3264"), - enums::Capability::CapabilityInt64 => Some("shader_int64"), - enums::Capability::CapabilityInt64Atomics => panic!(), // not supported - enums::Capability::CapabilityImageBasic => panic!(), // not supported - enums::Capability::CapabilityImageReadWrite => panic!(), // not supported - enums::Capability::CapabilityImageMipmap => panic!(), // not supported - enums::Capability::CapabilityPipes => panic!(), // not supported - enums::Capability::CapabilityGroups => panic!(), // not supported - enums::Capability::CapabilityDeviceEnqueue => panic!(), // not supported - enums::Capability::CapabilityLiteralSampler => panic!(), // not supported - enums::Capability::CapabilityAtomicStorage => panic!(), // not supported - enums::Capability::CapabilityInt16 => Some("shader_int16"), - enums::Capability::CapabilityTessellationPointSize => + Capability::CapabilityMatrix => None, // always supported + Capability::CapabilityShader => None, // always supported + Capability::CapabilityGeometry => Some("geometry_shader"), + Capability::CapabilityTessellation => Some("tessellation_shader"), + Capability::CapabilityAddresses => panic!(), // not supported + Capability::CapabilityLinkage => panic!(), // not supported + Capability::CapabilityKernel => panic!(), // not supported + Capability::CapabilityVector16 => panic!(), // not supported + Capability::CapabilityFloat16Buffer => panic!(), // not supported + Capability::CapabilityFloat16 => panic!(), // not supported + Capability::CapabilityFloat64 => Some("shader_f3264"), + Capability::CapabilityInt64 => Some("shader_int64"), + Capability::CapabilityInt64Atomics => panic!(), // not supported + Capability::CapabilityImageBasic => panic!(), // not supported + Capability::CapabilityImageReadWrite => panic!(), // not supported + Capability::CapabilityImageMipmap => panic!(), // not supported + Capability::CapabilityPipes => panic!(), // not supported + Capability::CapabilityGroups => panic!(), // not supported + Capability::CapabilityDeviceEnqueue => panic!(), // not supported + Capability::CapabilityLiteralSampler => panic!(), // not supported + Capability::CapabilityAtomicStorage => panic!(), // not supported + Capability::CapabilityInt16 => Some("shader_int16"), + Capability::CapabilityTessellationPointSize => Some("shader_tessellation_and_geometry_point_size"), - enums::Capability::CapabilityGeometryPointSize => + Capability::CapabilityGeometryPointSize => Some("shader_tessellation_and_geometry_point_size"), - enums::Capability::CapabilityImageGatherExtended => Some("shader_image_gather_extended"), - enums::Capability::CapabilityStorageImageMultisample => + Capability::CapabilityImageGatherExtended => Some("shader_image_gather_extended"), + Capability::CapabilityStorageImageMultisample => Some("shader_storage_image_multisample"), - enums::Capability::CapabilityUniformBufferArrayDynamicIndexing => + Capability::CapabilityUniformBufferArrayDynamicIndexing => Some("shader_uniform_buffer_array_dynamic_indexing"), - enums::Capability::CapabilitySampledImageArrayDynamicIndexing => + Capability::CapabilitySampledImageArrayDynamicIndexing => Some("shader_sampled_image_array_dynamic_indexing"), - enums::Capability::CapabilityStorageBufferArrayDynamicIndexing => + Capability::CapabilityStorageBufferArrayDynamicIndexing => Some("shader_storage_buffer_array_dynamic_indexing"), - enums::Capability::CapabilityStorageImageArrayDynamicIndexing => + Capability::CapabilityStorageImageArrayDynamicIndexing => Some("shader_storage_image_array_dynamic_indexing"), - enums::Capability::CapabilityClipDistance => Some("shader_clip_distance"), - enums::Capability::CapabilityCullDistance => Some("shader_cull_distance"), - enums::Capability::CapabilityImageCubeArray => Some("image_cube_array"), - enums::Capability::CapabilitySampleRateShading => Some("sample_rate_shading"), - enums::Capability::CapabilityImageRect => panic!(), // not supported - enums::Capability::CapabilitySampledRect => panic!(), // not supported - enums::Capability::CapabilityGenericPointer => panic!(), // not supported - enums::Capability::CapabilityInt8 => panic!(), // not supported - enums::Capability::CapabilityInputAttachment => None, // always supported - enums::Capability::CapabilitySparseResidency => Some("shader_resource_residency"), - enums::Capability::CapabilityMinLod => Some("shader_resource_min_lod"), - enums::Capability::CapabilitySampled1D => None, // always supported - enums::Capability::CapabilityImage1D => None, // always supported - enums::Capability::CapabilitySampledCubeArray => Some("image_cube_array"), - enums::Capability::CapabilitySampledBuffer => None, // always supported - enums::Capability::CapabilityImageBuffer => None, // always supported - enums::Capability::CapabilityImageMSArray => Some("shader_storage_image_multisample"), - enums::Capability::CapabilityStorageImageExtendedFormats => + Capability::CapabilityClipDistance => Some("shader_clip_distance"), + Capability::CapabilityCullDistance => Some("shader_cull_distance"), + Capability::CapabilityImageCubeArray => Some("image_cube_array"), + Capability::CapabilitySampleRateShading => Some("sample_rate_shading"), + Capability::CapabilityImageRect => panic!(), // not supported + Capability::CapabilitySampledRect => panic!(), // not supported + Capability::CapabilityGenericPointer => panic!(), // not supported + Capability::CapabilityInt8 => panic!(), // not supported + Capability::CapabilityInputAttachment => None, // always supported + Capability::CapabilitySparseResidency => Some("shader_resource_residency"), + Capability::CapabilityMinLod => Some("shader_resource_min_lod"), + Capability::CapabilitySampled1D => None, // always supported + Capability::CapabilityImage1D => None, // always supported + Capability::CapabilitySampledCubeArray => Some("image_cube_array"), + Capability::CapabilitySampledBuffer => None, // always supported + Capability::CapabilityImageBuffer => None, // always supported + Capability::CapabilityImageMSArray => Some("shader_storage_image_multisample"), + Capability::CapabilityStorageImageExtendedFormats => Some("shader_storage_image_extended_formats"), - enums::Capability::CapabilityImageQuery => None, // always supported - enums::Capability::CapabilityDerivativeControl => None, // always supported - enums::Capability::CapabilityInterpolationFunction => Some("sample_rate_shading"), - enums::Capability::CapabilityTransformFeedback => panic!(), // not supported - enums::Capability::CapabilityGeometryStreams => panic!(), // not supported - enums::Capability::CapabilityStorageImageReadWithoutFormat => + Capability::CapabilityImageQuery => None, // always supported + Capability::CapabilityDerivativeControl => None, // always supported + Capability::CapabilityInterpolationFunction => Some("sample_rate_shading"), + Capability::CapabilityTransformFeedback => panic!(), // not supported + Capability::CapabilityGeometryStreams => panic!(), // not supported + Capability::CapabilityStorageImageReadWithoutFormat => Some("shader_storage_image_read_without_format"), - enums::Capability::CapabilityStorageImageWriteWithoutFormat => + Capability::CapabilityStorageImageWriteWithoutFormat => Some("shader_storage_image_write_without_format"), - enums::Capability::CapabilityMultiViewport => Some("multi_viewport"), + Capability::CapabilityMultiViewport => Some("multi_viewport"), } } diff --git a/vulkano-shaders/src/spec_consts.rs b/vulkano-shaders/src/spec_consts.rs index bd9638f5..bde48823 100644 --- a/vulkano-shaders/src/spec_consts.rs +++ b/vulkano-shaders/src/spec_consts.rs @@ -9,17 +9,22 @@ use std::mem; -use enums; -use parse; +use syn::Ident; +use proc_macro2::{Span, TokenStream}; + +use enums::Decoration; +use parse::{Instruction, Spirv}; +use spirv_search; +use structs; /// Returns true if the document has specialization constants. -pub fn has_specialization_constants(doc: &parse::Spirv) -> bool { +pub fn has_specialization_constants(doc: &Spirv) -> bool { for instruction in doc.instructions.iter() { match instruction { - &parse::Instruction::SpecConstantTrue { .. } => return true, - &parse::Instruction::SpecConstantFalse { .. } => return true, - &parse::Instruction::SpecConstant { .. } => return true, - &parse::Instruction::SpecConstantComposite { .. } => return true, + &Instruction::SpecConstantTrue { .. } => return true, + &Instruction::SpecConstantFalse { .. } => return true, + &Instruction::SpecConstant { .. } => return true, + &Instruction::SpecConstantComposite { .. } => return true, _ => (), } } @@ -29,56 +34,38 @@ pub fn has_specialization_constants(doc: &parse::Spirv) -> bool { /// Writes the `SpecializationConstants` struct that contains the specialization constants and /// implements the `Default` and the `vulkano::pipeline::shader::SpecializationConstants` traits. -pub fn write_specialization_constants(doc: &parse::Spirv) -> String { +pub fn write_specialization_constants(doc: &Spirv) -> TokenStream { struct SpecConst { name: String, constant_id: u32, - rust_ty: String, + rust_ty: TokenStream, rust_size: usize, - rust_alignment: usize, - default_value: String, + rust_alignment: u32, + default_value: TokenStream, } let mut spec_consts = Vec::new(); for instruction in doc.instructions.iter() { let (type_id, result_id, default_value) = match instruction { - &parse::Instruction::SpecConstantTrue { - result_type_id, - result_id, - } => { - (result_type_id, result_id, "1u32".to_string()) - }, - &parse::Instruction::SpecConstantFalse { - result_type_id, - result_id, - } => { - (result_type_id, result_id, "0u32".to_string()) - }, - &parse::Instruction::SpecConstant { - result_type_id, - result_id, - ref data, - } => { - let data = data.iter() - .map(|d| d.to_string() + "u32") - .collect::>() - .join(", "); - let def_val = format!("unsafe {{ ::std::mem::transmute([{}]) }}", data); + &Instruction::SpecConstantTrue { result_type_id, result_id } => + (result_type_id, result_id, quote!{1u32}), + + &Instruction::SpecConstantFalse { result_type_id, result_id } => + (result_type_id, result_id, quote!{0u32}), + + &Instruction::SpecConstant { result_type_id, result_id, ref data } => { + let def_val = quote!{ + unsafe {{ ::std::mem::transmute([ #( #data ),* ]) }} + }; (result_type_id, result_id, def_val) - }, - &parse::Instruction::SpecConstantComposite { - result_type_id, - result_id, - ref data, - } => { - let data = data.iter() - .map(|d| d.to_string() + "u32") - .collect::>() - .join(", "); - let def_val = format!("unsafe {{ ::std::mem::transmute([{}]) }}", data); + } + &Instruction::SpecConstantComposite { result_type_id, result_id, ref data } => { + let def_val = quote!{ + unsafe {{ ::std::mem::transmute([ #( #data ),* ]) }} + }; (result_type_id, result_id, def_val) - }, + } _ => continue, }; @@ -88,105 +75,93 @@ pub fn write_specialization_constants(doc: &parse::Spirv) -> String { let constant_id = doc.instructions .iter() .filter_map(|i| match i { - &parse::Instruction::Decorate { - target_id, - decoration: enums::Decoration::DecorationSpecId, - ref params, - } if target_id == result_id => { - Some(params[0]) - }, - _ => None, - }) + &Instruction::Decorate { target_id, decoration: Decoration::DecorationSpecId, ref params } + if target_id == result_id => Some(params[0]), + _ => None, + }) .next() .expect("Found a specialization constant with no SpecId decoration"); spec_consts.push(SpecConst { - name: ::name_from_id(doc, result_id), - constant_id, - rust_ty, - rust_size, - rust_alignment, - default_value, - }); + name: spirv_search::name_from_id(doc, result_id), + constant_id, + rust_ty, + rust_size, + rust_alignment: rust_alignment as u32, + default_value, + }); } let map_entries = { let mut map_entries = Vec::new(); let mut curr_offset = 0; - for c in &spec_consts { - map_entries.push(format!( - "SpecializationMapEntry {{ - constant_id: \ - {}, - offset: {}, - size: {}, - \ - }}", - c.constant_id, - curr_offset, - c.rust_size - )); + for spec_const in &spec_consts { + let constant_id = spec_const.constant_id; + let rust_size = spec_const.rust_size; + map_entries.push(quote!{ + SpecializationMapEntry { + constant_id: #constant_id, + offset: #curr_offset, + size: #rust_size, + } + }); - assert_ne!(c.rust_size, 0); - curr_offset += c.rust_size; - curr_offset = c.rust_alignment * (1 + (curr_offset - 1) / c.rust_alignment); + assert_ne!(spec_const.rust_size, 0); + curr_offset += spec_const.rust_size as u32; + curr_offset = spec_const.rust_alignment * (1 + (curr_offset - 1) / spec_const.rust_alignment); } map_entries }; - format!( - r#" + let num_map_entries = map_entries.len(); -#[derive(Debug, Copy, Clone)] -#[allow(non_snake_case)] -#[repr(C)] -pub struct SpecializationConstants {{ - {struct_def} -}} + let mut struct_members = vec!(); + let mut struct_member_defaults = vec!(); + for spec_const in spec_consts { + let name = Ident::new(&spec_const.name, Span::call_site()); + let rust_ty = spec_const.rust_ty; + let default_value = spec_const.default_value; + struct_members.push(quote!{ pub #name: #rust_ty }); + struct_member_defaults.push(quote!{ #name: #default_value }); + } -impl Default for SpecializationConstants {{ - fn default() -> SpecializationConstants {{ - SpecializationConstants {{ - {def_vals} - }} - }} -}} + quote!{ + #[derive(Debug, Copy, Clone)] + #[allow(non_snake_case)] + #[repr(C)] + pub struct SpecializationConstants { + #( #struct_members ),* + } -unsafe impl SpecConstsTrait for SpecializationConstants {{ - fn descriptors() -> &'static [SpecializationMapEntry] {{ - static DESCRIPTORS: [SpecializationMapEntry; {num_map_entries}] = [ - {map_entries} - ]; - &DESCRIPTORS - }} -}} + impl Default for SpecializationConstants { + fn default() -> SpecializationConstants { + SpecializationConstants { + #( #struct_member_defaults ),* + } + } + } - "#, - struct_def = spec_consts - .iter() - .map(|c| format!("pub {}: {}", c.name, c.rust_ty)) - .collect::>() - .join(", "), - def_vals = spec_consts - .iter() - .map(|c| format!("{}: {}", c.name, c.default_value)) - .collect::>() - .join(", "), - num_map_entries = map_entries.len(), - map_entries = map_entries.join(", ") - ) + unsafe impl SpecConstsTrait for SpecializationConstants { + fn descriptors() -> &'static [SpecializationMapEntry] { + static DESCRIPTORS: [SpecializationMapEntry; #num_map_entries] = [ + #( #map_entries ),* + ]; + &DESCRIPTORS + } + } + } } // Wrapper around `type_from_id` that also handles booleans. -fn spec_const_type_from_id(doc: &parse::Spirv, searched: u32) -> (String, Option, usize) { +fn spec_const_type_from_id(doc: &Spirv, searched: u32) -> (TokenStream, Option, usize) { for instruction in doc.instructions.iter() { match instruction { - &parse::Instruction::TypeBool { result_id } if result_id == searched => { - return ("u32".to_owned(), Some(mem::size_of::()), mem::align_of::()); + &Instruction::TypeBool { result_id } if result_id == searched => { + return (quote!{u32}, Some(mem::size_of::()), mem::align_of::()); }, _ => (), } } - ::structs::type_from_id(doc, searched) + structs::type_from_id(doc, searched) } diff --git a/vulkano-shaders/src/spirv_search.rs b/vulkano-shaders/src/spirv_search.rs new file mode 100644 index 00000000..06306cbb --- /dev/null +++ b/vulkano-shaders/src/spirv_search.rs @@ -0,0 +1,190 @@ +// Copyright (c) 2016 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use parse::{Instruction, Spirv}; +use enums::Decoration; + +/// Returns the vulkano `Format` and number of occupied locations from an id. +/// +/// If `ignore_first_array` is true, the function expects the outermost instruction to be +/// `OpTypeArray`. If it's the case, the OpTypeArray will be ignored. If not, the function will +/// panic. +pub fn format_from_id(doc: &Spirv, searched: u32, ignore_first_array: bool) -> (String, usize) { + for instruction in doc.instructions.iter() { + match instruction { + &Instruction::TypeInt { + result_id, + width, + signedness, + } if result_id == searched => { + assert!(!ignore_first_array); + let format = match (width, signedness) { + (8, true) => "R8Sint", + (8, false) => "R8Uint", + (16, true) => "R16Sint", + (16, false) => "R16Uint", + (32, true) => "R32Sint", + (32, false) => "R32Uint", + (64, true) => "R64Sint", + (64, false) => "R64Uint", + _ => panic!(), + }; + return (format.to_string(), 1); + }, + &Instruction::TypeFloat { result_id, width } if result_id == searched => { + assert!(!ignore_first_array); + let format = match width { + 32 => "R32Sfloat", + 64 => "R64Sfloat", + _ => panic!(), + }; + return (format.to_string(), 1); + } + &Instruction::TypeVector { + result_id, + component_id, + count, + } if result_id == searched => { + assert!(!ignore_first_array); + let (format, sz) = format_from_id(doc, component_id, false); + assert!(format.starts_with("R32")); + assert_eq!(sz, 1); + let format = match count { + 1 => format, + 2 => format!("R32G32{}", &format[3 ..]), + 3 => format!("R32G32B32{}", &format[3 ..]), + 4 => format!("R32G32B32A32{}", &format[3 ..]), + _ => panic!("Found vector type with more than 4 elements") + }; + return (format, sz); + }, + &Instruction::TypeMatrix { + result_id, + column_type_id, + column_count, + } if result_id == searched => { + assert!(!ignore_first_array); + let (format, sz) = format_from_id(doc, column_type_id, false); + return (format, sz * column_count as usize); + }, + &Instruction::TypeArray { + result_id, + type_id, + length_id, + } if result_id == searched => { + if ignore_first_array { + return format_from_id(doc, type_id, false); + } + + let (format, sz) = format_from_id(doc, type_id, false); + let len = doc.instructions + .iter() + .filter_map(|e| match e { + &Instruction::Constant { result_id, ref data, .. } + if result_id == length_id => Some(data.clone()), + _ => None, + }) + .next() + .expect("failed to find array length"); + let len = len.iter().rev().fold(0u64, |a, &b| (a << 32) | b as u64); + return (format, sz * len as usize); + }, + &Instruction::TypePointer { result_id, type_id, .. } + if result_id == searched => { + return format_from_id(doc, type_id, ignore_first_array); + }, + _ => (), + } + } + + panic!("Type #{} not found or invalid", searched) +} + +pub fn name_from_id(doc: &Spirv, searched: u32) -> String { + for instruction in &doc.instructions { + if let &Instruction::Name { target_id, ref name } = instruction { + if target_id == searched { + return name.clone() + } + } + } + + String::from("__unnamed") +} + +pub fn member_name_from_id(doc: &Spirv, searched: u32, searched_member: u32) -> String { + for instruction in &doc.instructions { + if let &Instruction::MemberName { target_id, member, ref name } = instruction { + if target_id == searched && member == searched_member { + return name.clone() + } + } + } + + String::from("__unnamed") +} + +pub fn location_decoration(doc: &Spirv, searched: u32) -> Option { + for instruction in &doc.instructions { + if let &Instruction::Decorate { target_id, decoration: Decoration::DecorationLocation, ref params } = instruction { + if target_id == searched { + return Some(params[0]) + } + } + } + + None +} + +/// Returns true if a `BuiltIn` decorator is applied on an id. +pub fn is_builtin(doc: &Spirv, id: u32) -> bool { + for instruction in &doc.instructions { + match *instruction { + Instruction::Decorate { target_id, decoration: Decoration::DecorationBuiltIn, .. } + if target_id == id => { return true } + Instruction::MemberDecorate { target_id, decoration: Decoration::DecorationBuiltIn, .. } + if target_id == id => { return true } + _ => (), + } + } + + for instruction in &doc.instructions { + match *instruction { + Instruction::Variable { + result_type_id, + result_id, + .. + } if result_id == id => { + return is_builtin(doc, result_type_id); + } + Instruction::TypeArray { result_id, type_id, .. } if result_id == id => { + return is_builtin(doc, type_id); + } + Instruction::TypeRuntimeArray { result_id, type_id } if result_id == id => { + return is_builtin(doc, type_id); + } + Instruction::TypeStruct { + result_id, + ref member_types, + } if result_id == id => { + for &mem in member_types { + if is_builtin(doc, mem) { + return true; + } + } + } + Instruction::TypePointer { result_id, type_id, .. } if result_id == id => { + return is_builtin(doc, type_id); + } + _ => () + } + } + + false +} diff --git a/vulkano-shaders/src/structs.rs b/vulkano-shaders/src/structs.rs index 9354ac12..cd2f68ac 100644 --- a/vulkano-shaders/src/structs.rs +++ b/vulkano-shaders/src/structs.rs @@ -9,55 +9,38 @@ use std::mem; -use enums; -use parse; +use syn::Ident; +use proc_macro2::{Span, TokenStream}; + +use parse::{Instruction, Spirv}; +use enums::Decoration; +use spirv_search; /// Translates all the structs that are contained in the SPIR-V document as Rust structs. -pub fn write_structs(doc: &parse::Spirv) -> String { - let mut result = String::new(); - +pub fn write_structs(doc: &Spirv) -> TokenStream { + let mut structs = vec!(); for instruction in &doc.instructions { match *instruction { - parse::Instruction::TypeStruct { - result_id, - ref member_types, - } => { - let (s, _) = write_struct(doc, result_id, member_types); - result.push_str(&s); - result.push_str("\n"); - }, - _ => (), + Instruction::TypeStruct { result_id, ref member_types } => + structs.push(write_struct(doc, result_id, member_types).0), + _ => () } } - result -} - -/// Represents a rust struct member -struct Member { - name: String, - value: String, - offset: Option, -} - -impl Member { - fn declaration_text(&self) -> String { - let offset = match self.offset { - Some(o) => format!("/* offset: {} */", o), - _ => "".to_owned(), - }; - format!(" pub {}: {} {}", self.name, self.value, offset) - } - fn copy_text(&self) -> String { - format!(" {name}: self.{name}", name = self.name) + quote!{ + #( #structs )* } } /// Analyzes a single struct, returns a string containing its Rust definition, plus its size. -fn write_struct(doc: &parse::Spirv, struct_id: u32, members: &[u32]) -> (String, Option) { - let name = ::name_from_id(doc, struct_id); +fn write_struct(doc: &Spirv, struct_id: u32, members: &[u32]) -> (TokenStream, Option) { + let name = Ident::new(&spirv_search::name_from_id(doc, struct_id), Span::call_site()); // The members of this struct. + struct Member { + pub name: Ident, + pub ty: TokenStream, + } let mut rust_members = Vec::with_capacity(members.len()); // Padding structs will be named `_paddingN` where `N` is determined by this variable. @@ -70,12 +53,12 @@ fn write_struct(doc: &parse::Spirv, struct_id: u32, members: &[u32]) -> (String, for (num, &member) in members.iter().enumerate() { // Compute infos about the member. let (ty, rust_size, rust_align) = type_from_id(doc, member); - let member_name = ::member_name_from_id(doc, struct_id, num as u32); + let member_name = spirv_search::member_name_from_id(doc, struct_id, num as u32); // Ignore the whole struct is a member is built in, which includes // `gl_Position` for example. if is_builtin_member(doc, struct_id, num as u32) { - return (String::new(), None); // TODO: is this correct? shouldn't it return a correct struct but with a flag or something? + return (quote!{}, None); // TODO: is this correct? shouldn't it return a correct struct but with a flag or something? } // Finding offset of the current member, as requested by the SPIR-V code. @@ -83,10 +66,10 @@ fn write_struct(doc: &parse::Spirv, struct_id: u32, members: &[u32]) -> (String, .iter() .filter_map(|i| { match *i { - parse::Instruction::MemberDecorate { + Instruction::MemberDecorate { target_id, member, - decoration: enums::Decoration::DecorationOffset, + decoration: Decoration::DecorationOffset, ref params, } if target_id == struct_id && member as usize == num => { return Some(params[0]); @@ -102,7 +85,7 @@ fn write_struct(doc: &parse::Spirv, struct_id: u32, members: &[u32]) -> (String, // variables only. Ignoring these. let spirv_offset = match spirv_offset { Some(o) => o as usize, - None => return (String::new(), None), // TODO: shouldn't we return and let the caller ignore it instead? + None => return (quote!{}, None), // TODO: shouldn't we return and let the caller ignore it instead? }; // We need to add a dummy field if necessary. @@ -124,10 +107,9 @@ fn write_struct(doc: &parse::Spirv, struct_id: u32, members: &[u32]) -> (String, let padding_num = next_padding_num; next_padding_num += 1; rust_members.push(Member { - name: format!("_dummy{}", padding_num), - value: format!("[u8; {}]", diff), - offset: None, - }); + name: Ident::new(&format!("_dummy{}", padding_num), Span::call_site()), + ty: quote!{ [u8; #diff] }, + }); *current_rust_offset += diff; } } @@ -140,29 +122,28 @@ fn write_struct(doc: &parse::Spirv, struct_id: u32, members: &[u32]) -> (String, } rust_members.push(Member { - name: member_name.to_owned(), - value: ty, - offset: Some(spirv_offset), - }); + name: Ident::new(&member_name, Span::call_site()), + ty, + }); } // Try determine the total size of the struct in order to add padding at the end of the struct. let spirv_req_total_size = doc.instructions .iter() .filter_map(|i| match *i { - parse::Instruction::Decorate { + Instruction::Decorate { target_id, - decoration: enums::Decoration::DecorationArrayStride, + decoration: Decoration::DecorationArrayStride, ref params, } => { for inst in doc.instructions.iter() { match *inst { - parse::Instruction::TypeArray { + Instruction::TypeArray { result_id, type_id, .. } if result_id == target_id && type_id == struct_id => { return Some(params[0]); }, - parse::Instruction::TypeRuntimeArray { result_id, type_id } + Instruction::TypeRuntimeArray { result_id, type_id } if result_id == target_id && type_id == struct_id => { return Some(params[0]); }, @@ -186,58 +167,63 @@ fn write_struct(doc: &parse::Spirv, struct_id: u32, members: &[u32]) -> (String, let diff = req_size.checked_sub(cur_size as u32).unwrap(); if diff >= 1 { rust_members.push(Member { - name: format!("_dummy{}", next_padding_num), - value: format!("[u8; {}]", diff), - offset: None, - }); + name: Ident::new(&format!("_dummy{}", next_padding_num), Span::call_site()), + ty: quote!{ [u8; {}] }, + }); } } // We can only implement Clone if there's no unsized member in the struct. - let (impl_text, derive_text) = if current_rust_offset.is_some() { - let i = format!("\nimpl Clone for {name} {{\n fn clone(&self) -> Self {{\n \ - {name} {{\n{copies}\n }}\n }}\n}}\n", - name = name, - copies = rust_members - .iter() - .map(Member::copy_text) - .collect::>() - .join(",\n")); - (i, "#[derive(Copy)]") + let (clone_impl, copy_derive) = if current_rust_offset.is_some() { + let mut copies = vec!(); + for member in &rust_members { + let name = &member.name; + copies.push(quote!{ #name: self.#name, }); + } + ( + // Clone is implemented manually because members can be large arrays + // that do not implement Clone, but do implement Copy + quote!{ + impl Clone for #name { + fn clone(&self) -> Self { + #name { + #( #copies )* + } + } + } + }, + quote!{ #[derive(Copy)] } + ) } else { - ("".to_owned(), "") + (quote!{}, quote!{}) }; - let s = - format!("#[repr(C)]\n{derive_text}\n#[allow(non_snake_case)]\npub struct {name} \ - {{\n{members}\n}} /* total_size: {t:?} */\n{impl_text}", - name = name, - members = rust_members - .iter() - .map(Member::declaration_text) - .collect::>() - .join(",\n"), - t = spirv_req_total_size, - impl_text = impl_text, - derive_text = derive_text); - (s, - spirv_req_total_size - .map(|sz| sz as usize) - .or(current_rust_offset)) + let mut members = vec!(); + for member in &rust_members { + let name = &member.name; + let ty = &member.ty; + members.push(quote!(pub #name: #ty,)); + } + + let ast = quote! { + #[repr(C)] + #copy_derive + #[allow(non_snake_case)] + pub struct #name { + #( #members )* + } + #clone_impl + }; + + (ast, spirv_req_total_size.map(|sz| sz as usize).or(current_rust_offset)) } /// Returns true if a `BuiltIn` decorator is applied on a struct member. -fn is_builtin_member(doc: &parse::Spirv, id: u32, member_id: u32) -> bool { +fn is_builtin_member(doc: &Spirv, id: u32, member_id: u32) -> bool { for instruction in &doc.instructions { match *instruction { - parse::Instruction::MemberDecorate { - target_id, - member, - decoration: enums::Decoration::DecorationBuiltIn, - .. - } if target_id == id && member == member_id => { - return true; - }, + Instruction::MemberDecorate { target_id, member, decoration: Decoration::DecorationBuiltIn, .. } + if target_id == id && member == member_id => { return true } _ => (), } } @@ -248,17 +234,13 @@ fn is_builtin_member(doc: &parse::Spirv, id: u32, member_id: u32) -> bool { /// Returns the type name to put in the Rust struct, and its size and alignment. /// /// The size can be `None` if it's only known at runtime. -pub fn type_from_id(doc: &parse::Spirv, searched: u32) -> (String, Option, usize) { +pub fn type_from_id(doc: &Spirv, searched: u32) -> (TokenStream, Option, usize) { for instruction in doc.instructions.iter() { match instruction { - &parse::Instruction::TypeBool { result_id } if result_id == searched => { + &Instruction::TypeBool { result_id } if result_id == searched => { panic!("Can't put booleans in structs") - }, - &parse::Instruction::TypeInt { - result_id, - width, - signedness, - } if result_id == searched => { + } + &Instruction::TypeInt { result_id, width, signedness } if result_id == searched => { match (width, signedness) { (8, true) => { #[repr(C)] @@ -267,7 +249,7 @@ pub fn type_from_id(doc: &parse::Spirv, searched: u32) -> (String, Option after: u8, } let size = unsafe { (&(&*(0 as *const Foo)).after) as *const u8 as usize }; - return ("i8".to_owned(), Some(size), mem::align_of::()); + return (quote!{i8}, Some(size), mem::align_of::()); }, (8, false) => { #[repr(C)] @@ -276,7 +258,7 @@ pub fn type_from_id(doc: &parse::Spirv, searched: u32) -> (String, Option after: u8, } let size = unsafe { (&(&*(0 as *const Foo)).after) as *const u8 as usize }; - return ("u8".to_owned(), Some(size), mem::align_of::()); + return (quote!{u8}, Some(size), mem::align_of::()); }, (16, true) => { #[repr(C)] @@ -285,7 +267,7 @@ pub fn type_from_id(doc: &parse::Spirv, searched: u32) -> (String, Option after: u8, } let size = unsafe { (&(&*(0 as *const Foo)).after) as *const u8 as usize }; - return ("i16".to_owned(), Some(size), mem::align_of::()); + return (quote!{i16}, Some(size), mem::align_of::()); }, (16, false) => { #[repr(C)] @@ -294,7 +276,7 @@ pub fn type_from_id(doc: &parse::Spirv, searched: u32) -> (String, Option after: u8, } let size = unsafe { (&(&*(0 as *const Foo)).after) as *const u8 as usize }; - return ("u16".to_owned(), Some(size), mem::align_of::()); + return (quote!{u16}, Some(size), mem::align_of::()); }, (32, true) => { #[repr(C)] @@ -303,7 +285,7 @@ pub fn type_from_id(doc: &parse::Spirv, searched: u32) -> (String, Option after: u8, } let size = unsafe { (&(&*(0 as *const Foo)).after) as *const u8 as usize }; - return ("i32".to_owned(), Some(size), mem::align_of::()); + return (quote!{i32}, Some(size), mem::align_of::()); }, (32, false) => { #[repr(C)] @@ -312,7 +294,7 @@ pub fn type_from_id(doc: &parse::Spirv, searched: u32) -> (String, Option after: u8, } let size = unsafe { (&(&*(0 as *const Foo)).after) as *const u8 as usize }; - return ("u32".to_owned(), Some(size), mem::align_of::()); + return (quote!{u32}, Some(size), mem::align_of::()); }, (64, true) => { #[repr(C)] @@ -321,7 +303,7 @@ pub fn type_from_id(doc: &parse::Spirv, searched: u32) -> (String, Option after: u8, } let size = unsafe { (&(&*(0 as *const Foo)).after) as *const u8 as usize }; - return ("i64".to_owned(), Some(size), mem::align_of::()); + return (quote!{i64}, Some(size), mem::align_of::()); }, (64, false) => { #[repr(C)] @@ -330,12 +312,12 @@ pub fn type_from_id(doc: &parse::Spirv, searched: u32) -> (String, Option after: u8, } let size = unsafe { (&(&*(0 as *const Foo)).after) as *const u8 as usize }; - return ("u64".to_owned(), Some(size), mem::align_of::()); + return (quote!{u64}, Some(size), mem::align_of::()); }, _ => panic!("No Rust equivalent for an integer of width {}", width), } - }, - &parse::Instruction::TypeFloat { result_id, width } if result_id == searched => { + } + &Instruction::TypeFloat { result_id, width } if result_id == searched => { match width { 32 => { #[repr(C)] @@ -344,7 +326,7 @@ pub fn type_from_id(doc: &parse::Spirv, searched: u32) -> (String, Option after: u8, } let size = unsafe { (&(&*(0 as *const Foo)).after) as *const u8 as usize }; - return ("f32".to_owned(), Some(size), mem::align_of::()); + return (quote!{f32}, Some(size), mem::align_of::()); }, 64 => { #[repr(C)] @@ -353,61 +335,58 @@ pub fn type_from_id(doc: &parse::Spirv, searched: u32) -> (String, Option after: u8, } let size = unsafe { (&(&*(0 as *const Foo)).after) as *const u8 as usize }; - return ("f64".to_owned(), Some(size), mem::align_of::()); + return (quote!{f64}, Some(size), mem::align_of::()); }, _ => panic!("No Rust equivalent for a floating-point of width {}", width), } - }, - &parse::Instruction::TypeVector { + } + &Instruction::TypeVector { result_id, component_id, count, } if result_id == searched => { debug_assert_eq!(mem::align_of::<[u32; 3]>(), mem::align_of::()); - let (t, t_size, t_align) = type_from_id(doc, component_id); - return (format!("[{}; {}]", t, count), t_size.map(|s| s * count as usize), t_align); - }, - &parse::Instruction::TypeMatrix { + let (ty, t_size, t_align) = type_from_id(doc, component_id); + let array_length = count as usize; + let size = t_size.map(|s| s * count as usize); + return (quote!{ [#ty; #array_length] }, size, t_align); + } + &Instruction::TypeMatrix { result_id, column_type_id, column_count, } if result_id == searched => { // FIXME: row-major or column-major debug_assert_eq!(mem::align_of::<[u32; 3]>(), mem::align_of::()); - let (t, t_size, t_align) = type_from_id(doc, column_type_id); - return (format!("[{}; {}]", t, column_count), - t_size.map(|s| s * column_count as usize), - t_align); - }, - &parse::Instruction::TypeArray { + let (ty, t_size, t_align) = type_from_id(doc, column_type_id); + let array_length = column_count as usize; + let size = t_size.map(|s| s * column_count as usize); + return (quote!{ [#ty; #array_length] }, size, t_align); + } + &Instruction::TypeArray { result_id, type_id, length_id, } if result_id == searched => { debug_assert_eq!(mem::align_of::<[u32; 3]>(), mem::align_of::()); - let (t, t_size, t_align) = type_from_id(doc, type_id); + let (ty, t_size, t_align) = type_from_id(doc, type_id); let t_size = t_size.expect("array components must be sized"); let len = doc.instructions .iter() .filter_map(|e| match e { - &parse::Instruction::Constant { - result_id, - ref data, - .. - } if result_id == length_id => Some(data.clone()), - _ => None, - }) + &Instruction::Constant { result_id, ref data, .. } + if result_id == length_id => Some(data.clone()), + _ => None, + }) .next() .expect("failed to find array length"); let len = len.iter().rev().fold(0u64, |a, &b| (a << 32) | b as u64); let stride = doc.instructions.iter().filter_map(|e| match e { - parse::Instruction::Decorate{ + Instruction::Decorate { target_id, - decoration: enums::Decoration::DecorationArrayStride, + decoration: Decoration::DecorationArrayStride, ref params, - } if *target_id == searched => { - Some(params[0]) - }, + } if *target_id == searched => Some(params[0]), _ => None, }) .next().expect("failed to find ArrayStride decoration"); @@ -417,27 +396,30 @@ pub fn type_from_id(doc: &parse::Spirv, searched: u32) -> (String, Option the array element in a struct or rounding up the size of a vector or matrix \ (e.g. increase a vec3 to a vec4)") } - return (format!("[{}; {}]", t, len), Some(t_size * len as usize), t_align); - }, - &parse::Instruction::TypeRuntimeArray { result_id, type_id } + let array_length = len as usize; + let size = Some(t_size * len as usize); + return (quote!{ [#ty; #array_length] }, size, t_align); + } + &Instruction::TypeRuntimeArray { result_id, type_id } if result_id == searched => { debug_assert_eq!(mem::align_of::<[u32; 3]>(), mem::align_of::()); - let (t, _, t_align) = type_from_id(doc, type_id); - return (format!("[{}]", t), None, t_align); - }, - &parse::Instruction::TypeStruct { + let (ty, _, t_align) = type_from_id(doc, type_id); + return (quote!{ [#ty] }, None, t_align); + } + &Instruction::TypeStruct { result_id, ref member_types, } if result_id == searched => { // TODO: take the Offset member decorate into account? - let name = ::name_from_id(doc, result_id); + let name = Ident::new(&spirv_search::name_from_id(doc, result_id), Span::call_site()); + let ty = quote!{ #name }; let (_, size) = write_struct(doc, result_id, member_types); let align = member_types .iter() .map(|&t| type_from_id(doc, t).2) .max() .unwrap_or(1); - return (name, size, align); + return (ty, size, align); }, _ => (), }