vulkano-shaders cleanup (#1058)

Use syn to construct ast instead of raw strings
Move spirv searching methods from lib.rs into its own module
Improve formatting
This commit is contained in:
Lucas Kent 2018-10-05 17:00:02 +10:00 committed by GitHub
parent b6c4d84e61
commit d779829cbd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1051 additions and 1125 deletions

View File

@ -1,5 +1,6 @@
# Unreleased (Breaking)
- `vulkano_shaders::reflect` now returns `Result<proc_macro2::TokenStream, Error>` instead of `Result<String, Error>`
- 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`

View File

@ -0,0 +1,102 @@
// 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.
// 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");
}

View File

@ -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]

View File

@ -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()
}

View File

@ -10,3 +10,6 @@ categories = ["rendering::graphics-api"]
[dependencies]
shaderc = "0.3"
syn = "0.15"
quote = "0.6"
proc-macro2 = "0.4"

View File

@ -1,45 +0,0 @@
// Copyright (c) 2016 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.
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);
}

View File

@ -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::<Vec<_>>()
.concat();
.collect::<Vec<_>>();
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::<Vec<_>>()
.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::<Vec<_>>();
// 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<usize> {{
match set {{
{num_bindings_in_set_body}
fn num_bindings_in_set(&self, set: usize) -> Option<usize> {
match set {
#( #num_bindings_in_set_body )*
_ => None
}}
}}
}
}
fn descriptor(&self, set: usize, binding: usize) -> Option<DescriptorDesc> {{
match (set, binding) {{
{descriptor_body}
fn descriptor(&self, set: usize, binding: usize) -> Option<DescriptorDesc> {
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<PipelineLayoutDescPcRange> {{
{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<PipelineLayoutDescPcRange> {
#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()
}

View File

@ -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::<Vec<String>>()
.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::<Vec<_>>();
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::<Vec<_>>()
.join("");
.collect::<Vec<_>>();
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<Self::Item> {{
{body}
fn next(&mut self) -> Option<Self::Item> {
#( #body )*
None
\
}}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {{
\
let len = ({len} - self.num) as usize;
fn size_hint(&self) -> (usize, Option<usize>) {
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 {}
}
}

View File

@ -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<CompilationArtifact, String> {
let mut compiler = Compiler::new().ok_or("failed to create GLSL compiler")?;
@ -34,12 +44,41 @@ pub fn compile(code: &str, ty: ShaderKind) -> Result<CompilationArtifact, String
Ok(content)
}
pub fn reflect(name: &str, spirv: &[u32]) -> Result<String, Error> {
pub fn reflect(name: &str, spirv: &[u32]) -> Result<TokenStream, Error> {
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<TokenStream> = 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<TokenStream> = vec!();
let mut entry_points_outside_impl: Vec<TokenStream> = 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<String, Error> {
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::<Vec<String>>()
.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<ParseError> 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<u32> {
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"),
}
}

View File

@ -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::<Vec<_>>()
.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::<Vec<_>>()
.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::<Vec<_>>()
.join(", "),
def_vals = spec_consts
.iter()
.map(|c| format!("{}: {}", c.name, c.default_value))
.collect::<Vec<_>>()
.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>, usize) {
fn spec_const_type_from_id(doc: &Spirv, searched: u32) -> (TokenStream, Option<usize>, 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::<u32>()), mem::align_of::<u32>());
&Instruction::TypeBool { result_id } if result_id == searched => {
return (quote!{u32}, Some(mem::size_of::<u32>()), mem::align_of::<u32>());
},
_ => (),
}
}
::structs::type_from_id(doc, searched)
structs::type_from_id(doc, searched)
}

View File

@ -0,0 +1,190 @@
// Copyright (c) 2016 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.
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<u32> {
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
}

View File

@ -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<usize>,
}
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<usize>) {
let name = ::name_from_id(doc, struct_id);
fn write_struct(doc: &Spirv, struct_id: u32, members: &[u32]) -> (TokenStream, Option<usize>) {
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::<Vec<_>>()
.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::<Vec<_>>()
.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>, usize) {
pub fn type_from_id(doc: &Spirv, searched: u32) -> (TokenStream, Option<usize>, 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<usize>
after: u8,
}
let size = unsafe { (&(&*(0 as *const Foo)).after) as *const u8 as usize };
return ("i8".to_owned(), Some(size), mem::align_of::<Foo>());
return (quote!{i8}, Some(size), mem::align_of::<Foo>());
},
(8, false) => {
#[repr(C)]
@ -276,7 +258,7 @@ pub fn type_from_id(doc: &parse::Spirv, searched: u32) -> (String, Option<usize>
after: u8,
}
let size = unsafe { (&(&*(0 as *const Foo)).after) as *const u8 as usize };
return ("u8".to_owned(), Some(size), mem::align_of::<Foo>());
return (quote!{u8}, Some(size), mem::align_of::<Foo>());
},
(16, true) => {
#[repr(C)]
@ -285,7 +267,7 @@ pub fn type_from_id(doc: &parse::Spirv, searched: u32) -> (String, Option<usize>
after: u8,
}
let size = unsafe { (&(&*(0 as *const Foo)).after) as *const u8 as usize };
return ("i16".to_owned(), Some(size), mem::align_of::<Foo>());
return (quote!{i16}, Some(size), mem::align_of::<Foo>());
},
(16, false) => {
#[repr(C)]
@ -294,7 +276,7 @@ pub fn type_from_id(doc: &parse::Spirv, searched: u32) -> (String, Option<usize>
after: u8,
}
let size = unsafe { (&(&*(0 as *const Foo)).after) as *const u8 as usize };
return ("u16".to_owned(), Some(size), mem::align_of::<Foo>());
return (quote!{u16}, Some(size), mem::align_of::<Foo>());
},
(32, true) => {
#[repr(C)]
@ -303,7 +285,7 @@ pub fn type_from_id(doc: &parse::Spirv, searched: u32) -> (String, Option<usize>
after: u8,
}
let size = unsafe { (&(&*(0 as *const Foo)).after) as *const u8 as usize };
return ("i32".to_owned(), Some(size), mem::align_of::<Foo>());
return (quote!{i32}, Some(size), mem::align_of::<Foo>());
},
(32, false) => {
#[repr(C)]
@ -312,7 +294,7 @@ pub fn type_from_id(doc: &parse::Spirv, searched: u32) -> (String, Option<usize>
after: u8,
}
let size = unsafe { (&(&*(0 as *const Foo)).after) as *const u8 as usize };
return ("u32".to_owned(), Some(size), mem::align_of::<Foo>());
return (quote!{u32}, Some(size), mem::align_of::<Foo>());
},
(64, true) => {
#[repr(C)]
@ -321,7 +303,7 @@ pub fn type_from_id(doc: &parse::Spirv, searched: u32) -> (String, Option<usize>
after: u8,
}
let size = unsafe { (&(&*(0 as *const Foo)).after) as *const u8 as usize };
return ("i64".to_owned(), Some(size), mem::align_of::<Foo>());
return (quote!{i64}, Some(size), mem::align_of::<Foo>());
},
(64, false) => {
#[repr(C)]
@ -330,12 +312,12 @@ pub fn type_from_id(doc: &parse::Spirv, searched: u32) -> (String, Option<usize>
after: u8,
}
let size = unsafe { (&(&*(0 as *const Foo)).after) as *const u8 as usize };
return ("u64".to_owned(), Some(size), mem::align_of::<Foo>());
return (quote!{u64}, Some(size), mem::align_of::<Foo>());
},
_ => 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<usize>
after: u8,
}
let size = unsafe { (&(&*(0 as *const Foo)).after) as *const u8 as usize };
return ("f32".to_owned(), Some(size), mem::align_of::<Foo>());
return (quote!{f32}, Some(size), mem::align_of::<Foo>());
},
64 => {
#[repr(C)]
@ -353,61 +335,58 @@ pub fn type_from_id(doc: &parse::Spirv, searched: u32) -> (String, Option<usize>
after: u8,
}
let size = unsafe { (&(&*(0 as *const Foo)).after) as *const u8 as usize };
return ("f64".to_owned(), Some(size), mem::align_of::<Foo>());
return (quote!{f64}, Some(size), mem::align_of::<Foo>());
},
_ => 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::<u32>());
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::<u32>());
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::<u32>());
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<usize>
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::<u32>());
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);
},
_ => (),
}