mirror of
https://github.com/vulkano-rs/vulkano.git
synced 2024-11-21 22:34:43 +00:00
Add auto-generated SPIR-V parsing (#1701)
* Add auto-generated SPIR-V parsing * Add further reflection utilities for decorations, names and members, clean up Vulkano-shaders
This commit is contained in:
parent
ce110006bc
commit
8d321430cb
@ -128,13 +128,9 @@ impl AmbientLightingSystem {
|
||||
.unwrap();
|
||||
let mut descriptor_set_builder = PersistentDescriptorSet::start(layout.clone());
|
||||
|
||||
descriptor_set_builder
|
||||
.add_image(color_input)
|
||||
.unwrap();
|
||||
descriptor_set_builder.add_image(color_input).unwrap();
|
||||
|
||||
let descriptor_set = descriptor_set_builder
|
||||
.build()
|
||||
.unwrap();
|
||||
let descriptor_set = descriptor_set_builder.build().unwrap();
|
||||
|
||||
let viewport = Viewport {
|
||||
origin: [0.0, 0.0],
|
||||
|
@ -145,9 +145,7 @@ impl DirectionalLightingSystem {
|
||||
.add_image(normals_input)
|
||||
.unwrap();
|
||||
|
||||
let descriptor_set = descriptor_set_builder
|
||||
.build()
|
||||
.unwrap();
|
||||
let descriptor_set = descriptor_set_builder.build().unwrap();
|
||||
|
||||
let viewport = Viewport {
|
||||
origin: [0.0, 0.0],
|
||||
|
@ -159,9 +159,7 @@ impl PointLightingSystem {
|
||||
.add_image(depth_input)
|
||||
.unwrap();
|
||||
|
||||
let descriptor_set = descriptor_set_builder
|
||||
.build()
|
||||
.unwrap();
|
||||
let descriptor_set = descriptor_set_builder.build().unwrap();
|
||||
|
||||
let viewport = Viewport {
|
||||
origin: [0.0, 0.0],
|
||||
|
@ -227,15 +227,9 @@ fn main() {
|
||||
let layout = pipeline.layout().descriptor_set_layouts().get(0).unwrap();
|
||||
let mut set_builder = PersistentDescriptorSet::start(layout.clone());
|
||||
|
||||
set_builder
|
||||
.add_buffer(uniform_buffer_subbuffer)
|
||||
.unwrap();
|
||||
set_builder.add_buffer(uniform_buffer_subbuffer).unwrap();
|
||||
|
||||
let set = Arc::new(
|
||||
set_builder
|
||||
.build()
|
||||
.unwrap()
|
||||
);
|
||||
let set = Arc::new(set_builder.build().unwrap());
|
||||
|
||||
let (image_num, suboptimal, acquire_future) =
|
||||
match swapchain::acquire_next_image(swapchain.clone(), None) {
|
||||
|
@ -15,14 +15,10 @@ categories = ["rendering::graphics-api"]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
num-traits = "0.2"
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
shaderc = "0.7"
|
||||
spirv = "0.2"
|
||||
syn = { version = "1.0", features = ["full", "extra-traits"] }
|
||||
|
||||
[dev-dependencies]
|
||||
vulkano = { version = "0.25.0", path = "../vulkano" }
|
||||
|
||||
[features]
|
||||
|
@ -8,9 +8,6 @@
|
||||
// according to those terms.
|
||||
|
||||
use crate::entry_point;
|
||||
use crate::parse;
|
||||
use crate::parse::Instruction;
|
||||
pub use crate::parse::ParseError;
|
||||
use crate::read_file_to_string;
|
||||
use crate::spec_consts;
|
||||
use crate::structs;
|
||||
@ -20,7 +17,6 @@ use proc_macro2::{Span, TokenStream};
|
||||
pub use shaderc::{CompilationArtifact, IncludeType, ResolvedInclude, ShaderKind};
|
||||
use shaderc::{CompileOptions, Compiler, EnvVersion, SpirvVersion, TargetEnv};
|
||||
use std::collections::HashMap;
|
||||
use spirv::{Capability, StorageClass};
|
||||
use std::iter::Iterator;
|
||||
use std::path::Path;
|
||||
use std::{
|
||||
@ -28,6 +24,10 @@ use std::{
|
||||
io::Error as IoError,
|
||||
};
|
||||
use syn::Ident;
|
||||
use vulkano::{
|
||||
spirv::{Capability, Instruction, Spirv, SpirvError, StorageClass},
|
||||
Version,
|
||||
};
|
||||
|
||||
pub(super) fn path_to_str(path: &Path) -> &str {
|
||||
path.to_str().expect(
|
||||
@ -208,7 +208,7 @@ pub fn compile(
|
||||
|
||||
pub(super) fn reflect<'a, I>(
|
||||
prefix: &'a str,
|
||||
spirv: &[u32],
|
||||
words: &[u32],
|
||||
types_meta: &TypesMeta,
|
||||
input_paths: I,
|
||||
exact_entrypoint_interface: bool,
|
||||
@ -219,20 +219,20 @@ where
|
||||
I: Iterator<Item = &'a str>,
|
||||
{
|
||||
let struct_name = Ident::new(&format!("{}Shader", prefix), Span::call_site());
|
||||
let doc = parse::parse_spirv(spirv)?;
|
||||
let spirv = Spirv::new(words)?;
|
||||
|
||||
// checking whether each required capability is enabled in the Vulkan device
|
||||
let mut cap_checks: Vec<TokenStream> = vec![];
|
||||
match doc.version {
|
||||
(1, 0) => {}
|
||||
(1, 1) | (1, 2) | (1, 3) => {
|
||||
match spirv.version() {
|
||||
Version::V1_0 => {}
|
||||
Version::V1_1 | Version::V1_2 | Version::V1_3 => {
|
||||
cap_checks.push(quote! {
|
||||
if device.api_version() < Version::V1_1 {
|
||||
panic!("Device API version 1.1 required");
|
||||
}
|
||||
});
|
||||
}
|
||||
(1, 4) => {
|
||||
Version::V1_4 => {
|
||||
cap_checks.push(quote! {
|
||||
if device.api_version() < Version::V1_2
|
||||
&& !device.enabled_extensions().khr_spirv_1_4 {
|
||||
@ -240,7 +240,7 @@ where
|
||||
}
|
||||
});
|
||||
}
|
||||
(1, 5) => {
|
||||
Version::V1_5 => {
|
||||
cap_checks.push(quote! {
|
||||
if device.api_version() < Version::V1_2 {
|
||||
panic!("Device API version 1.2 required");
|
||||
@ -250,7 +250,7 @@ where
|
||||
_ => return Err(Error::UnsupportedSpirvVersion),
|
||||
}
|
||||
|
||||
for i in doc.instructions.iter() {
|
||||
for i in spirv.instructions() {
|
||||
let dev_req = {
|
||||
match i {
|
||||
Instruction::Variable {
|
||||
@ -262,9 +262,9 @@ where
|
||||
Instruction::TypePointer {
|
||||
result_id: _,
|
||||
storage_class,
|
||||
type_id: _,
|
||||
ty: _,
|
||||
} => storage_class_requirement(storage_class),
|
||||
Instruction::Capability(cap) => capability_requirement(cap),
|
||||
Instruction::Capability { capability } => capability_requirement(capability),
|
||||
_ => &[],
|
||||
}
|
||||
};
|
||||
@ -310,18 +310,19 @@ where
|
||||
|
||||
// writing one method for each entry point of this module
|
||||
let mut entry_points_inside_impl: Vec<TokenStream> = vec![];
|
||||
for instruction in doc.instructions.iter() {
|
||||
if let &Instruction::EntryPoint { .. } = instruction {
|
||||
let entry_point = entry_point::write_entry_point(
|
||||
prefix,
|
||||
&doc,
|
||||
instruction,
|
||||
types_meta,
|
||||
exact_entrypoint_interface,
|
||||
shared_constants,
|
||||
);
|
||||
entry_points_inside_impl.push(entry_point);
|
||||
}
|
||||
for instruction in spirv
|
||||
.iter_entry_point()
|
||||
.filter(|instruction| matches!(instruction, Instruction::EntryPoint { .. }))
|
||||
{
|
||||
let entry_point = entry_point::write_entry_point(
|
||||
prefix,
|
||||
&spirv,
|
||||
instruction,
|
||||
types_meta,
|
||||
exact_entrypoint_interface,
|
||||
shared_constants,
|
||||
);
|
||||
entry_points_inside_impl.push(entry_point);
|
||||
}
|
||||
|
||||
let include_bytes = input_paths.map(|s| {
|
||||
@ -332,10 +333,10 @@ where
|
||||
}
|
||||
});
|
||||
|
||||
let structs = structs::write_structs(prefix, &doc, types_meta, types_registry);
|
||||
let structs = structs::write_structs(prefix, &spirv, types_meta, types_registry);
|
||||
let specialization_constants = spec_consts::write_specialization_constants(
|
||||
prefix,
|
||||
&doc,
|
||||
&spirv,
|
||||
types_meta,
|
||||
shared_constants,
|
||||
types_registry,
|
||||
@ -355,7 +356,7 @@ where
|
||||
let _bytes = ( #( #include_bytes),* );
|
||||
|
||||
#( #cap_checks )*
|
||||
static WORDS: &[u32] = &[ #( #spirv ),* ];
|
||||
static WORDS: &[u32] = &[ #( #words ),* ];
|
||||
|
||||
unsafe {
|
||||
Ok(#struct_name {
|
||||
@ -384,7 +385,7 @@ where
|
||||
pub enum Error {
|
||||
UnsupportedSpirvVersion,
|
||||
IoError(IoError),
|
||||
ParseError(ParseError),
|
||||
SpirvError(SpirvError),
|
||||
}
|
||||
|
||||
impl From<IoError> for Error {
|
||||
@ -394,10 +395,10 @@ impl From<IoError> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseError> for Error {
|
||||
impl From<SpirvError> for Error {
|
||||
#[inline]
|
||||
fn from(err: ParseError) -> Error {
|
||||
Error::ParseError(err)
|
||||
fn from(err: SpirvError) -> Error {
|
||||
Error::SpirvError(err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -735,12 +736,12 @@ fn storage_class_requirement(storage_class: &StorageClass) -> &'static [DeviceRe
|
||||
StorageClass::StorageBuffer => &[DeviceRequirement::Extension(
|
||||
"khr_storage_buffer_storage_class",
|
||||
)],
|
||||
StorageClass::CallableDataNV => todo!(),
|
||||
StorageClass::IncomingCallableDataNV => todo!(),
|
||||
StorageClass::RayPayloadNV => todo!(),
|
||||
StorageClass::HitAttributeNV => todo!(),
|
||||
StorageClass::IncomingRayPayloadNV => todo!(),
|
||||
StorageClass::ShaderRecordBufferNV => todo!(),
|
||||
StorageClass::CallableDataKHR => todo!(),
|
||||
StorageClass::IncomingCallableDataKHR => todo!(),
|
||||
StorageClass::RayPayloadKHR => todo!(),
|
||||
StorageClass::HitAttributeKHR => todo!(),
|
||||
StorageClass::IncomingRayPayloadKHR => todo!(),
|
||||
StorageClass::ShaderRecordBufferKHR => todo!(),
|
||||
StorageClass::PhysicalStorageBuffer => todo!(),
|
||||
StorageClass::CodeSectionINTEL => todo!(),
|
||||
}
|
||||
@ -804,9 +805,9 @@ mod tests {
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let doc = parse::parse_spirv(comp.as_binary()).unwrap();
|
||||
let spirv = Spirv::new(comp.as_binary()).unwrap();
|
||||
let res = std::panic::catch_unwind(|| {
|
||||
structs::write_structs("", &doc, &TypesMeta::default(), &mut HashMap::new())
|
||||
structs::write_structs("", &spirv, &TypesMeta::default(), &mut HashMap::new())
|
||||
});
|
||||
assert!(res.is_err());
|
||||
}
|
||||
@ -834,8 +835,8 @@ mod tests {
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let doc = parse::parse_spirv(comp.as_binary()).unwrap();
|
||||
structs::write_structs("", &doc, &TypesMeta::default(), &mut HashMap::new());
|
||||
let spirv = Spirv::new(comp.as_binary()).unwrap();
|
||||
structs::write_structs("", &spirv, &TypesMeta::default(), &mut HashMap::new());
|
||||
}
|
||||
#[test]
|
||||
fn test_wrap_alignment() {
|
||||
@ -866,8 +867,8 @@ mod tests {
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let doc = parse::parse_spirv(comp.as_binary()).unwrap();
|
||||
structs::write_structs("", &doc, &TypesMeta::default(), &mut HashMap::new());
|
||||
let spirv = Spirv::new(comp.as_binary()).unwrap();
|
||||
structs::write_structs("", &spirv, &TypesMeta::default(), &mut HashMap::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -7,17 +7,16 @@
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use crate::parse::{Instruction, Spirv};
|
||||
use crate::{spirv_search, TypesMeta};
|
||||
use crate::TypesMeta;
|
||||
use proc_macro2::TokenStream;
|
||||
use spirv::{Decoration, Dim, ImageFormat, StorageClass};
|
||||
use std::cmp;
|
||||
use std::collections::HashSet;
|
||||
use vulkano::spirv::{Decoration, Dim, Id, ImageFormat, Instruction, Spirv, StorageClass};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Descriptor {
|
||||
set: u32,
|
||||
binding: u32,
|
||||
set_num: u32,
|
||||
binding_num: u32,
|
||||
desc_ty: TokenStream,
|
||||
descriptor_count: u64,
|
||||
variable_count: bool,
|
||||
@ -25,30 +24,30 @@ struct Descriptor {
|
||||
}
|
||||
|
||||
pub(super) fn write_descriptor_set_layout_descs(
|
||||
doc: &Spirv,
|
||||
entrypoint_id: u32,
|
||||
interface: &[u32],
|
||||
spirv: &Spirv,
|
||||
entrypoint_id: Id,
|
||||
interface: &[Id],
|
||||
exact_entrypoint_interface: bool,
|
||||
stages: &TokenStream,
|
||||
) -> TokenStream {
|
||||
// TODO: somewhat implemented correctly
|
||||
|
||||
// Finding all the descriptors.
|
||||
let descriptors = find_descriptors(doc, entrypoint_id, interface, exact_entrypoint_interface);
|
||||
let num_sets = descriptors.iter().map(|d| d.set + 1).max().unwrap_or(0);
|
||||
let descriptors = find_descriptors(spirv, entrypoint_id, interface, exact_entrypoint_interface);
|
||||
let num_sets = descriptors.iter().map(|d| d.set_num + 1).max().unwrap_or(0);
|
||||
let sets: Vec<_> = (0..num_sets)
|
||||
.map(|set_num| {
|
||||
let num_bindings = descriptors
|
||||
.iter()
|
||||
.filter(|d| d.set == set_num)
|
||||
.map(|d| d.binding + 1)
|
||||
.filter(|d| d.set_num == set_num)
|
||||
.map(|d| d.binding_num + 1)
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
let bindings: Vec<_> = (0..num_bindings)
|
||||
.map(|binding_num| {
|
||||
match descriptors
|
||||
.iter()
|
||||
.find(|d| d.set == set_num && d.binding == binding_num)
|
||||
.find(|d| d.set_num == set_num && d.binding_num == binding_num)
|
||||
{
|
||||
Some(d) => {
|
||||
let desc_ty = &d.desc_ty;
|
||||
@ -89,7 +88,7 @@ pub(super) fn write_descriptor_set_layout_descs(
|
||||
|
||||
pub(super) fn write_push_constant_ranges(
|
||||
shader: &str,
|
||||
doc: &Spirv,
|
||||
spirv: &Spirv,
|
||||
stage: &TokenStream,
|
||||
types_meta: &TypesMeta,
|
||||
) -> TokenStream {
|
||||
@ -97,17 +96,18 @@ pub(super) fn write_push_constant_ranges(
|
||||
|
||||
// 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 {
|
||||
for type_id in spirv
|
||||
.iter_global()
|
||||
.filter_map(|instruction| match instruction {
|
||||
&Instruction::TypePointer {
|
||||
type_id,
|
||||
ty,
|
||||
storage_class: StorageClass::PushConstant,
|
||||
..
|
||||
} => type_id,
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let (_, _, size, _) = crate::structs::type_from_id(shader, doc, type_id, types_meta);
|
||||
} => Some(ty),
|
||||
_ => None,
|
||||
})
|
||||
{
|
||||
let (_, _, size, _) = crate::structs::type_from_id(shader, spirv, type_id, types_meta);
|
||||
let size = size.expect("Found runtime-sized push constants") as u32;
|
||||
push_constants_size = cmp::max(push_constants_size, size);
|
||||
}
|
||||
@ -130,22 +130,20 @@ pub(super) fn write_push_constant_ranges(
|
||||
}
|
||||
|
||||
fn find_descriptors(
|
||||
doc: &Spirv,
|
||||
entrypoint_id: u32,
|
||||
interface: &[u32],
|
||||
spirv: &Spirv,
|
||||
entrypoint_id: Id,
|
||||
interface: &[Id],
|
||||
exact: bool,
|
||||
) -> Vec<Descriptor> {
|
||||
let mut descriptors = Vec::new();
|
||||
|
||||
// For SPIR-V 1.4+, the entrypoint interface can specify variables of all storage classes,
|
||||
// and most tools will put all used variables in the entrypoint interface. However,
|
||||
// SPIR-V 1.0-1.3 do not specify variables other than Input/Output ones in the interface,
|
||||
// and instead the function itself must be inspected.
|
||||
let variables = if exact {
|
||||
let mut found_variables: HashSet<u32> = interface.iter().cloned().collect();
|
||||
let mut inspected_functions: HashSet<u32> = HashSet::new();
|
||||
let mut found_variables: HashSet<Id> = interface.iter().cloned().collect();
|
||||
let mut inspected_functions: HashSet<Id> = HashSet::new();
|
||||
find_variables_in_function(
|
||||
&doc,
|
||||
&spirv,
|
||||
entrypoint_id,
|
||||
&mut inspected_functions,
|
||||
&mut found_variables,
|
||||
@ -155,60 +153,109 @@ fn find_descriptors(
|
||||
None
|
||||
};
|
||||
|
||||
// Looping to find all the interface elements that have the `DescriptorSet` decoration.
|
||||
for set_decoration in doc.get_decorations(Decoration::DescriptorSet) {
|
||||
let variable_id = set_decoration.target_id;
|
||||
// Looping to find all the global variables that have the `DescriptorSet` decoration.
|
||||
spirv
|
||||
.iter_global()
|
||||
.filter_map(|instruction| {
|
||||
let (variable_id, variable_type_id, storage_class) = match instruction {
|
||||
Instruction::Variable {
|
||||
result_id,
|
||||
result_type_id,
|
||||
..
|
||||
} => {
|
||||
let (real_type, storage_class) = match spirv
|
||||
.id(*result_type_id)
|
||||
.instruction()
|
||||
{
|
||||
Instruction::TypePointer {
|
||||
ty, storage_class, ..
|
||||
} => (ty, storage_class),
|
||||
_ => panic!(
|
||||
"Variable {} result_type_id does not refer to a TypePointer instruction", result_id
|
||||
),
|
||||
};
|
||||
|
||||
if exact && !variables.as_ref().unwrap().contains(&variable_id) {
|
||||
continue;
|
||||
}
|
||||
(*result_id, *real_type, storage_class)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let set = set_decoration.params[0];
|
||||
if exact && !variables.as_ref().unwrap().contains(&variable_id) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Find which type is pointed to by this variable.
|
||||
let (pointed_ty, storage_class) = pointer_variable_ty(doc, variable_id);
|
||||
// Name of the variable.
|
||||
let name = spirv_search::name_from_id(doc, variable_id);
|
||||
let variable_id_info = spirv.id(variable_id);
|
||||
let set_num = match variable_id_info
|
||||
.iter_decoration()
|
||||
.find_map(|instruction| match instruction {
|
||||
Instruction::Decorate {
|
||||
decoration: Decoration::DescriptorSet { descriptor_set },
|
||||
..
|
||||
} => Some(*descriptor_set),
|
||||
_ => None,
|
||||
}) {
|
||||
Some(x) => x,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
// Find the binding point of this descriptor.
|
||||
// TODO: There was a previous todo here, I think it was asking for this to be implemented for member decorations? check git history
|
||||
let binding = doc
|
||||
.get_decoration_params(variable_id, Decoration::Binding)
|
||||
.unwrap()[0];
|
||||
let binding_num = variable_id_info
|
||||
.iter_decoration()
|
||||
.find_map(|instruction| match instruction {
|
||||
Instruction::Decorate {
|
||||
decoration: Decoration::Binding { binding_point },
|
||||
..
|
||||
} => Some(*binding_point),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let nonwritable = doc
|
||||
.get_decoration_params(variable_id, Decoration::NonWritable)
|
||||
.is_some();
|
||||
let name = variable_id_info
|
||||
.iter_name()
|
||||
.find_map(|instruction| match instruction {
|
||||
Instruction::Name { name, .. } => Some(name.as_str()),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or("__unnamed");
|
||||
|
||||
// Find information about the kind of binding for this descriptor.
|
||||
let (desc_ty, mutable, descriptor_count, variable_count) =
|
||||
descriptor_infos(doc, pointed_ty, storage_class, false).expect(&format!(
|
||||
let nonwritable = variable_id_info.iter_decoration().any(|instruction| {
|
||||
matches!(
|
||||
instruction,
|
||||
Instruction::Decorate {
|
||||
decoration: Decoration::NonWritable,
|
||||
..
|
||||
}
|
||||
)
|
||||
});
|
||||
|
||||
// Find information about the kind of binding for this descriptor.
|
||||
let (desc_ty, mutable, descriptor_count, variable_count) =
|
||||
descriptor_infos(spirv, variable_type_id, storage_class, false).expect(&format!(
|
||||
"Couldn't find relevant type for uniform `{}` (type {}, maybe unimplemented)",
|
||||
name, pointed_ty
|
||||
name, variable_type_id,
|
||||
));
|
||||
descriptors.push(Descriptor {
|
||||
desc_ty,
|
||||
set,
|
||||
binding,
|
||||
descriptor_count,
|
||||
mutable: !nonwritable && mutable,
|
||||
variable_count: variable_count,
|
||||
});
|
||||
}
|
||||
|
||||
descriptors
|
||||
Some(Descriptor {
|
||||
desc_ty,
|
||||
set_num,
|
||||
binding_num,
|
||||
descriptor_count,
|
||||
mutable: !nonwritable && mutable,
|
||||
variable_count: variable_count,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
// Recursively finds every pointer variable used in the execution of a function.
|
||||
fn find_variables_in_function(
|
||||
doc: &Spirv,
|
||||
function: u32,
|
||||
inspected_functions: &mut HashSet<u32>,
|
||||
found_variables: &mut HashSet<u32>,
|
||||
spirv: &Spirv,
|
||||
function: Id,
|
||||
inspected_functions: &mut HashSet<Id>,
|
||||
found_variables: &mut HashSet<Id>,
|
||||
) {
|
||||
inspected_functions.insert(function);
|
||||
let mut in_function = false;
|
||||
for instruction in &doc.instructions {
|
||||
for instruction in spirv.instructions() {
|
||||
if !in_function {
|
||||
match instruction {
|
||||
Instruction::Function { result_id, .. } if result_id == &function => {
|
||||
@ -223,20 +270,22 @@ fn find_variables_in_function(
|
||||
Instruction::Load { pointer, .. } | Instruction::Store { pointer, .. } => {
|
||||
found_variables.insert(*pointer);
|
||||
}
|
||||
Instruction::AccessChain { base_id, .. }
|
||||
| Instruction::InBoundsAccessChain { base_id, .. } => {
|
||||
found_variables.insert(*base_id);
|
||||
Instruction::AccessChain { base, .. }
|
||||
| Instruction::InBoundsAccessChain { base, .. } => {
|
||||
found_variables.insert(*base);
|
||||
}
|
||||
Instruction::FunctionCall {
|
||||
function_id, args, ..
|
||||
function,
|
||||
arguments,
|
||||
..
|
||||
} => {
|
||||
args.iter().for_each(|&x| {
|
||||
arguments.iter().for_each(|&x| {
|
||||
found_variables.insert(x);
|
||||
});
|
||||
if !inspected_functions.contains(function_id) {
|
||||
if !inspected_functions.contains(function) {
|
||||
find_variables_in_function(
|
||||
doc,
|
||||
*function_id,
|
||||
spirv,
|
||||
*function,
|
||||
inspected_functions,
|
||||
found_variables,
|
||||
);
|
||||
@ -252,16 +301,12 @@ fn find_variables_in_function(
|
||||
found_variables.insert(*coordinate);
|
||||
found_variables.insert(*sample);
|
||||
}
|
||||
Instruction::CopyMemory {
|
||||
target_id,
|
||||
source_id,
|
||||
..
|
||||
} => {
|
||||
found_variables.insert(*target_id);
|
||||
found_variables.insert(*source_id);
|
||||
Instruction::CopyMemory { target, source, .. } => {
|
||||
found_variables.insert(*target);
|
||||
found_variables.insert(*source);
|
||||
}
|
||||
Instruction::CopyObject { operand_id, .. } => {
|
||||
found_variables.insert(*operand_id);
|
||||
Instruction::CopyObject { operand, .. } => {
|
||||
found_variables.insert(*operand);
|
||||
}
|
||||
Instruction::AtomicLoad { pointer, .. }
|
||||
| Instruction::AtomicIIncrement { pointer, .. }
|
||||
@ -270,57 +315,35 @@ fn find_variables_in_function(
|
||||
| Instruction::AtomicFlagClear { pointer, .. } => {
|
||||
found_variables.insert(*pointer);
|
||||
}
|
||||
Instruction::AtomicStore {
|
||||
pointer, value_id, ..
|
||||
}
|
||||
| Instruction::AtomicExchange {
|
||||
pointer, value_id, ..
|
||||
}
|
||||
| Instruction::AtomicIAdd {
|
||||
pointer, value_id, ..
|
||||
}
|
||||
| Instruction::AtomicISub {
|
||||
pointer, value_id, ..
|
||||
}
|
||||
| Instruction::AtomicSMin {
|
||||
pointer, value_id, ..
|
||||
}
|
||||
| Instruction::AtomicUMin {
|
||||
pointer, value_id, ..
|
||||
}
|
||||
| Instruction::AtomicSMax {
|
||||
pointer, value_id, ..
|
||||
}
|
||||
| Instruction::AtomicUMax {
|
||||
pointer, value_id, ..
|
||||
}
|
||||
| Instruction::AtomicAnd {
|
||||
pointer, value_id, ..
|
||||
}
|
||||
| Instruction::AtomicOr {
|
||||
pointer, value_id, ..
|
||||
}
|
||||
| Instruction::AtomicXor {
|
||||
pointer, value_id, ..
|
||||
} => {
|
||||
Instruction::AtomicStore { pointer, value, .. }
|
||||
| Instruction::AtomicExchange { pointer, value, .. }
|
||||
| Instruction::AtomicIAdd { pointer, value, .. }
|
||||
| Instruction::AtomicISub { pointer, value, .. }
|
||||
| Instruction::AtomicSMin { pointer, value, .. }
|
||||
| Instruction::AtomicUMin { pointer, value, .. }
|
||||
| Instruction::AtomicSMax { pointer, value, .. }
|
||||
| Instruction::AtomicUMax { pointer, value, .. }
|
||||
| Instruction::AtomicAnd { pointer, value, .. }
|
||||
| Instruction::AtomicOr { pointer, value, .. }
|
||||
| Instruction::AtomicXor { pointer, value, .. } => {
|
||||
found_variables.insert(*pointer);
|
||||
found_variables.insert(*value_id);
|
||||
found_variables.insert(*value);
|
||||
}
|
||||
Instruction::AtomicCompareExchange {
|
||||
pointer,
|
||||
value_id,
|
||||
comparator_id,
|
||||
value,
|
||||
comparator,
|
||||
..
|
||||
}
|
||||
| Instruction::AtomicCompareExchangeWeak {
|
||||
pointer,
|
||||
value_id,
|
||||
comparator_id,
|
||||
value,
|
||||
comparator,
|
||||
..
|
||||
} => {
|
||||
found_variables.insert(*pointer);
|
||||
found_variables.insert(*value_id);
|
||||
found_variables.insert(*comparator_id);
|
||||
found_variables.insert(*value);
|
||||
found_variables.insert(*comparator);
|
||||
}
|
||||
Instruction::ExtInst { operands, .. } => {
|
||||
// We don't know which extended instructions take pointers,
|
||||
@ -336,263 +359,237 @@ fn find_variables_in_function(
|
||||
}
|
||||
}
|
||||
|
||||
/// Assumes that `variable` is a variable with a `TypePointer` and returns the id of the pointed
|
||||
/// type and the storage class.
|
||||
fn pointer_variable_ty(doc: &Spirv, variable: u32) -> (u32, StorageClass) {
|
||||
let var_ty = doc
|
||||
.instructions
|
||||
.iter()
|
||||
.filter_map(|i| match i {
|
||||
&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 {
|
||||
&Instruction::TypePointer {
|
||||
result_id,
|
||||
type_id,
|
||||
ref storage_class,
|
||||
..
|
||||
} if result_id == var_ty => Some((type_id, storage_class.clone())),
|
||||
_ => None,
|
||||
})
|
||||
.next()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Returns a `DescriptorDescTy` constructor, a bool indicating whether the descriptor is
|
||||
/// 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: &Spirv,
|
||||
pointed_ty: u32,
|
||||
pointer_storage: StorageClass,
|
||||
spirv: &Spirv,
|
||||
pointed_ty: Id,
|
||||
pointer_storage: &StorageClass,
|
||||
force_combined_image_sampled: bool,
|
||||
) -> Option<(TokenStream, bool, u64, bool)> {
|
||||
doc.instructions
|
||||
.iter()
|
||||
.filter_map(|i| {
|
||||
match i {
|
||||
Instruction::TypeStruct { result_id, member_types } if *result_id == pointed_ty => {
|
||||
let decoration_block = doc
|
||||
.get_decoration_params(pointed_ty, Decoration::Block)
|
||||
.is_some();
|
||||
let decoration_buffer_block = doc
|
||||
.get_decoration_params(pointed_ty, Decoration::BufferBlock)
|
||||
.is_some();
|
||||
let id_info = spirv.id(pointed_ty);
|
||||
|
||||
match id_info.instruction() {
|
||||
Instruction::TypeStruct { .. } => {
|
||||
let decoration_block = id_info.iter_decoration().any(|instruction| {
|
||||
matches!(
|
||||
instruction,
|
||||
Instruction::Decorate {
|
||||
decoration: Decoration::Block,
|
||||
..
|
||||
}
|
||||
)
|
||||
});
|
||||
|
||||
let decoration_buffer_block = id_info.iter_decoration().any(|instruction| {
|
||||
matches!(
|
||||
instruction,
|
||||
Instruction::Decorate {
|
||||
decoration: Decoration::BufferBlock,
|
||||
..
|
||||
}
|
||||
)
|
||||
});
|
||||
|
||||
assert!(
|
||||
decoration_block ^ decoration_buffer_block,
|
||||
"Structs in shader interface are expected to be decorated with one of Block or BufferBlock"
|
||||
);
|
||||
|
||||
let (ty, mutable) = if decoration_buffer_block
|
||||
|| decoration_block && *pointer_storage == StorageClass::StorageBuffer
|
||||
{
|
||||
// VK_DESCRIPTOR_TYPE_STORAGE_BUFFER
|
||||
// Determine whether all members have a NonWritable decoration.
|
||||
let nonwritable = id_info.iter_members().all(|member_info| {
|
||||
member_info.iter_decoration().any(|instruction| {
|
||||
matches!(
|
||||
instruction,
|
||||
Instruction::MemberDecorate {
|
||||
decoration: Decoration::NonWritable,
|
||||
..
|
||||
}
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
(quote! { DescriptorDescTy::StorageBuffer }, !nonwritable)
|
||||
} else {
|
||||
// VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER
|
||||
(quote! { DescriptorDescTy::UniformBuffer }, false) // Uniforms are never mutable.
|
||||
};
|
||||
|
||||
Some((ty, mutable, 1, false))
|
||||
}
|
||||
&Instruction::TypeImage {
|
||||
ref dim,
|
||||
arrayed,
|
||||
ms,
|
||||
sampled,
|
||||
ref image_format,
|
||||
..
|
||||
} => {
|
||||
let ms = ms != 0;
|
||||
assert!(sampled != 0, "Vulkan requires that variables of type OpTypeImage have a Sampled operand of 1 or 2");
|
||||
let vulkan_format = to_vulkan_format(image_format);
|
||||
|
||||
match dim {
|
||||
Dim::SubpassData => {
|
||||
// VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT
|
||||
assert!(
|
||||
decoration_block ^ decoration_buffer_block,
|
||||
"Structs in shader interface are expected to be decorated with one of Block or BufferBlock"
|
||||
);
|
||||
|
||||
let (ty, mutable) = if decoration_buffer_block || decoration_block && pointer_storage == StorageClass::StorageBuffer {
|
||||
// VK_DESCRIPTOR_TYPE_STORAGE_BUFFER
|
||||
// Determine whether all members have a NonWritable decoration.
|
||||
let nonwritable = (0..member_types.len() as u32).all(|i| {
|
||||
doc.get_member_decoration_params(pointed_ty, i, Decoration::NonWritable).is_some()
|
||||
});
|
||||
|
||||
(quote! { DescriptorDescTy::StorageBuffer }, !nonwritable)
|
||||
} else {
|
||||
// VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER
|
||||
(quote! { DescriptorDescTy::UniformBuffer }, false) // Uniforms are never mutable.
|
||||
};
|
||||
|
||||
Some((ty, mutable, 1, false))
|
||||
}
|
||||
&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 vulkan_format = to_vulkan_format(*format);
|
||||
|
||||
match dim {
|
||||
Dim::DimSubpassData => {
|
||||
// VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT
|
||||
assert!(
|
||||
!force_combined_image_sampled,
|
||||
"An OpTypeSampledImage can't point to \
|
||||
!force_combined_image_sampled,
|
||||
"An OpTypeSampledImage can't point to \
|
||||
an OpTypeImage whose dimension is \
|
||||
SubpassData"
|
||||
);
|
||||
assert!(
|
||||
*format == ImageFormat::Unknown,
|
||||
"If Dim is SubpassData, Image Format must be Unknown"
|
||||
);
|
||||
assert!(!sampled, "If Dim is SubpassData, Sampled must be 2");
|
||||
assert!(!arrayed, "If Dim is SubpassData, Arrayed must be 0");
|
||||
);
|
||||
assert!(
|
||||
*image_format == ImageFormat::Unknown,
|
||||
"If Dim is SubpassData, Image Format must be Unknown"
|
||||
);
|
||||
assert!(sampled == 2, "If Dim is SubpassData, Sampled must be 2");
|
||||
assert!(arrayed == 0, "If Dim is SubpassData, Arrayed must be 0");
|
||||
|
||||
let desc = quote! {
|
||||
DescriptorDescTy::InputAttachment {
|
||||
multisampled: #ms,
|
||||
}
|
||||
};
|
||||
|
||||
Some((desc, true, 1, false)) // Never writable.
|
||||
let desc = quote! {
|
||||
DescriptorDescTy::InputAttachment {
|
||||
multisampled: #ms,
|
||||
}
|
||||
Dim::DimBuffer => {
|
||||
let (ty, mutable) = if sampled {
|
||||
// VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER
|
||||
(quote! { DescriptorDescTy::UniformTexelBuffer }, false) // Uniforms are never mutable.
|
||||
} else {
|
||||
// VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER
|
||||
(quote! { DescriptorDescTy::StorageTexelBuffer }, true)
|
||||
};
|
||||
};
|
||||
|
||||
let desc = quote! {
|
||||
Some((desc, true, 1, false)) // Never writable.
|
||||
}
|
||||
Dim::Buffer => {
|
||||
let (ty, mutable) = if sampled == 1 {
|
||||
// VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER
|
||||
(quote! { DescriptorDescTy::UniformTexelBuffer }, false)
|
||||
// Uniforms are never mutable.
|
||||
} else {
|
||||
// VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER
|
||||
(quote! { DescriptorDescTy::StorageTexelBuffer }, true)
|
||||
};
|
||||
|
||||
let desc = quote! {
|
||||
#ty {
|
||||
format: #vulkan_format,
|
||||
}
|
||||
};
|
||||
|
||||
Some((desc, mutable, 1, false))
|
||||
}
|
||||
_ => {
|
||||
let view_type = match (dim, arrayed) {
|
||||
(Dim::Dim1D, 0) => quote! { ImageViewType::Dim1d },
|
||||
(Dim::Dim1D, 1) => quote! { ImageViewType::Dim1dArray },
|
||||
(Dim::Dim2D, 0) => quote! { ImageViewType::Dim2d },
|
||||
(Dim::Dim2D, 1) => quote! { ImageViewType::Dim2dArray },
|
||||
(Dim::Dim3D, 0) => quote! { ImageViewType::Dim3d },
|
||||
(Dim::Dim3D, 1) => panic!("Vulkan doesn't support arrayed 3D textures"),
|
||||
(Dim::Cube, 0) => quote! { ImageViewType::Cube },
|
||||
(Dim::Cube, 1) => quote! { ImageViewType::CubeArray },
|
||||
(Dim::Rect, _) => panic!("Vulkan doesn't support rectangle textures"),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let image_desc = quote! {
|
||||
DescriptorDescImage {
|
||||
format: #vulkan_format,
|
||||
multisampled: #ms,
|
||||
view_type: #view_type,
|
||||
}
|
||||
};
|
||||
|
||||
let (desc, mutable) = if force_combined_image_sampled {
|
||||
// VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER
|
||||
// Never writable.
|
||||
assert!(
|
||||
sampled == 1,
|
||||
"A combined image sampler must not reference a storage image"
|
||||
);
|
||||
|
||||
(
|
||||
quote! {
|
||||
DescriptorDescTy::CombinedImageSampler {
|
||||
image_desc: #image_desc,
|
||||
immutable_samplers: Vec::new(),
|
||||
}
|
||||
},
|
||||
false, // Sampled images are never mutable.
|
||||
)
|
||||
} else {
|
||||
let (ty, mutable) = if sampled == 1 {
|
||||
// VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE
|
||||
(quote! { DescriptorDescTy::SampledImage }, false) // Sampled images are never mutable.
|
||||
} else {
|
||||
// VK_DESCRIPTOR_TYPE_STORAGE_IMAGE
|
||||
(quote! { DescriptorDescTy::StorageImage }, true)
|
||||
};
|
||||
|
||||
(
|
||||
quote! {
|
||||
#ty {
|
||||
format: #vulkan_format,
|
||||
image_desc: #image_desc,
|
||||
}
|
||||
};
|
||||
},
|
||||
mutable,
|
||||
)
|
||||
};
|
||||
|
||||
Some((desc, mutable, 1, false))
|
||||
}
|
||||
_ => {
|
||||
let view_type = match (dim, arrayed) {
|
||||
(Dim::Dim1D, false) => quote! { ImageViewType::Dim1d },
|
||||
(Dim::Dim1D, true) => quote! { ImageViewType::Dim1dArray },
|
||||
(Dim::Dim2D, false) => quote! { ImageViewType::Dim2d },
|
||||
(Dim::Dim2D, true) => quote! { ImageViewType::Dim2dArray },
|
||||
(Dim::Dim3D, false) => quote! { ImageViewType::Dim3d },
|
||||
(Dim::Dim3D, true) => panic!("Vulkan doesn't support arrayed 3D textures"),
|
||||
(Dim::DimCube, false) => quote! { ImageViewType::Cube },
|
||||
(Dim::DimCube, true) => quote! { ImageViewType::CubeArray },
|
||||
(Dim::DimRect, _) => panic!("Vulkan doesn't support rectangle textures"),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let image_desc = quote! {
|
||||
DescriptorDescImage {
|
||||
format: #vulkan_format,
|
||||
multisampled: #ms,
|
||||
view_type: #view_type,
|
||||
}
|
||||
};
|
||||
|
||||
let (desc, mutable) = if force_combined_image_sampled {
|
||||
// VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER
|
||||
// Never writable.
|
||||
assert!(sampled, "A combined image sampler must not reference a storage image");
|
||||
|
||||
(
|
||||
quote! {
|
||||
DescriptorDescTy::CombinedImageSampler {
|
||||
image_desc: #image_desc,
|
||||
immutable_samplers: Vec::new(),
|
||||
}
|
||||
},
|
||||
false, // Sampled images are never mutable.
|
||||
)
|
||||
} else {
|
||||
let (ty, mutable) = if sampled {
|
||||
// VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE
|
||||
(quote! { DescriptorDescTy::SampledImage }, false) // Sampled images are never mutable.
|
||||
} else {
|
||||
// VK_DESCRIPTOR_TYPE_STORAGE_IMAGE
|
||||
(quote! { DescriptorDescTy::StorageImage }, true)
|
||||
};
|
||||
|
||||
(
|
||||
quote! {
|
||||
#ty {
|
||||
image_desc: #image_desc,
|
||||
}
|
||||
},
|
||||
mutable,
|
||||
)
|
||||
};
|
||||
|
||||
Some((desc, mutable, 1, false))
|
||||
}
|
||||
}
|
||||
Some((desc, mutable, 1, false))
|
||||
}
|
||||
|
||||
&Instruction::TypeSampledImage {
|
||||
result_id,
|
||||
image_type_id,
|
||||
} if result_id == pointed_ty => {
|
||||
descriptor_infos(doc, image_type_id, pointer_storage.clone(), true)
|
||||
}
|
||||
|
||||
&Instruction::TypeSampler { result_id } if result_id == pointed_ty => {
|
||||
let desc = quote! { DescriptorDescTy::Sampler { immutable_samplers: Vec::new() } };
|
||||
Some((desc, false, 1, false))
|
||||
}
|
||||
&Instruction::TypeArray {
|
||||
result_id,
|
||||
type_id,
|
||||
length_id,
|
||||
} if result_id == pointed_ty => {
|
||||
let (desc, mutable, arr, variable_count) =
|
||||
match descriptor_infos(doc, type_id, pointer_storage.clone(), false) {
|
||||
None => return None,
|
||||
Some(v) => v,
|
||||
};
|
||||
assert_eq!(arr, 1); // TODO: implement?
|
||||
assert!(!variable_count); // TODO: Is this even a thing?
|
||||
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(0, |a, &b| (a << 32) | b as u64);
|
||||
|
||||
Some((desc, mutable, len, false))
|
||||
}
|
||||
|
||||
&Instruction::TypeRuntimeArray {
|
||||
result_id,
|
||||
type_id,
|
||||
} if result_id == pointed_ty => {
|
||||
let (desc, mutable, arr, variable_count) =
|
||||
match descriptor_infos(doc, type_id, pointer_storage.clone(), false) {
|
||||
None => return None,
|
||||
Some(v) => v,
|
||||
};
|
||||
assert_eq!(arr, 1); // TODO: implement?
|
||||
assert!(!variable_count); // TODO: Don't think this is possible?
|
||||
|
||||
Some((desc, mutable, 1, true))
|
||||
}
|
||||
|
||||
_ => None, // TODO: other types
|
||||
}
|
||||
})
|
||||
.next()
|
||||
}
|
||||
|
||||
&Instruction::TypeSampledImage { image_type, .. } => {
|
||||
descriptor_infos(spirv, image_type, pointer_storage, true)
|
||||
}
|
||||
|
||||
&Instruction::TypeSampler { .. } => {
|
||||
let desc = quote! { DescriptorDescTy::Sampler { immutable_samplers: Vec::new() } };
|
||||
Some((desc, false, 1, false))
|
||||
}
|
||||
|
||||
&Instruction::TypeArray {
|
||||
element_type,
|
||||
length,
|
||||
..
|
||||
} => {
|
||||
let (desc, mutable, arr, variable_count) =
|
||||
match descriptor_infos(spirv, element_type, pointer_storage, false) {
|
||||
None => return None,
|
||||
Some(v) => v,
|
||||
};
|
||||
assert_eq!(arr, 1); // TODO: implement?
|
||||
assert!(!variable_count); // TODO: Is this even a thing?
|
||||
let len = match spirv.id(length).instruction() {
|
||||
&Instruction::Constant { ref value, .. } => value,
|
||||
_ => panic!("failed to find array length"),
|
||||
};
|
||||
let len = len.iter().rev().fold(0, |a, &b| (a << 32) | b as u64);
|
||||
|
||||
Some((desc, mutable, len, false))
|
||||
}
|
||||
|
||||
&Instruction::TypeRuntimeArray { element_type, .. } => {
|
||||
let (desc, mutable, arr, variable_count) =
|
||||
match descriptor_infos(spirv, element_type, pointer_storage, false) {
|
||||
None => return None,
|
||||
Some(v) => v,
|
||||
};
|
||||
assert_eq!(arr, 1); // TODO: implement?
|
||||
assert!(!variable_count); // TODO: Don't think this is possible?
|
||||
|
||||
Some((desc, mutable, 1, true))
|
||||
}
|
||||
|
||||
_ => None, // TODO: other types
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::codegen::compile;
|
||||
use crate::parse;
|
||||
use shaderc::ShaderKind;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
@ -668,15 +665,17 @@ mod tests {
|
||||
((c[3] as u32) << 24) | ((c[2] as u32) << 16) | ((c[1] as u32) << 8) | c[0] as u32
|
||||
})
|
||||
.collect();
|
||||
let doc = parse::parse_spirv(&instructions).unwrap();
|
||||
let spirv = Spirv::new(&instructions).unwrap();
|
||||
|
||||
let mut descriptors = Vec::new();
|
||||
for instruction in doc.instructions.iter() {
|
||||
for instruction in spirv.instructions() {
|
||||
if let &Instruction::EntryPoint {
|
||||
id, ref interface, ..
|
||||
entry_point,
|
||||
ref interface,
|
||||
..
|
||||
} = instruction
|
||||
{
|
||||
descriptors.push(find_descriptors(&doc, id, interface, true));
|
||||
descriptors.push(find_descriptors(&spirv, entry_point, interface, true));
|
||||
}
|
||||
}
|
||||
|
||||
@ -684,7 +683,7 @@ mod tests {
|
||||
let e1_descriptors = descriptors.get(0).expect("Could not find entrypoint1");
|
||||
let mut e1_bindings = Vec::new();
|
||||
for d in e1_descriptors {
|
||||
e1_bindings.push((d.set, d.binding));
|
||||
e1_bindings.push((d.set_num, d.binding_num));
|
||||
}
|
||||
assert_eq!(e1_bindings.len(), 5);
|
||||
assert!(e1_bindings.contains(&(0, 0)));
|
||||
@ -697,7 +696,7 @@ mod tests {
|
||||
let e2_descriptors = descriptors.get(1).expect("Could not find entrypoint2");
|
||||
let mut e2_bindings = Vec::new();
|
||||
for d in e2_descriptors {
|
||||
e2_bindings.push((d.set, d.binding));
|
||||
e2_bindings.push((d.set_num, d.binding_num));
|
||||
}
|
||||
assert_eq!(e2_bindings.len(), 3);
|
||||
assert!(e2_bindings.contains(&(0, 0)));
|
||||
@ -745,17 +744,19 @@ mod tests {
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let doc = parse::parse_spirv(comp.as_binary()).unwrap();
|
||||
let spirv = Spirv::new(comp.as_binary()).unwrap();
|
||||
|
||||
for instruction in doc.instructions.iter() {
|
||||
for instruction in spirv.instructions() {
|
||||
if let &Instruction::EntryPoint {
|
||||
id, ref interface, ..
|
||||
entry_point,
|
||||
ref interface,
|
||||
..
|
||||
} = instruction
|
||||
{
|
||||
let descriptors = find_descriptors(&doc, id, interface, true);
|
||||
let descriptors = find_descriptors(&spirv, entry_point, interface, true);
|
||||
let mut bindings = Vec::new();
|
||||
for d in descriptors {
|
||||
bindings.push((d.set, d.binding));
|
||||
bindings.push((d.set_num, d.binding_num));
|
||||
}
|
||||
assert_eq!(bindings.len(), 4);
|
||||
assert!(bindings.contains(&(1, 0)));
|
||||
@ -770,7 +771,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn to_vulkan_format(spirv_format: ImageFormat) -> TokenStream {
|
||||
fn to_vulkan_format(spirv_format: &ImageFormat) -> TokenStream {
|
||||
match spirv_format {
|
||||
ImageFormat::Unknown => quote! { None },
|
||||
ImageFormat::Rgba32f => quote! { Some(Format::R32G32B32A32_SFLOAT) },
|
||||
|
@ -8,15 +8,16 @@
|
||||
// according to those terms.
|
||||
|
||||
use crate::descriptor_sets::{write_descriptor_set_layout_descs, write_push_constant_ranges};
|
||||
use crate::parse::{Instruction, Spirv};
|
||||
use crate::{spirv_search, TypesMeta};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use spirv::{Decoration, ExecutionMode, ExecutionModel, StorageClass};
|
||||
use syn::Ident;
|
||||
use vulkano::spirv::{
|
||||
Decoration, ExecutionMode, ExecutionModel, Id, Instruction, Spirv, StorageClass,
|
||||
};
|
||||
|
||||
pub(super) fn write_entry_point(
|
||||
shader: &str,
|
||||
doc: &Spirv,
|
||||
spirv: &Spirv,
|
||||
instruction: &Instruction,
|
||||
types_meta: &TypesMeta,
|
||||
exact_entrypoint_interface: bool,
|
||||
@ -24,12 +25,12 @@ pub(super) fn write_entry_point(
|
||||
) -> TokenStream {
|
||||
let (execution, id, ep_name, interface) = match instruction {
|
||||
&Instruction::EntryPoint {
|
||||
ref execution,
|
||||
id,
|
||||
ref execution_model,
|
||||
entry_point,
|
||||
ref name,
|
||||
ref interface,
|
||||
..
|
||||
} => (execution, id, name, interface),
|
||||
} => (execution_model, entry_point, name, interface),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
@ -45,7 +46,7 @@ pub(super) fn write_entry_point(
|
||||
};
|
||||
|
||||
let (input_interface, output_interface) = write_interfaces(
|
||||
doc,
|
||||
spirv,
|
||||
interface,
|
||||
ignore_first_array_in,
|
||||
ignore_first_array_out,
|
||||
@ -74,20 +75,25 @@ pub(super) fn write_entry_point(
|
||||
| ExecutionModel::Kernel
|
||||
| ExecutionModel::TaskNV
|
||||
| ExecutionModel::MeshNV
|
||||
| ExecutionModel::RayGenerationNV
|
||||
| ExecutionModel::IntersectionNV
|
||||
| ExecutionModel::AnyHitNV
|
||||
| ExecutionModel::ClosestHitNV
|
||||
| ExecutionModel::MissNV
|
||||
| ExecutionModel::CallableNV => unreachable!(),
|
||||
| ExecutionModel::RayGenerationKHR
|
||||
| ExecutionModel::IntersectionKHR
|
||||
| ExecutionModel::AnyHitKHR
|
||||
| ExecutionModel::ClosestHitKHR
|
||||
| ExecutionModel::MissKHR
|
||||
| ExecutionModel::CallableKHR => unreachable!(),
|
||||
}
|
||||
};
|
||||
|
||||
let descriptor_set_layout_descs =
|
||||
write_descriptor_set_layout_descs(&doc, id, interface, exact_entrypoint_interface, &stage);
|
||||
let push_constant_ranges = write_push_constant_ranges(shader, &doc, &stage, &types_meta);
|
||||
let descriptor_set_layout_descs = write_descriptor_set_layout_descs(
|
||||
&spirv,
|
||||
id,
|
||||
interface,
|
||||
exact_entrypoint_interface,
|
||||
&stage,
|
||||
);
|
||||
let push_constant_ranges = write_push_constant_ranges(shader, &spirv, &stage, &types_meta);
|
||||
|
||||
let spec_consts_struct = if crate::spec_consts::has_specialization_constants(doc) {
|
||||
let spec_consts_struct = if crate::spec_consts::has_specialization_constants(spirv) {
|
||||
let spec_consts_struct_name = Ident::new(
|
||||
&format!(
|
||||
"{}SpecializationConstants",
|
||||
@ -126,17 +132,15 @@ pub(super) fn write_entry_point(
|
||||
}
|
||||
|
||||
ExecutionModel::Geometry => {
|
||||
let mut execution_mode = None;
|
||||
|
||||
for instruction in doc.instructions.iter() {
|
||||
if let &Instruction::ExecutionMode {
|
||||
target_id,
|
||||
ref mode,
|
||||
..
|
||||
} = instruction
|
||||
{
|
||||
if target_id == id {
|
||||
execution_mode = match mode {
|
||||
let execution_mode =
|
||||
spirv
|
||||
.iter_execution_mode()
|
||||
.find_map(|instruction| match instruction {
|
||||
&Instruction::ExecutionMode {
|
||||
entry_point,
|
||||
ref mode,
|
||||
..
|
||||
} if entry_point == id => match mode {
|
||||
&ExecutionMode::InputPoints => Some(quote! { Points }),
|
||||
&ExecutionMode::InputLines => Some(quote! { Lines }),
|
||||
&ExecutionMode::InputLinesAdjacency => {
|
||||
@ -146,12 +150,10 @@ pub(super) fn write_entry_point(
|
||||
&ExecutionMode::InputTrianglesAdjacency => {
|
||||
Some(quote! { TrianglesWithAdjacency })
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
});
|
||||
|
||||
quote! {
|
||||
::vulkano::pipeline::shader::GraphicsShaderType::Geometry(
|
||||
@ -169,12 +171,12 @@ pub(super) fn write_entry_point(
|
||||
ExecutionModel::Kernel
|
||||
| ExecutionModel::TaskNV
|
||||
| ExecutionModel::MeshNV
|
||||
| ExecutionModel::RayGenerationNV
|
||||
| ExecutionModel::IntersectionNV
|
||||
| ExecutionModel::AnyHitNV
|
||||
| ExecutionModel::ClosestHitNV
|
||||
| ExecutionModel::MissNV
|
||||
| ExecutionModel::CallableNV => {
|
||||
| ExecutionModel::RayGenerationKHR
|
||||
| ExecutionModel::IntersectionKHR
|
||||
| ExecutionModel::AnyHitKHR
|
||||
| ExecutionModel::ClosestHitKHR
|
||||
| ExecutionModel::MissKHR
|
||||
| ExecutionModel::CallableKHR => {
|
||||
panic!("Shaders with {:?} are not supported", execution)
|
||||
}
|
||||
};
|
||||
@ -227,8 +229,8 @@ struct Element {
|
||||
}
|
||||
|
||||
fn write_interfaces(
|
||||
doc: &Spirv,
|
||||
interface: &[u32],
|
||||
spirv: &Spirv,
|
||||
interface: &[Id],
|
||||
ignore_first_array_in: bool,
|
||||
ignore_first_array_out: bool,
|
||||
) -> (TokenStream, TokenStream) {
|
||||
@ -236,50 +238,64 @@ fn write_interfaces(
|
||||
let mut output_elements = vec![];
|
||||
|
||||
// Filling `input_elements` and `output_elements`.
|
||||
for interface in interface.iter() {
|
||||
for i in doc.instructions.iter() {
|
||||
match i {
|
||||
&Instruction::Variable {
|
||||
result_type_id,
|
||||
result_id,
|
||||
ref storage_class,
|
||||
..
|
||||
} if &result_id == interface => {
|
||||
if spirv_search::is_builtin(doc, result_id) {
|
||||
continue;
|
||||
}
|
||||
for &interface in interface.iter() {
|
||||
let interface_info = spirv.id(interface);
|
||||
|
||||
let (to_write, ignore_first_array) = match storage_class {
|
||||
&StorageClass::Input => (&mut input_elements, ignore_first_array_in),
|
||||
&StorageClass::Output => (&mut output_elements, ignore_first_array_out),
|
||||
_ => continue,
|
||||
};
|
||||
match interface_info.instruction() {
|
||||
&Instruction::Variable {
|
||||
result_type_id,
|
||||
result_id,
|
||||
ref storage_class,
|
||||
..
|
||||
} => {
|
||||
if spirv_search::is_builtin(spirv, result_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let name = spirv_search::name_from_id(doc, result_id);
|
||||
if name == "__unnamed" {
|
||||
continue;
|
||||
} // FIXME: hack
|
||||
let id_info = spirv.id(result_id);
|
||||
|
||||
let location = match doc.get_decoration_params(result_id, Decoration::Location)
|
||||
{
|
||||
Some(l) => l[0],
|
||||
None => panic!(
|
||||
let (to_write, ignore_first_array) = match storage_class {
|
||||
&StorageClass::Input => (&mut input_elements, ignore_first_array_in),
|
||||
&StorageClass::Output => (&mut output_elements, ignore_first_array_out),
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let name = match id_info
|
||||
.iter_name()
|
||||
.find_map(|instruction| match instruction {
|
||||
Instruction::Name { name, .. } => Some(name.as_str()),
|
||||
_ => None,
|
||||
}) {
|
||||
Some(name) => name,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let location = id_info
|
||||
.iter_decoration()
|
||||
.find_map(|instruction| match instruction {
|
||||
Instruction::Decorate {
|
||||
decoration: Decoration::Location { location },
|
||||
..
|
||||
} => Some(*location),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Attribute `{}` (id {}) is missing a location",
|
||||
name, result_id
|
||||
),
|
||||
};
|
||||
|
||||
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,
|
||||
)
|
||||
});
|
||||
}
|
||||
_ => (),
|
||||
|
||||
let (format, location_len) =
|
||||
spirv_search::format_from_id(spirv, result_type_id, ignore_first_array);
|
||||
to_write.push(Element {
|
||||
location,
|
||||
name: name.to_owned(),
|
||||
format,
|
||||
location_len,
|
||||
});
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -7,19 +7,18 @@
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use crate::parse::{Instruction, Spirv};
|
||||
use crate::{spirv_search, TypesMeta};
|
||||
use crate::TypesMeta;
|
||||
use crate::{structs, RegisteredType};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use spirv::Decoration;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::mem;
|
||||
use syn::Ident;
|
||||
use vulkano::spirv::{Decoration, Instruction, Spirv};
|
||||
|
||||
/// Returns true if the document has specialization constants.
|
||||
pub fn has_specialization_constants(doc: &Spirv) -> bool {
|
||||
for instruction in doc.instructions.iter() {
|
||||
pub fn has_specialization_constants(spirv: &Spirv) -> bool {
|
||||
for instruction in spirv.iter_global() {
|
||||
match instruction {
|
||||
&Instruction::SpecConstantTrue { .. } => return true,
|
||||
&Instruction::SpecConstantFalse { .. } => return true,
|
||||
@ -36,7 +35,7 @@ pub fn has_specialization_constants(doc: &Spirv) -> bool {
|
||||
/// implements the `Default` and the `vulkano::pipeline::shader::SpecializationConstants` traits.
|
||||
pub(super) fn write_specialization_constants<'a>(
|
||||
shader: &'a str,
|
||||
doc: &Spirv,
|
||||
spirv: &Spirv,
|
||||
types_meta: &TypesMeta,
|
||||
shared_constants: bool,
|
||||
types_registry: &'a mut HashMap<String, RegisteredType>,
|
||||
@ -53,8 +52,8 @@ pub(super) fn write_specialization_constants<'a>(
|
||||
|
||||
let mut spec_consts = Vec::new();
|
||||
|
||||
for instruction in doc.instructions.iter() {
|
||||
let (type_id, result_id, default_value) = match instruction {
|
||||
for instruction in spirv.iter_global() {
|
||||
let (result_type_id, result_id, default_value) = match instruction {
|
||||
&Instruction::SpecConstantTrue {
|
||||
result_type_id,
|
||||
result_id,
|
||||
@ -68,40 +67,65 @@ pub(super) fn write_specialization_constants<'a>(
|
||||
&Instruction::SpecConstant {
|
||||
result_type_id,
|
||||
result_id,
|
||||
ref data,
|
||||
ref value,
|
||||
} => {
|
||||
let def_val = quote! {
|
||||
unsafe {{ ::std::mem::transmute([ #( #data ),* ]) }}
|
||||
unsafe {{ ::std::mem::transmute([ #( #value ),* ]) }}
|
||||
};
|
||||
(result_type_id, result_id, def_val)
|
||||
}
|
||||
&Instruction::SpecConstantComposite {
|
||||
result_type_id,
|
||||
result_id,
|
||||
ref data,
|
||||
ref constituents,
|
||||
} => {
|
||||
let constituents = constituents.iter().map(|&id| u32::from(id));
|
||||
let def_val = quote! {
|
||||
unsafe {{ ::std::mem::transmute([ #( #data ),* ]) }}
|
||||
unsafe {{ ::std::mem::transmute([ #( #constituents ),* ]) }}
|
||||
};
|
||||
(result_type_id, result_id, def_val)
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
// Translate bool to u32
|
||||
let (rust_ty, rust_signature, rust_size, rust_alignment) =
|
||||
spec_const_type_from_id(shader, doc, type_id, types_meta);
|
||||
match spirv.id(result_type_id).instruction() {
|
||||
Instruction::TypeBool { .. } => (
|
||||
quote! {u32},
|
||||
Cow::from("u32"),
|
||||
Some(mem::size_of::<u32>()),
|
||||
mem::align_of::<u32>(),
|
||||
),
|
||||
_ => structs::type_from_id(shader, spirv, result_type_id, types_meta),
|
||||
};
|
||||
let rust_size = rust_size.expect("Found runtime-sized specialization constant");
|
||||
|
||||
let constant_id = doc.get_decoration_params(result_id, Decoration::SpecId);
|
||||
let id_info = spirv.id(result_id);
|
||||
|
||||
let constant_id = id_info
|
||||
.iter_decoration()
|
||||
.find_map(|instruction| match instruction {
|
||||
Instruction::Decorate {
|
||||
decoration:
|
||||
Decoration::SpecId {
|
||||
specialization_constant_id,
|
||||
},
|
||||
..
|
||||
} => Some(*specialization_constant_id),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
if let Some(constant_id) = constant_id {
|
||||
let constant_id = constant_id[0];
|
||||
|
||||
let mut name = spirv_search::name_from_id(doc, result_id);
|
||||
|
||||
if name == "__unnamed".to_owned() {
|
||||
name = String::from(format!("constant_{}", constant_id));
|
||||
}
|
||||
let name = match id_info
|
||||
.iter_name()
|
||||
.find_map(|instruction| match instruction {
|
||||
Instruction::Name { name, .. } => Some(name.as_str()),
|
||||
_ => None,
|
||||
}) {
|
||||
Some(name) => name.to_owned(),
|
||||
None => format!("constant_{}", constant_id),
|
||||
};
|
||||
|
||||
spec_consts.push(SpecConst {
|
||||
name,
|
||||
@ -207,27 +231,3 @@ pub(super) fn write_specialization_constants<'a>(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper around `type_from_id` that also handles booleans.
|
||||
fn spec_const_type_from_id(
|
||||
shader: &str,
|
||||
doc: &Spirv,
|
||||
searched: u32,
|
||||
types_meta: &TypesMeta,
|
||||
) -> (TokenStream, Cow<'static, str>, Option<usize>, usize) {
|
||||
for instruction in doc.instructions.iter() {
|
||||
match instruction {
|
||||
&Instruction::TypeBool { result_id } if result_id == searched => {
|
||||
return (
|
||||
quote! {u32},
|
||||
Cow::from("u32"),
|
||||
Some(mem::size_of::<u32>()),
|
||||
mem::align_of::<u32>(),
|
||||
);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
structs::type_from_id(shader, doc, searched, types_meta)
|
||||
}
|
||||
|
@ -7,186 +7,152 @@
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use crate::parse::{Instruction, Spirv};
|
||||
use spirv::Decoration;
|
||||
use vulkano::spirv::{Decoration, Id, Instruction, Spirv};
|
||||
|
||||
/// 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) => "R8_SINT",
|
||||
(8, false) => "R8_UINT",
|
||||
(16, true) => "R16_SINT",
|
||||
(16, false) => "R16_UINT",
|
||||
(32, true) => "R32_SINT",
|
||||
(32, false) => "R32_UINT",
|
||||
(64, true) => "R64_SINT",
|
||||
(64, false) => "R64_UINT",
|
||||
_ => panic!(),
|
||||
};
|
||||
return (format.to_string(), 1);
|
||||
}
|
||||
&Instruction::TypeFloat { result_id, width } if result_id == searched => {
|
||||
assert!(!ignore_first_array);
|
||||
let format = match width {
|
||||
32 => "R32_SFLOAT",
|
||||
64 => "R64_SFLOAT",
|
||||
_ => 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);
|
||||
}
|
||||
pub fn format_from_id(spirv: &Spirv, searched: Id, ignore_first_array: bool) -> (String, usize) {
|
||||
let id_info = spirv.id(searched);
|
||||
|
||||
let (format, sz) = format_from_id(doc, type_id, false);
|
||||
let len = doc
|
||||
.instructions
|
||||
match id_info.instruction() {
|
||||
&Instruction::TypeInt {
|
||||
width, signedness, ..
|
||||
} => {
|
||||
assert!(!ignore_first_array);
|
||||
let format = match (width, signedness) {
|
||||
(8, 1) => "R8_SINT",
|
||||
(8, 0) => "R8_UINT",
|
||||
(16, 1) => "R16_SINT",
|
||||
(16, 0) => "R16_UINT",
|
||||
(32, 1) => "R32_SINT",
|
||||
(32, 0) => "R32_UINT",
|
||||
(64, 1) => "R64_SINT",
|
||||
(64, 0) => "R64_UINT",
|
||||
_ => panic!(),
|
||||
};
|
||||
(format.to_string(), 1)
|
||||
}
|
||||
&Instruction::TypeFloat { width, .. } => {
|
||||
assert!(!ignore_first_array);
|
||||
let format = match width {
|
||||
32 => "R32_SFLOAT",
|
||||
64 => "R64_SFLOAT",
|
||||
_ => panic!(),
|
||||
};
|
||||
(format.to_string(), 1)
|
||||
}
|
||||
&Instruction::TypeVector {
|
||||
component_type,
|
||||
component_count,
|
||||
..
|
||||
} => {
|
||||
assert!(!ignore_first_array);
|
||||
let (format, sz) = format_from_id(spirv, component_type, false);
|
||||
assert!(format.starts_with("R32"));
|
||||
assert_eq!(sz, 1);
|
||||
let format = match component_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"),
|
||||
};
|
||||
(format, sz)
|
||||
}
|
||||
&Instruction::TypeMatrix {
|
||||
column_type,
|
||||
column_count,
|
||||
..
|
||||
} => {
|
||||
assert!(!ignore_first_array);
|
||||
let (format, sz) = format_from_id(spirv, column_type, false);
|
||||
(format, sz * column_count as usize)
|
||||
}
|
||||
&Instruction::TypeArray {
|
||||
element_type,
|
||||
length,
|
||||
..
|
||||
} => {
|
||||
if ignore_first_array {
|
||||
format_from_id(spirv, element_type, false)
|
||||
} else {
|
||||
let (format, sz) = format_from_id(spirv, element_type, false);
|
||||
let len = spirv
|
||||
.instructions()
|
||||
.iter()
|
||||
.filter_map(|e| match e {
|
||||
&Instruction::Constant {
|
||||
result_id,
|
||||
ref data,
|
||||
ref value,
|
||||
..
|
||||
} if result_id == length_id => Some(data.clone()),
|
||||
} if result_id == length => Some(value.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();
|
||||
(format, sz * len as usize)
|
||||
}
|
||||
}
|
||||
&Instruction::TypePointer { ty, .. } => format_from_id(spirv, ty, ignore_first_array),
|
||||
_ => panic!("Type #{} not found or invalid", searched),
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
/// Returns true if a `BuiltIn` decorator is applied on an id.
|
||||
pub fn is_builtin(doc: &Spirv, id: u32) -> bool {
|
||||
if doc.get_decoration_params(id, Decoration::BuiltIn).is_some() {
|
||||
return true;
|
||||
}
|
||||
if doc.get_member_decoration_builtin_params(id).is_some() {
|
||||
pub fn is_builtin(spirv: &Spirv, id: Id) -> bool {
|
||||
let id_info = spirv.id(id);
|
||||
|
||||
if id_info.iter_decoration().any(|instruction| {
|
||||
matches!(
|
||||
instruction,
|
||||
Instruction::Decorate {
|
||||
decoration: Decoration::BuiltIn { .. },
|
||||
..
|
||||
}
|
||||
)
|
||||
}) {
|
||||
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;
|
||||
}
|
||||
if id_info
|
||||
.iter_members()
|
||||
.flat_map(|member_info| member_info.iter_decoration())
|
||||
.any(|instruction| {
|
||||
matches!(
|
||||
instruction,
|
||||
Instruction::MemberDecorate {
|
||||
decoration: Decoration::BuiltIn { .. },
|
||||
..
|
||||
}
|
||||
}
|
||||
Instruction::TypePointer {
|
||||
result_id, type_id, ..
|
||||
} if result_id == id => {
|
||||
return is_builtin(doc, type_id);
|
||||
}
|
||||
_ => (),
|
||||
)
|
||||
})
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
match id_info.instruction() {
|
||||
Instruction::Variable { result_type_id, .. } => {
|
||||
return is_builtin(spirv, *result_type_id);
|
||||
}
|
||||
Instruction::TypeArray { element_type, .. } => {
|
||||
return is_builtin(spirv, *element_type);
|
||||
}
|
||||
Instruction::TypeRuntimeArray { element_type, .. } => {
|
||||
return is_builtin(spirv, *element_type);
|
||||
}
|
||||
Instruction::TypeStruct { member_types, .. } => {
|
||||
if member_types.iter().any(|ty| is_builtin(spirv, *ty)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Instruction::TypePointer { ty, .. } => {
|
||||
return is_builtin(spirv, *ty);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
false
|
||||
|
@ -7,43 +7,41 @@
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use crate::parse::{Instruction, Spirv};
|
||||
use crate::{spirv_search, RegisteredType, TypesMeta};
|
||||
use crate::{RegisteredType, TypesMeta};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use spirv::Decoration;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::mem;
|
||||
use syn::Ident;
|
||||
use syn::LitStr;
|
||||
use vulkano::spirv::{Decoration, Id, Instruction, Spirv};
|
||||
|
||||
/// Translates all the structs that are contained in the SPIR-V document as Rust structs.
|
||||
pub(super) fn write_structs<'a>(
|
||||
shader: &'a str,
|
||||
doc: &Spirv,
|
||||
spirv: &Spirv,
|
||||
types_meta: &TypesMeta,
|
||||
types_registry: &'a mut HashMap<String, RegisteredType>,
|
||||
) -> TokenStream {
|
||||
let mut structs = vec![];
|
||||
for instruction in &doc.instructions {
|
||||
match *instruction {
|
||||
let structs = spirv
|
||||
.iter_global()
|
||||
.filter_map(|instruction| match instruction {
|
||||
Instruction::TypeStruct {
|
||||
result_id,
|
||||
ref member_types,
|
||||
} => structs.push(
|
||||
member_types,
|
||||
} => Some(
|
||||
write_struct(
|
||||
shader,
|
||||
doc,
|
||||
result_id,
|
||||
spirv,
|
||||
*result_id,
|
||||
member_types,
|
||||
types_meta,
|
||||
Some(types_registry),
|
||||
)
|
||||
.0,
|
||||
),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
|
||||
quote! {
|
||||
#( #structs )*
|
||||
@ -53,14 +51,21 @@ pub(super) fn write_structs<'a>(
|
||||
/// Analyzes a single struct, returns a string containing its Rust definition, plus its size.
|
||||
fn write_struct<'a>(
|
||||
shader: &'a str,
|
||||
doc: &Spirv,
|
||||
struct_id: u32,
|
||||
members: &[u32],
|
||||
spirv: &Spirv,
|
||||
struct_id: Id,
|
||||
members: &[Id],
|
||||
types_meta: &TypesMeta,
|
||||
types_registry: Option<&'a mut HashMap<String, RegisteredType>>,
|
||||
) -> (TokenStream, Option<usize>) {
|
||||
let id_info = spirv.id(struct_id);
|
||||
let name = Ident::new(
|
||||
&spirv_search::name_from_id(doc, struct_id),
|
||||
id_info
|
||||
.iter_name()
|
||||
.find_map(|instruction| match instruction {
|
||||
Instruction::Name { name, .. } => Some(name.as_str()),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or("__unnamed"),
|
||||
Span::call_site(),
|
||||
);
|
||||
|
||||
@ -80,24 +85,43 @@ fn write_struct<'a>(
|
||||
// Equals to `None` if there's a runtime-sized field in there.
|
||||
let mut current_rust_offset = Some(0);
|
||||
|
||||
for (num, &member) in members.iter().enumerate() {
|
||||
for (&member, member_info) in members.iter().zip(id_info.iter_members()) {
|
||||
// Compute infos about the member.
|
||||
let (ty, signature, rust_size, rust_align) = type_from_id(shader, doc, member, types_meta);
|
||||
let member_name = spirv_search::member_name_from_id(doc, struct_id, num as u32);
|
||||
let (ty, signature, rust_size, rust_align) =
|
||||
type_from_id(shader, spirv, member, types_meta);
|
||||
let member_name = member_info
|
||||
.iter_name()
|
||||
.find_map(|instruction| match instruction {
|
||||
Instruction::MemberName { name, .. } => Some(name.as_str()),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or("__unnamed");
|
||||
|
||||
// Ignore the whole struct is a member is built in, which includes
|
||||
// `gl_Position` for example.
|
||||
if doc
|
||||
.get_member_decoration_params(struct_id, num as u32, Decoration::BuiltIn)
|
||||
.is_some()
|
||||
{
|
||||
if member_info.iter_decoration().any(|instruction| {
|
||||
matches!(
|
||||
instruction,
|
||||
Instruction::MemberDecorate {
|
||||
decoration: Decoration::BuiltIn { .. },
|
||||
..
|
||||
}
|
||||
)
|
||||
}) {
|
||||
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.
|
||||
let spirv_offset = doc
|
||||
.get_member_decoration_params(struct_id, num as u32, Decoration::Offset)
|
||||
.map(|x| x[0]);
|
||||
let spirv_offset =
|
||||
member_info
|
||||
.iter_decoration()
|
||||
.find_map(|instruction| match instruction {
|
||||
Instruction::MemberDecorate {
|
||||
decoration: Decoration::Offset { byte_offset },
|
||||
..
|
||||
} => Some(*byte_offset),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
// Some structs don't have `Offset` decorations, in the case they are used as local
|
||||
// variables only. Ignoring these.
|
||||
@ -150,21 +174,28 @@ fn write_struct<'a>(
|
||||
|
||||
// Try determine the total size of the struct in order to add padding at the end of the struct.
|
||||
let mut spirv_req_total_size = None;
|
||||
for inst in doc.instructions.iter() {
|
||||
match *inst {
|
||||
for inst in spirv.iter_global() {
|
||||
match inst {
|
||||
Instruction::TypeArray {
|
||||
result_id, type_id, ..
|
||||
} if type_id == struct_id => {
|
||||
if let Some(params) = doc.get_decoration_params(result_id, Decoration::ArrayStride)
|
||||
{
|
||||
spirv_req_total_size = Some(params[0]);
|
||||
}
|
||||
result_id,
|
||||
element_type,
|
||||
..
|
||||
}
|
||||
Instruction::TypeRuntimeArray { result_id, type_id } if type_id == struct_id => {
|
||||
if let Some(params) = doc.get_decoration_params(result_id, Decoration::ArrayStride)
|
||||
{
|
||||
spirv_req_total_size = Some(params[0]);
|
||||
}
|
||||
| Instruction::TypeRuntimeArray {
|
||||
result_id,
|
||||
element_type,
|
||||
} if *element_type == struct_id => {
|
||||
spirv_req_total_size =
|
||||
spirv
|
||||
.id(*result_id)
|
||||
.iter_decoration()
|
||||
.find_map(|instruction| match instruction {
|
||||
Instruction::Decorate {
|
||||
decoration: Decoration::ArrayStride { array_stride },
|
||||
..
|
||||
} => Some(*array_stride),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
@ -384,260 +415,255 @@ fn write_struct<'a>(
|
||||
/// The size can be `None` if it's only known at runtime.
|
||||
pub(super) fn type_from_id(
|
||||
shader: &str,
|
||||
doc: &Spirv,
|
||||
searched: u32,
|
||||
spirv: &Spirv,
|
||||
searched: Id,
|
||||
types_meta: &TypesMeta,
|
||||
) -> (TokenStream, Cow<'static, str>, Option<usize>, usize) {
|
||||
for instruction in doc.instructions.iter() {
|
||||
match instruction {
|
||||
&Instruction::TypeBool { result_id } if result_id == searched => {
|
||||
panic!("Can't put booleans in structs")
|
||||
}
|
||||
&Instruction::TypeInt {
|
||||
result_id,
|
||||
width,
|
||||
signedness,
|
||||
} if result_id == searched => match (width, signedness) {
|
||||
(8, true) => {
|
||||
#[repr(C)]
|
||||
struct Foo {
|
||||
data: i8,
|
||||
after: u8,
|
||||
}
|
||||
return (
|
||||
quote! {i8},
|
||||
Cow::from("i8"),
|
||||
Some(std::mem::size_of::<i8>()),
|
||||
mem::align_of::<Foo>(),
|
||||
);
|
||||
let id_info = spirv.id(searched);
|
||||
|
||||
match id_info.instruction() {
|
||||
Instruction::TypeBool { .. } => {
|
||||
panic!("Can't put booleans in structs")
|
||||
}
|
||||
Instruction::TypeInt {
|
||||
width, signedness, ..
|
||||
} => match (width, signedness) {
|
||||
(8, 1) => {
|
||||
#[repr(C)]
|
||||
struct Foo {
|
||||
data: i8,
|
||||
after: u8,
|
||||
}
|
||||
(8, false) => {
|
||||
#[repr(C)]
|
||||
struct Foo {
|
||||
data: u8,
|
||||
after: u8,
|
||||
}
|
||||
return (
|
||||
quote! {u8},
|
||||
Cow::from("u8"),
|
||||
Some(std::mem::size_of::<u8>()),
|
||||
mem::align_of::<Foo>(),
|
||||
);
|
||||
}
|
||||
(16, true) => {
|
||||
#[repr(C)]
|
||||
struct Foo {
|
||||
data: i16,
|
||||
after: u8,
|
||||
}
|
||||
return (
|
||||
quote! {i16},
|
||||
Cow::from("i16"),
|
||||
Some(std::mem::size_of::<i16>()),
|
||||
mem::align_of::<Foo>(),
|
||||
);
|
||||
}
|
||||
(16, false) => {
|
||||
#[repr(C)]
|
||||
struct Foo {
|
||||
data: u16,
|
||||
after: u8,
|
||||
}
|
||||
return (
|
||||
quote! {u16},
|
||||
Cow::from("u16"),
|
||||
Some(std::mem::size_of::<u16>()),
|
||||
mem::align_of::<Foo>(),
|
||||
);
|
||||
}
|
||||
(32, true) => {
|
||||
#[repr(C)]
|
||||
struct Foo {
|
||||
data: i32,
|
||||
after: u8,
|
||||
}
|
||||
return (
|
||||
quote! {i32},
|
||||
Cow::from("i32"),
|
||||
Some(std::mem::size_of::<i32>()),
|
||||
mem::align_of::<Foo>(),
|
||||
);
|
||||
}
|
||||
(32, false) => {
|
||||
#[repr(C)]
|
||||
struct Foo {
|
||||
data: u32,
|
||||
after: u8,
|
||||
}
|
||||
return (
|
||||
quote! {u32},
|
||||
Cow::from("u32"),
|
||||
Some(std::mem::size_of::<u32>()),
|
||||
mem::align_of::<Foo>(),
|
||||
);
|
||||
}
|
||||
(64, true) => {
|
||||
#[repr(C)]
|
||||
struct Foo {
|
||||
data: i64,
|
||||
after: u8,
|
||||
}
|
||||
return (
|
||||
quote! {i64},
|
||||
Cow::from("i64"),
|
||||
Some(std::mem::size_of::<i64>()),
|
||||
mem::align_of::<Foo>(),
|
||||
);
|
||||
}
|
||||
(64, false) => {
|
||||
#[repr(C)]
|
||||
struct Foo {
|
||||
data: u64,
|
||||
after: u8,
|
||||
}
|
||||
return (
|
||||
quote! {u64},
|
||||
Cow::from("u64"),
|
||||
Some(std::mem::size_of::<u64>()),
|
||||
mem::align_of::<Foo>(),
|
||||
);
|
||||
}
|
||||
_ => panic!("No Rust equivalent for an integer of width {}", width),
|
||||
},
|
||||
&Instruction::TypeFloat { result_id, width } if result_id == searched => match width {
|
||||
32 => {
|
||||
#[repr(C)]
|
||||
struct Foo {
|
||||
data: f32,
|
||||
after: u8,
|
||||
}
|
||||
return (
|
||||
quote! {f32},
|
||||
Cow::from("f32"),
|
||||
Some(std::mem::size_of::<f32>()),
|
||||
mem::align_of::<Foo>(),
|
||||
);
|
||||
}
|
||||
64 => {
|
||||
#[repr(C)]
|
||||
struct Foo {
|
||||
data: f64,
|
||||
after: u8,
|
||||
}
|
||||
return (
|
||||
quote! {f64},
|
||||
Cow::from("f64"),
|
||||
Some(std::mem::size_of::<f64>()),
|
||||
mem::align_of::<Foo>(),
|
||||
);
|
||||
}
|
||||
_ => panic!("No Rust equivalent for a floating-point of width {}", width),
|
||||
},
|
||||
&Instruction::TypeVector {
|
||||
result_id,
|
||||
component_id,
|
||||
count,
|
||||
} if result_id == searched => {
|
||||
debug_assert_eq!(mem::align_of::<[u32; 3]>(), mem::align_of::<u32>());
|
||||
let (ty, item, t_size, t_align) =
|
||||
type_from_id(shader, doc, component_id, types_meta);
|
||||
let array_length = count as usize;
|
||||
let size = t_size.map(|s| s * count as usize);
|
||||
return (
|
||||
quote! { [#ty; #array_length] },
|
||||
Cow::from(format!("[{}; {}]", item, array_length)),
|
||||
size,
|
||||
t_align,
|
||||
quote! {i8},
|
||||
Cow::from("i8"),
|
||||
Some(std::mem::size_of::<i8>()),
|
||||
mem::align_of::<Foo>(),
|
||||
);
|
||||
}
|
||||
&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 (ty, item, t_size, t_align) =
|
||||
type_from_id(shader, doc, column_type_id, types_meta);
|
||||
let array_length = column_count as usize;
|
||||
let size = t_size.map(|s| s * column_count as usize);
|
||||
(8, 0) => {
|
||||
#[repr(C)]
|
||||
struct Foo {
|
||||
data: u8,
|
||||
after: u8,
|
||||
}
|
||||
return (
|
||||
quote! { [#ty; #array_length] },
|
||||
Cow::from(format!("[{}; {}]", item, array_length)),
|
||||
size,
|
||||
t_align,
|
||||
quote! {u8},
|
||||
Cow::from("u8"),
|
||||
Some(std::mem::size_of::<u8>()),
|
||||
mem::align_of::<Foo>(),
|
||||
);
|
||||
}
|
||||
&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 (ty, item, t_size, t_align) = type_from_id(shader, doc, type_id, types_meta);
|
||||
let t_size = t_size.expect("array components must be sized");
|
||||
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);
|
||||
let stride = doc
|
||||
.get_decoration_params(searched, Decoration::ArrayStride)
|
||||
.unwrap()[0];
|
||||
if stride as usize > t_size {
|
||||
panic!("Not possible to generate a rust array with the correct alignment since the SPIR-V \
|
||||
(16, 1) => {
|
||||
#[repr(C)]
|
||||
struct Foo {
|
||||
data: i16,
|
||||
after: u8,
|
||||
}
|
||||
return (
|
||||
quote! {i16},
|
||||
Cow::from("i16"),
|
||||
Some(std::mem::size_of::<i16>()),
|
||||
mem::align_of::<Foo>(),
|
||||
);
|
||||
}
|
||||
(16, 0) => {
|
||||
#[repr(C)]
|
||||
struct Foo {
|
||||
data: u16,
|
||||
after: u8,
|
||||
}
|
||||
return (
|
||||
quote! {u16},
|
||||
Cow::from("u16"),
|
||||
Some(std::mem::size_of::<u16>()),
|
||||
mem::align_of::<Foo>(),
|
||||
);
|
||||
}
|
||||
(32, 1) => {
|
||||
#[repr(C)]
|
||||
struct Foo {
|
||||
data: i32,
|
||||
after: u8,
|
||||
}
|
||||
return (
|
||||
quote! {i32},
|
||||
Cow::from("i32"),
|
||||
Some(std::mem::size_of::<i32>()),
|
||||
mem::align_of::<Foo>(),
|
||||
);
|
||||
}
|
||||
(32, 0) => {
|
||||
#[repr(C)]
|
||||
struct Foo {
|
||||
data: u32,
|
||||
after: u8,
|
||||
}
|
||||
return (
|
||||
quote! {u32},
|
||||
Cow::from("u32"),
|
||||
Some(std::mem::size_of::<u32>()),
|
||||
mem::align_of::<Foo>(),
|
||||
);
|
||||
}
|
||||
(64, 1) => {
|
||||
#[repr(C)]
|
||||
struct Foo {
|
||||
data: i64,
|
||||
after: u8,
|
||||
}
|
||||
return (
|
||||
quote! {i64},
|
||||
Cow::from("i64"),
|
||||
Some(std::mem::size_of::<i64>()),
|
||||
mem::align_of::<Foo>(),
|
||||
);
|
||||
}
|
||||
(64, 0) => {
|
||||
#[repr(C)]
|
||||
struct Foo {
|
||||
data: u64,
|
||||
after: u8,
|
||||
}
|
||||
return (
|
||||
quote! {u64},
|
||||
Cow::from("u64"),
|
||||
Some(std::mem::size_of::<u64>()),
|
||||
mem::align_of::<Foo>(),
|
||||
);
|
||||
}
|
||||
_ => panic!("No Rust equivalent for an integer of width {}", width),
|
||||
},
|
||||
Instruction::TypeFloat { width, .. } => match width {
|
||||
32 => {
|
||||
#[repr(C)]
|
||||
struct Foo {
|
||||
data: f32,
|
||||
after: u8,
|
||||
}
|
||||
return (
|
||||
quote! {f32},
|
||||
Cow::from("f32"),
|
||||
Some(std::mem::size_of::<f32>()),
|
||||
mem::align_of::<Foo>(),
|
||||
);
|
||||
}
|
||||
64 => {
|
||||
#[repr(C)]
|
||||
struct Foo {
|
||||
data: f64,
|
||||
after: u8,
|
||||
}
|
||||
return (
|
||||
quote! {f64},
|
||||
Cow::from("f64"),
|
||||
Some(std::mem::size_of::<f64>()),
|
||||
mem::align_of::<Foo>(),
|
||||
);
|
||||
}
|
||||
_ => panic!("No Rust equivalent for a floating-point of width {}", width),
|
||||
},
|
||||
&Instruction::TypeVector {
|
||||
component_type,
|
||||
component_count,
|
||||
..
|
||||
} => {
|
||||
debug_assert_eq!(mem::align_of::<[u32; 3]>(), mem::align_of::<u32>());
|
||||
let (ty, item, t_size, t_align) =
|
||||
type_from_id(shader, spirv, component_type, types_meta);
|
||||
let array_length = component_count as usize;
|
||||
let size = t_size.map(|s| s * component_count as usize);
|
||||
return (
|
||||
quote! { [#ty; #array_length] },
|
||||
Cow::from(format!("[{}; {}]", item, array_length)),
|
||||
size,
|
||||
t_align,
|
||||
);
|
||||
}
|
||||
&Instruction::TypeMatrix {
|
||||
column_type,
|
||||
column_count,
|
||||
..
|
||||
} => {
|
||||
// FIXME: row-major or column-major
|
||||
debug_assert_eq!(mem::align_of::<[u32; 3]>(), mem::align_of::<u32>());
|
||||
let (ty, item, t_size, t_align) = type_from_id(shader, spirv, column_type, types_meta);
|
||||
let array_length = column_count as usize;
|
||||
let size = t_size.map(|s| s * column_count as usize);
|
||||
return (
|
||||
quote! { [#ty; #array_length] },
|
||||
Cow::from(format!("[{}; {}]", item, array_length)),
|
||||
size,
|
||||
t_align,
|
||||
);
|
||||
}
|
||||
&Instruction::TypeArray {
|
||||
element_type,
|
||||
length,
|
||||
..
|
||||
} => {
|
||||
debug_assert_eq!(mem::align_of::<[u32; 3]>(), mem::align_of::<u32>());
|
||||
let (ty, item, t_size, t_align) = type_from_id(shader, spirv, element_type, types_meta);
|
||||
let t_size = t_size.expect("array components must be sized");
|
||||
let len = match spirv.id(length).instruction() {
|
||||
&Instruction::Constant { ref value, .. } => value,
|
||||
_ => panic!("failed to find array length"),
|
||||
};
|
||||
let len = len.iter().rev().fold(0u64, |a, &b| (a << 32) | b as u64);
|
||||
let stride = id_info
|
||||
.iter_decoration()
|
||||
.find_map(|instruction| match instruction {
|
||||
Instruction::Decorate {
|
||||
decoration: Decoration::ArrayStride { array_stride },
|
||||
..
|
||||
} => Some(*array_stride),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap();
|
||||
if stride as usize > t_size {
|
||||
panic!("Not possible to generate a rust array with the correct alignment since the SPIR-V \
|
||||
ArrayStride is larger than the size of the array element in rust. Try wrapping \
|
||||
the array element in a struct or rounding up the size of a vector or matrix \
|
||||
(e.g. increase a vec3 to a vec4)")
|
||||
}
|
||||
let array_length = len as usize;
|
||||
let size = Some(t_size * len as usize);
|
||||
return (
|
||||
quote! { [#ty; #array_length] },
|
||||
Cow::from(format!("[{}; {}]", item, 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 (ty, name, _, t_align) = type_from_id(shader, doc, type_id, types_meta);
|
||||
return (
|
||||
quote! { [#ty] },
|
||||
Cow::from(format!("[{}]", name)),
|
||||
None,
|
||||
t_align,
|
||||
);
|
||||
}
|
||||
&Instruction::TypeStruct {
|
||||
result_id,
|
||||
ref member_types,
|
||||
} if result_id == searched => {
|
||||
// TODO: take the Offset member decorate into account?
|
||||
let name_string = spirv_search::name_from_id(doc, result_id);
|
||||
let name = Ident::new(&name_string, Span::call_site());
|
||||
let ty = quote! { #name };
|
||||
let (_, size) =
|
||||
write_struct(shader, doc, result_id, member_types, types_meta, None);
|
||||
let align = member_types
|
||||
.iter()
|
||||
.map(|&t| type_from_id(shader, doc, t, types_meta).3)
|
||||
.max()
|
||||
.unwrap_or(1);
|
||||
return (ty, Cow::from(name_string), size, align);
|
||||
}
|
||||
_ => (),
|
||||
let array_length = len as usize;
|
||||
let size = Some(t_size * len as usize);
|
||||
return (
|
||||
quote! { [#ty; #array_length] },
|
||||
Cow::from(format!("[{}; {}]", item, array_length)),
|
||||
size,
|
||||
t_align,
|
||||
);
|
||||
}
|
||||
&Instruction::TypeRuntimeArray { element_type, .. } => {
|
||||
debug_assert_eq!(mem::align_of::<[u32; 3]>(), mem::align_of::<u32>());
|
||||
let (ty, name, _, t_align) = type_from_id(shader, spirv, element_type, types_meta);
|
||||
return (
|
||||
quote! { [#ty] },
|
||||
Cow::from(format!("[{}]", name)),
|
||||
None,
|
||||
t_align,
|
||||
);
|
||||
}
|
||||
Instruction::TypeStruct { member_types, .. } => {
|
||||
// TODO: take the Offset member decorate into account?
|
||||
let name_string = id_info
|
||||
.iter_name()
|
||||
.find_map(|instruction| match instruction {
|
||||
Instruction::Name { name, .. } => Some(name.as_str()),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or("__unnamed");
|
||||
let name = Ident::new(&name_string, Span::call_site());
|
||||
let ty = quote! { #name };
|
||||
let (_, size) = write_struct(shader, spirv, searched, member_types, types_meta, None);
|
||||
let align = member_types
|
||||
.iter()
|
||||
.map(|&t| type_from_id(shader, spirv, t, types_meta).3)
|
||||
.max()
|
||||
.unwrap_or(1);
|
||||
return (ty, Cow::from(name_string.to_owned()), size, align);
|
||||
}
|
||||
_ => panic!("Type #{} not found", searched),
|
||||
}
|
||||
|
||||
panic!("Type #{} not found", searched)
|
||||
}
|
||||
|
@ -32,4 +32,6 @@ lazy_static = "1.4"
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
regex = "1.5"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
vk-parse = "0.6"
|
||||
|
@ -7,7 +7,7 @@
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use super::{write_file, RegistryData};
|
||||
use super::{write_file, VkRegistryData};
|
||||
use heck::SnakeCase;
|
||||
use indexmap::IndexMap;
|
||||
use proc_macro2::{Ident, Literal, TokenStream};
|
||||
@ -31,9 +31,9 @@ fn conflicts_extensions(name: &str) -> &'static [&'static str] {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(data: &RegistryData) {
|
||||
write_device_extensions(data);
|
||||
write_instance_extensions(data);
|
||||
pub fn write(vk_data: &VkRegistryData) {
|
||||
write_device_extensions(vk_data);
|
||||
write_instance_extensions(vk_data);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@ -62,19 +62,19 @@ enum ExtensionStatus {
|
||||
Deprecated(Option<Replacement>),
|
||||
}
|
||||
|
||||
fn write_device_extensions(data: &RegistryData) {
|
||||
fn write_device_extensions(vk_data: &VkRegistryData) {
|
||||
write_file(
|
||||
"device_extensions.rs",
|
||||
format!("vk.xml header version {}", data.header_version),
|
||||
device_extensions_output(&extensions_members("device", &data.extensions)),
|
||||
format!("vk.xml header version {}", vk_data.header_version),
|
||||
device_extensions_output(&extensions_members("device", &vk_data.extensions)),
|
||||
);
|
||||
}
|
||||
|
||||
fn write_instance_extensions(data: &RegistryData) {
|
||||
fn write_instance_extensions(vk_data: &VkRegistryData) {
|
||||
write_file(
|
||||
"instance_extensions.rs",
|
||||
format!("vk.xml header version {}", data.header_version),
|
||||
instance_extensions_output(&extensions_members("instance", &data.extensions)),
|
||||
format!("vk.xml header version {}", vk_data.header_version),
|
||||
instance_extensions_output(&extensions_members("instance", &vk_data.extensions)),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use super::{write_file, RegistryData};
|
||||
use super::{write_file, VkRegistryData};
|
||||
use heck::SnakeCase;
|
||||
use indexmap::IndexMap;
|
||||
use proc_macro2::{Ident, TokenStream};
|
||||
@ -61,13 +61,13 @@ fn required_by_extensions(name: &str) -> &'static [&'static str] {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(data: &RegistryData) {
|
||||
let features_output = features_output(&features_members(&data.types));
|
||||
pub fn write(vk_data: &VkRegistryData) {
|
||||
let features_output = features_output(&features_members(&vk_data.types));
|
||||
let features_ffi_output =
|
||||
features_ffi_output(&features_ffi_members(&data.types, &data.extensions));
|
||||
features_ffi_output(&features_ffi_members(&vk_data.types, &vk_data.extensions));
|
||||
write_file(
|
||||
"features.rs",
|
||||
format!("vk.xml header version {}", data.header_version),
|
||||
format!("vk.xml header version {}", vk_data.header_version),
|
||||
quote! {
|
||||
#features_output
|
||||
#features_ffi_output
|
||||
|
@ -7,24 +7,26 @@
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use super::{write_file, RegistryData};
|
||||
use super::{write_file, VkRegistryData};
|
||||
use heck::{CamelCase, SnakeCase};
|
||||
use indexmap::IndexMap;
|
||||
use proc_macro2::{Ident, TokenStream};
|
||||
use quote::{format_ident, quote};
|
||||
use vk_parse::{Extension, ExtensionChild, InterfaceItem};
|
||||
|
||||
pub fn write(data: &RegistryData) {
|
||||
pub fn write(vk_data: &VkRegistryData) {
|
||||
let entry_fns_output = fns_output(&[], "Entry");
|
||||
let instance_fns_output = fns_output(
|
||||
&extension_fns_members("instance", &data.extensions),
|
||||
&extension_fns_members("instance", &vk_data.extensions),
|
||||
"Instance",
|
||||
);
|
||||
let device_fns_output =
|
||||
fns_output(&extension_fns_members("device", &data.extensions), "Device");
|
||||
let device_fns_output = fns_output(
|
||||
&extension_fns_members("device", &vk_data.extensions),
|
||||
"Device",
|
||||
);
|
||||
write_file(
|
||||
"fns.rs",
|
||||
format!("vk.xml header version {}", data.header_version),
|
||||
format!("vk.xml header version {}", vk_data.header_version),
|
||||
quote! {
|
||||
#entry_fns_output
|
||||
#instance_fns_output
|
||||
|
@ -7,17 +7,17 @@
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use super::{write_file, RegistryData};
|
||||
use super::{write_file, VkRegistryData};
|
||||
use lazy_static::lazy_static;
|
||||
use proc_macro2::{Ident, Literal, TokenStream};
|
||||
use quote::{format_ident, quote};
|
||||
use regex::Regex;
|
||||
|
||||
pub fn write(data: &RegistryData) {
|
||||
pub fn write(vk_data: &VkRegistryData) {
|
||||
write_file(
|
||||
"formats.rs",
|
||||
format!("vk.xml header version {}", data.header_version),
|
||||
formats_output(&formats_members(&data.formats)),
|
||||
format!("vk.xml header version {}", vk_data.header_version),
|
||||
formats_output(&formats_members(&vk_data.formats)),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -7,8 +7,19 @@
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use self::spirv_grammar::SpirvGrammar;
|
||||
use indexmap::IndexMap;
|
||||
use std::{collections::HashMap, env, fmt::Display, fs::File, io::{BufWriter, Write}, path::Path, process::Command};
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
env,
|
||||
fmt::Display,
|
||||
fs::File,
|
||||
io::{BufWriter, Write},
|
||||
path::Path,
|
||||
process::Command,
|
||||
};
|
||||
use vk_parse::{
|
||||
EnumSpec, EnumsChild, Extension, ExtensionChild, Feature, InterfaceItem, Registry,
|
||||
RegistryChild, Type, TypeCodeMarkup, TypeSpec, TypesChild,
|
||||
@ -19,16 +30,20 @@ mod features;
|
||||
mod fns;
|
||||
mod formats;
|
||||
mod properties;
|
||||
mod spirv;
|
||||
mod spirv_grammar;
|
||||
|
||||
pub fn autogen() {
|
||||
let registry = get_registry("vk.xml");
|
||||
let data = RegistryData::new(®istry);
|
||||
let registry = get_vk_registry("vk.xml");
|
||||
let vk_data = VkRegistryData::new(®istry);
|
||||
let spirv_grammar = get_spirv_grammar("spirv.core.grammar.json");
|
||||
|
||||
extensions::write(&data);
|
||||
features::write(&data);
|
||||
formats::write(&data);
|
||||
fns::write(&data);
|
||||
properties::write(&data);
|
||||
extensions::write(&vk_data);
|
||||
features::write(&vk_data);
|
||||
formats::write(&vk_data);
|
||||
fns::write(&vk_data);
|
||||
properties::write(&vk_data);
|
||||
spirv::write(&spirv_grammar);
|
||||
}
|
||||
|
||||
fn write_file(file: impl AsRef<Path>, source: impl AsRef<str>, content: impl Display) {
|
||||
@ -50,7 +65,7 @@ fn write_file(file: impl AsRef<Path>, source: impl AsRef<str>, content: impl Dis
|
||||
Command::new("rustfmt").arg(&path).status().ok();
|
||||
}
|
||||
|
||||
fn get_registry<P: AsRef<Path> + ?Sized>(path: &P) -> Registry {
|
||||
fn get_vk_registry<P: AsRef<Path> + ?Sized>(path: &P) -> Registry {
|
||||
let (registry, errors) = vk_parse::parse_file(path.as_ref()).unwrap();
|
||||
|
||||
if !errors.is_empty() {
|
||||
@ -64,7 +79,7 @@ fn get_registry<P: AsRef<Path> + ?Sized>(path: &P) -> Registry {
|
||||
registry
|
||||
}
|
||||
|
||||
pub struct RegistryData<'r> {
|
||||
pub struct VkRegistryData<'r> {
|
||||
pub header_version: u16,
|
||||
pub extensions: IndexMap<&'r str, &'r Extension>,
|
||||
pub features: IndexMap<&'r str, &'r Feature>,
|
||||
@ -72,7 +87,7 @@ pub struct RegistryData<'r> {
|
||||
pub types: HashMap<&'r str, (&'r Type, Vec<&'r str>)>,
|
||||
}
|
||||
|
||||
impl<'r> RegistryData<'r> {
|
||||
impl<'r> VkRegistryData<'r> {
|
||||
fn new(registry: &'r Registry) -> Self {
|
||||
let aliases = Self::get_aliases(®istry);
|
||||
let extensions = Self::get_extensions(®istry);
|
||||
@ -85,7 +100,7 @@ impl<'r> RegistryData<'r> {
|
||||
let types = Self::get_types(®istry, &aliases, &features, &extensions);
|
||||
let header_version = Self::get_header_version(®istry);
|
||||
|
||||
RegistryData {
|
||||
VkRegistryData {
|
||||
header_version,
|
||||
extensions,
|
||||
features,
|
||||
@ -106,11 +121,9 @@ impl<'r> RegistryData<'r> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
});
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
.unwrap()
|
||||
@ -136,7 +149,7 @@ impl<'r> RegistryData<'r> {
|
||||
.flatten()
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
||||
fn get_extensions(registry: &Registry) -> IndexMap<&str, &Extension> {
|
||||
let iter = registry
|
||||
.0
|
||||
@ -155,7 +168,7 @@ impl<'r> RegistryData<'r> {
|
||||
None
|
||||
})
|
||||
.flatten();
|
||||
|
||||
|
||||
let extensions: HashMap<&str, &Extension> =
|
||||
iter.clone().map(|ext| (ext.name.as_str(), ext)).collect();
|
||||
let mut names: Vec<_> = iter.map(|ext| ext.name.as_str()).collect();
|
||||
@ -168,10 +181,10 @@ impl<'r> RegistryData<'r> {
|
||||
(2, name.to_owned())
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
names.iter().map(|&name| (name, extensions[name])).collect()
|
||||
}
|
||||
|
||||
|
||||
fn get_features(registry: &Registry) -> IndexMap<&str, &Feature> {
|
||||
registry
|
||||
.0
|
||||
@ -180,12 +193,12 @@ impl<'r> RegistryData<'r> {
|
||||
if let RegistryChild::Feature(feat) = child {
|
||||
return Some((feat.name.as_str(), feat));
|
||||
}
|
||||
|
||||
|
||||
None
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
||||
fn get_formats<'a>(
|
||||
registry: &'a Registry,
|
||||
features: impl IntoIterator<Item = &'a ExtensionChild>,
|
||||
@ -233,7 +246,7 @@ impl<'r> RegistryData<'r> {
|
||||
)
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
||||
fn get_types<'a>(
|
||||
registry: &'a Registry,
|
||||
aliases: &HashMap<&'a str, &'a str>,
|
||||
@ -258,7 +271,7 @@ impl<'r> RegistryData<'r> {
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
|
||||
features
|
||||
.iter()
|
||||
.map(|(name, feature)| (name, &feature.children))
|
||||
@ -288,10 +301,81 @@ impl<'r> RegistryData<'r> {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
types
|
||||
.into_iter()
|
||||
.filter(|(_key, val)| !val.1.is_empty())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_spirv_grammar<P: AsRef<Path> + ?Sized>(path: &P) -> SpirvGrammar {
|
||||
let mut grammar = SpirvGrammar::new(path);
|
||||
|
||||
// Remove duplicate opcodes and enum values, preferring "more official" suffixes
|
||||
grammar
|
||||
.instructions
|
||||
.sort_by_key(|instruction| (instruction.opcode, suffix_key(&instruction.opname)));
|
||||
grammar
|
||||
.instructions
|
||||
.dedup_by_key(|instruction| instruction.opcode);
|
||||
|
||||
grammar
|
||||
.operand_kinds
|
||||
.iter_mut()
|
||||
.filter(|operand_kind| operand_kind.category == "BitEnum")
|
||||
.for_each(|operand_kind| {
|
||||
operand_kind.enumerants.sort_by_key(|enumerant| {
|
||||
let value = enumerant
|
||||
.value
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.strip_prefix("0x")
|
||||
.unwrap();
|
||||
(
|
||||
u32::from_str_radix(value, 16).unwrap(),
|
||||
suffix_key(&enumerant.enumerant),
|
||||
)
|
||||
});
|
||||
operand_kind.enumerants.dedup_by_key(|enumerant| {
|
||||
let value = enumerant
|
||||
.value
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.strip_prefix("0x")
|
||||
.unwrap();
|
||||
u32::from_str_radix(value, 16).unwrap()
|
||||
});
|
||||
});
|
||||
|
||||
grammar
|
||||
.operand_kinds
|
||||
.iter_mut()
|
||||
.filter(|operand_kind| operand_kind.category == "ValueEnum")
|
||||
.for_each(|operand_kind| {
|
||||
operand_kind.enumerants.sort_by_key(|enumerant| {
|
||||
(enumerant.value.as_u64(), suffix_key(&enumerant.enumerant))
|
||||
});
|
||||
operand_kind
|
||||
.enumerants
|
||||
.dedup_by_key(|enumerant| enumerant.value.as_u64());
|
||||
});
|
||||
|
||||
grammar
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref VENDOR_SUFFIXES: Regex = Regex::new(r"(?:AMD|GOOGLE|INTEL|NV)$").unwrap();
|
||||
}
|
||||
|
||||
fn suffix_key(name: &str) -> u32 {
|
||||
if VENDOR_SUFFIXES.is_match(name) {
|
||||
3
|
||||
} else if name.ends_with("EXT") {
|
||||
2
|
||||
} else if name.ends_with("KHR") {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use super::{write_file, RegistryData};
|
||||
use super::{write_file, VkRegistryData};
|
||||
use heck::SnakeCase;
|
||||
use indexmap::IndexMap;
|
||||
use proc_macro2::{Ident, TokenStream};
|
||||
@ -19,13 +19,13 @@ use std::{
|
||||
};
|
||||
use vk_parse::{Extension, Type, TypeMember, TypeMemberMarkup, TypeSpec};
|
||||
|
||||
pub fn write(data: &RegistryData) {
|
||||
let properties_output = properties_output(&properties_members(&data.types));
|
||||
pub fn write(vk_data: &VkRegistryData) {
|
||||
let properties_output = properties_output(&properties_members(&vk_data.types));
|
||||
let properties_ffi_output =
|
||||
properties_ffi_output(&properties_ffi_members(&data.types, &data.extensions));
|
||||
properties_ffi_output(&properties_ffi_members(&vk_data.types, &vk_data.extensions));
|
||||
write_file(
|
||||
"properties.rs",
|
||||
format!("vk.xml header version {}", data.header_version),
|
||||
format!("vk.xml header version {}", vk_data.header_version),
|
||||
quote! {
|
||||
#properties_output
|
||||
#properties_ffi_output
|
||||
|
674
vulkano/autogen/spirv.rs
Normal file
674
vulkano/autogen/spirv.rs
Normal file
@ -0,0 +1,674 @@
|
||||
// Copyright (c) 2021 The Vulkano developers
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE or
|
||||
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
|
||||
// license <LICENSE-MIT or https://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 super::{write_file, SpirvGrammar};
|
||||
use heck::SnakeCase;
|
||||
use lazy_static::lazy_static;
|
||||
use proc_macro2::{Ident, TokenStream};
|
||||
use quote::{format_ident, quote};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
iter::FromIterator,
|
||||
};
|
||||
|
||||
lazy_static! {
|
||||
static ref SPEC_CONSTANT_OP: HashSet<&'static str> = {
|
||||
HashSet::from_iter([
|
||||
"SConvert",
|
||||
"FConvert",
|
||||
"SNegate",
|
||||
"Not",
|
||||
"IAdd",
|
||||
"ISub",
|
||||
"IMul",
|
||||
"UDiv",
|
||||
"SDiv",
|
||||
"UMod",
|
||||
"SRem",
|
||||
"SMod",
|
||||
"ShiftRightLogical",
|
||||
"ShiftRightArithmetic",
|
||||
"ShiftLeftLogical",
|
||||
"BitwiseOr",
|
||||
"BitwiseXor",
|
||||
"BitwiseAnd",
|
||||
"VectorShuffle",
|
||||
"CompositeExtract",
|
||||
"CompositeInsert",
|
||||
"LogicalOr",
|
||||
"LogicalAnd",
|
||||
"LogicalNot",
|
||||
"LogicalEqual",
|
||||
"LogicalNotEqual",
|
||||
"Select",
|
||||
"IEqual",
|
||||
"INotEqual",
|
||||
"ULessThan",
|
||||
"SLessThan",
|
||||
"UGreaterThan",
|
||||
"SGreaterThan",
|
||||
"ULessThanEqual",
|
||||
"SLessThanEqual",
|
||||
"UGreaterThanEqual",
|
||||
"SGreaterThanEqual",
|
||||
"QuantizeToF16",
|
||||
"ConvertFToS",
|
||||
"ConvertSToF",
|
||||
"ConvertFToU",
|
||||
"ConvertUToF",
|
||||
"UConvert",
|
||||
"ConvertPtrToU",
|
||||
"ConvertUToPtr",
|
||||
"GenericCastToPtr",
|
||||
"PtrCastToGeneric",
|
||||
"Bitcast",
|
||||
"FNegate",
|
||||
"FAdd",
|
||||
"FSub",
|
||||
"FMul",
|
||||
"FDiv",
|
||||
"FRem",
|
||||
"FMod",
|
||||
"AccessChain",
|
||||
"InBoundsAccessChain",
|
||||
"PtrAccessChain",
|
||||
"InBoundsPtrAccessChain",
|
||||
])
|
||||
};
|
||||
}
|
||||
|
||||
pub fn write(grammar: &SpirvGrammar) {
|
||||
let mut instr_members = instruction_members(grammar);
|
||||
let instr_output = instruction_output(&instr_members, false);
|
||||
|
||||
instr_members.retain(|member| SPEC_CONSTANT_OP.contains(member.name.to_string().as_str()));
|
||||
instr_members.iter_mut().for_each(|member| {
|
||||
if member.has_result_type_id {
|
||||
member.operands.remove(0);
|
||||
}
|
||||
if member.has_result_id {
|
||||
member.operands.remove(0);
|
||||
}
|
||||
});
|
||||
let spec_constant_instr_output = instruction_output(&instr_members, true);
|
||||
|
||||
let bit_enum_output = bit_enum_output(&bit_enum_members(grammar));
|
||||
let value_enum_output = value_enum_output(&value_enum_members(grammar));
|
||||
|
||||
write_file(
|
||||
"spirv.rs",
|
||||
format!(
|
||||
"SPIR-V grammar version {}.{}.{}",
|
||||
grammar.major_version, grammar.minor_version, grammar.revision
|
||||
),
|
||||
quote! {
|
||||
#instr_output
|
||||
#spec_constant_instr_output
|
||||
#bit_enum_output
|
||||
#value_enum_output
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct InstructionMember {
|
||||
name: Ident,
|
||||
has_result_id: bool,
|
||||
has_result_type_id: bool,
|
||||
opcode: u16,
|
||||
operands: Vec<OperandMember>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct OperandMember {
|
||||
name: Ident,
|
||||
ty: TokenStream,
|
||||
parse: TokenStream,
|
||||
}
|
||||
|
||||
fn instruction_output(members: &[InstructionMember], spec_constant: bool) -> TokenStream {
|
||||
let struct_items = members
|
||||
.iter()
|
||||
.map(|InstructionMember { name, operands, .. }| {
|
||||
if operands.is_empty() {
|
||||
quote! { #name, }
|
||||
} else {
|
||||
let operands = operands.iter().map(|OperandMember { name, ty, .. }| {
|
||||
quote! { #name: #ty, }
|
||||
});
|
||||
quote! {
|
||||
#name {
|
||||
#(#operands)*
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
let parse_items = members.iter().map(
|
||||
|InstructionMember {
|
||||
name,
|
||||
opcode,
|
||||
operands,
|
||||
..
|
||||
}| {
|
||||
if operands.is_empty() {
|
||||
quote! {
|
||||
#opcode => Self::#name,
|
||||
}
|
||||
} else {
|
||||
let operands_items =
|
||||
operands.iter().map(|OperandMember { name, parse, .. }| {
|
||||
quote! {
|
||||
#name: #parse,
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
#opcode => Self::#name {
|
||||
#(#operands_items)*
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let doc = if spec_constant {
|
||||
"An instruction that is used as the operand of the `SpecConstantOp` instruction."
|
||||
} else {
|
||||
"A parsed SPIR-V instruction."
|
||||
};
|
||||
|
||||
let enum_name = if spec_constant {
|
||||
format_ident!("SpecConstantInstruction")
|
||||
} else {
|
||||
format_ident!("Instruction")
|
||||
};
|
||||
|
||||
let result_fns = if spec_constant {
|
||||
quote! {}
|
||||
} else {
|
||||
let result_id_items = members.iter().filter_map(
|
||||
|InstructionMember {
|
||||
name,
|
||||
has_result_id,
|
||||
..
|
||||
}| {
|
||||
if *has_result_id {
|
||||
Some(quote! { Self::#name { result_id, .. } })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
quote! {
|
||||
/// Returns the `Id` that is assigned by this instruction, if any.
|
||||
pub fn result_id(&self) -> Option<Id> {
|
||||
match self {
|
||||
#(#result_id_items)|* => Some(*result_id),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let opcode_error = if spec_constant {
|
||||
format_ident!("UnknownSpecConstantOpcode")
|
||||
} else {
|
||||
format_ident!("UnknownOpcode")
|
||||
};
|
||||
|
||||
quote! {
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[doc=#doc]
|
||||
pub enum #enum_name {
|
||||
#(#struct_items)*
|
||||
}
|
||||
|
||||
impl #enum_name {
|
||||
fn parse(reader: &mut InstructionReader) -> Result<Self, ParseError> {
|
||||
let opcode = (reader.next_u32()? & 0xffff) as u16;
|
||||
|
||||
Ok(match opcode {
|
||||
#(#parse_items)*
|
||||
opcode => return Err(reader.map_err(ParseErrors::#opcode_error(opcode))),
|
||||
})
|
||||
}
|
||||
|
||||
#result_fns
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn instruction_members(grammar: &SpirvGrammar) -> Vec<InstructionMember> {
|
||||
let operand_kinds = kinds_to_types(grammar);
|
||||
grammar
|
||||
.instructions
|
||||
.iter()
|
||||
.map(|instruction| {
|
||||
let name = format_ident!("{}", instruction.opname.strip_prefix("Op").unwrap());
|
||||
let mut has_result_id = false;
|
||||
let mut has_result_type_id = false;
|
||||
let mut operand_names = HashMap::new();
|
||||
|
||||
let mut operands = instruction
|
||||
.operands
|
||||
.iter()
|
||||
.map(|operand| {
|
||||
let name = if operand.kind == "IdResult" {
|
||||
has_result_id = true;
|
||||
format_ident!("result_id")
|
||||
} else if operand.kind == "IdResultType" {
|
||||
has_result_type_id = true;
|
||||
format_ident!("result_type_id")
|
||||
} else {
|
||||
to_member_name(&operand.kind, operand.name.as_ref().map(|x| x.as_str()))
|
||||
};
|
||||
|
||||
*operand_names.entry(name.clone()).or_insert(0) += 1;
|
||||
|
||||
let (ty, parse) = &operand_kinds[operand.kind.as_str()];
|
||||
let ty = match operand.quantifier {
|
||||
Some('?') => quote! { Option<#ty> },
|
||||
Some('*') => quote! { Vec<#ty> },
|
||||
_ => ty.clone(),
|
||||
};
|
||||
let parse = match operand.quantifier {
|
||||
Some('?') => quote! {
|
||||
if !reader.is_empty() {
|
||||
Some(#parse)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
Some('*') => quote! {{
|
||||
let mut vec = Vec::new();
|
||||
while !reader.is_empty() {
|
||||
vec.push(#parse);
|
||||
}
|
||||
vec
|
||||
}},
|
||||
_ => parse.clone(),
|
||||
};
|
||||
|
||||
OperandMember {
|
||||
name,
|
||||
ty,
|
||||
parse: parse.clone(),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Add number to operands with identical names
|
||||
for name in operand_names
|
||||
.into_iter()
|
||||
.filter_map(|(n, c)| if c > 1 { Some(n) } else { None })
|
||||
{
|
||||
let mut num = 1;
|
||||
|
||||
for operand in operands.iter_mut().filter(|o| o.name == name) {
|
||||
operand.name = format_ident!("{}{}", name, format!("{}", num));
|
||||
num += 1;
|
||||
}
|
||||
}
|
||||
|
||||
InstructionMember {
|
||||
name,
|
||||
has_result_id,
|
||||
has_result_type_id,
|
||||
opcode: instruction.opcode,
|
||||
operands,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct KindEnumMember {
|
||||
name: Ident,
|
||||
value: u32,
|
||||
parameters: Vec<OperandMember>,
|
||||
}
|
||||
|
||||
fn bit_enum_output(enums: &[(Ident, Vec<KindEnumMember>)]) -> TokenStream {
|
||||
let enum_items = enums.iter().map(|(name, members)| {
|
||||
let members_items = members.iter().map(
|
||||
|KindEnumMember {
|
||||
name, parameters, ..
|
||||
}| {
|
||||
if parameters.is_empty() {
|
||||
quote! {
|
||||
pub #name: bool,
|
||||
}
|
||||
} else if let [OperandMember { ty, .. }] = parameters.as_slice() {
|
||||
quote! {
|
||||
pub #name: Option<#ty>,
|
||||
}
|
||||
} else {
|
||||
let params = parameters.iter().map(|OperandMember { ty, .. }| {
|
||||
quote! { #ty }
|
||||
});
|
||||
quote! {
|
||||
pub #name: Option<(#(#params),*)>,
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
let parse_items = members.iter().map(
|
||||
|KindEnumMember {
|
||||
name,
|
||||
value,
|
||||
parameters,
|
||||
..
|
||||
}| {
|
||||
if parameters.is_empty() {
|
||||
quote! {
|
||||
#name: value & #value != 0,
|
||||
}
|
||||
} else {
|
||||
let some = if let [OperandMember { parse, .. }] = parameters.as_slice() {
|
||||
quote! { #parse }
|
||||
} else {
|
||||
let parse = parameters.iter().map(|OperandMember { parse, .. }| parse);
|
||||
quote! { (#(#parse),*) }
|
||||
};
|
||||
|
||||
quote! {
|
||||
#name: if value & #value != 0 {
|
||||
Some(#some)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
quote! {
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct #name {
|
||||
#(#members_items)*
|
||||
}
|
||||
|
||||
impl #name {
|
||||
fn parse(reader: &mut InstructionReader) -> Result<#name, ParseError> {
|
||||
let value = reader.next_u32()?;
|
||||
|
||||
Ok(Self {
|
||||
#(#parse_items)*
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
#(#enum_items)*
|
||||
}
|
||||
}
|
||||
|
||||
fn bit_enum_members(grammar: &SpirvGrammar) -> Vec<(Ident, Vec<KindEnumMember>)> {
|
||||
let parameter_kinds = kinds_to_types(grammar);
|
||||
|
||||
grammar
|
||||
.operand_kinds
|
||||
.iter()
|
||||
.filter(|operand_kind| operand_kind.category == "BitEnum")
|
||||
.map(|operand_kind| {
|
||||
let members = operand_kind
|
||||
.enumerants
|
||||
.iter()
|
||||
.filter_map(|enumerant| {
|
||||
let value = enumerant
|
||||
.value
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.strip_prefix("0x")
|
||||
.unwrap();
|
||||
let value = u32::from_str_radix(value, 16).unwrap();
|
||||
|
||||
if value == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let name = match enumerant.enumerant.to_snake_case().as_str() {
|
||||
"const" => format_ident!("constant"),
|
||||
"not_na_n" => format_ident!("not_nan"),
|
||||
name => format_ident!("{}", name),
|
||||
};
|
||||
|
||||
let parameters = enumerant
|
||||
.parameters
|
||||
.iter()
|
||||
.map(|param| {
|
||||
let name = to_member_name(
|
||||
¶m.kind,
|
||||
param.name.as_ref().map(|x| x.as_str()),
|
||||
);
|
||||
let (ty, parse) = parameter_kinds[param.kind.as_str()].clone();
|
||||
|
||||
OperandMember { name, ty, parse }
|
||||
})
|
||||
.collect();
|
||||
|
||||
Some(KindEnumMember {
|
||||
name,
|
||||
value,
|
||||
parameters,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
(format_ident!("{}", operand_kind.kind), members)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn value_enum_output(enums: &[(Ident, Vec<KindEnumMember>)]) -> TokenStream {
|
||||
let enum_items = enums.iter().map(|(name, members)| {
|
||||
let members_items = members.iter().map(
|
||||
|KindEnumMember {
|
||||
name, parameters, ..
|
||||
}| {
|
||||
if parameters.is_empty() {
|
||||
quote! {
|
||||
#name,
|
||||
}
|
||||
} else {
|
||||
let params = parameters.iter().map(|OperandMember { name, ty, .. }| {
|
||||
quote! { #name: #ty, }
|
||||
});
|
||||
quote! {
|
||||
#name {
|
||||
#(#params)*
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
let parse_items = members.iter().map(
|
||||
|KindEnumMember {
|
||||
name,
|
||||
value,
|
||||
parameters,
|
||||
..
|
||||
}| {
|
||||
if parameters.is_empty() {
|
||||
quote! {
|
||||
#value => Self::#name,
|
||||
}
|
||||
} else {
|
||||
let params_items =
|
||||
parameters.iter().map(|OperandMember { name, parse, .. }| {
|
||||
quote! {
|
||||
#name: #parse,
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
#value => Self::#name {
|
||||
#(#params_items)*
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
let name_string = name.to_string();
|
||||
|
||||
quote! {
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum #name {
|
||||
#(#members_items)*
|
||||
}
|
||||
|
||||
impl #name {
|
||||
fn parse(reader: &mut InstructionReader) -> Result<#name, ParseError> {
|
||||
Ok(match reader.next_u32()? {
|
||||
#(#parse_items)*
|
||||
value => return Err(reader.map_err(ParseErrors::UnknownEnumerant(#name_string, value))),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
#(#enum_items)*
|
||||
}
|
||||
}
|
||||
|
||||
fn value_enum_members(grammar: &SpirvGrammar) -> Vec<(Ident, Vec<KindEnumMember>)> {
|
||||
let parameter_kinds = kinds_to_types(grammar);
|
||||
|
||||
grammar
|
||||
.operand_kinds
|
||||
.iter()
|
||||
.filter(|operand_kind| operand_kind.category == "ValueEnum")
|
||||
.map(|operand_kind| {
|
||||
let members = operand_kind
|
||||
.enumerants
|
||||
.iter()
|
||||
.map(|enumerant| {
|
||||
let name = match enumerant.enumerant.as_str() {
|
||||
"1D" => format_ident!("Dim1D"),
|
||||
"2D" => format_ident!("Dim2D"),
|
||||
"3D" => format_ident!("Dim3D"),
|
||||
name => format_ident!("{}", name),
|
||||
};
|
||||
let parameters = enumerant
|
||||
.parameters
|
||||
.iter()
|
||||
.map(|param| {
|
||||
let name = to_member_name(
|
||||
¶m.kind,
|
||||
param.name.as_ref().map(|x| x.as_str()),
|
||||
);
|
||||
let (ty, parse) = parameter_kinds[param.kind.as_str()].clone();
|
||||
|
||||
OperandMember { name, ty, parse }
|
||||
})
|
||||
.collect();
|
||||
|
||||
KindEnumMember {
|
||||
name,
|
||||
value: enumerant.value.as_u64().unwrap() as u32,
|
||||
parameters,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
(format_ident!("{}", operand_kind.kind), members)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn to_member_name(kind: &str, name: Option<&str>) -> Ident {
|
||||
if let Some(name) = name {
|
||||
let name = name.to_snake_case();
|
||||
|
||||
// Fix some weird names
|
||||
match name.as_str() {
|
||||
"argument_0_argument_1" => format_ident!("arguments"),
|
||||
"member_0_type_member_1_type" => format_ident!("member_types"),
|
||||
"operand_1_operand_2" => format_ident!("operands"),
|
||||
"parameter_0_type_parameter_1_type" => format_ident!("parameter_types"),
|
||||
"the_name_of_the_opaque_type" => format_ident!("name"),
|
||||
"d_ref" => format_ident!("dref"),
|
||||
"type" => format_ident!("ty"), // type is a keyword
|
||||
_ => format_ident!("{}", name.replace("operand_", "operand")),
|
||||
}
|
||||
} else {
|
||||
format_ident!("{}", kind.to_snake_case())
|
||||
}
|
||||
}
|
||||
|
||||
fn kinds_to_types(grammar: &SpirvGrammar) -> HashMap<&str, (TokenStream, TokenStream)> {
|
||||
grammar
|
||||
.operand_kinds
|
||||
.iter()
|
||||
.map(|k| {
|
||||
let (ty, parse) = match k.kind.as_str() {
|
||||
"LiteralContextDependentNumber" => {
|
||||
(quote! { Vec<u32> }, quote! { reader.remainder() })
|
||||
}
|
||||
"LiteralExtInstInteger" | "LiteralInteger" | "LiteralInt32" => {
|
||||
(quote! { u32 }, quote! { reader.next_u32()? })
|
||||
}
|
||||
"LiteralInt64" => (quote! { u64 }, quote! { reader.next_u64()? }),
|
||||
"LiteralFloat32" => (
|
||||
quote! { f32 },
|
||||
quote! { f32::from_bits(reader.next_u32()?) },
|
||||
),
|
||||
"LiteralFloat64" => (
|
||||
quote! { f64 },
|
||||
quote! { f64::from_bits(reader.next_u64()?) },
|
||||
),
|
||||
"LiteralSpecConstantOpInteger" => (
|
||||
quote! { SpecConstantInstruction },
|
||||
quote! { SpecConstantInstruction::parse(reader)? },
|
||||
),
|
||||
"LiteralString" => (quote! { String }, quote! { reader.next_string()? }),
|
||||
"PairIdRefIdRef" => (
|
||||
quote! { (Id, Id) },
|
||||
quote! {
|
||||
(
|
||||
Id(reader.next_u32()?),
|
||||
Id(reader.next_u32()?),
|
||||
)
|
||||
},
|
||||
),
|
||||
"PairIdRefLiteralInteger" => (
|
||||
quote! { (Id, u32) },
|
||||
quote! {
|
||||
(
|
||||
Id(reader.next_u32()?),
|
||||
reader.next_u32()?
|
||||
)
|
||||
},
|
||||
),
|
||||
"PairLiteralIntegerIdRef" => (
|
||||
quote! { (u32, Id) },
|
||||
quote! {
|
||||
(
|
||||
reader.next_u32()?,
|
||||
Id(reader.next_u32()?)),
|
||||
},
|
||||
),
|
||||
_ if k.kind.starts_with("Id") => (quote! { Id }, quote! { Id(reader.next_u32()?) }),
|
||||
ident => {
|
||||
let ident = format_ident!("{}", ident);
|
||||
(quote! { #ident }, quote! { #ident::parse(reader)? })
|
||||
}
|
||||
};
|
||||
|
||||
(k.kind.as_str(), (ty, parse))
|
||||
})
|
||||
.collect()
|
||||
}
|
78
vulkano/autogen/spirv_grammar.rs
Normal file
78
vulkano/autogen/spirv_grammar.rs
Normal file
@ -0,0 +1,78 @@
|
||||
// Copyright (c) 2021 The Vulkano developers
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE or
|
||||
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
|
||||
// license <LICENSE-MIT or https://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 serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{BufReader, Read},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct SpirvGrammar {
|
||||
pub major_version: u16,
|
||||
pub minor_version: u16,
|
||||
pub revision: u16,
|
||||
pub instructions: Vec<SpirvInstruction>,
|
||||
pub operand_kinds: Vec<SpirvOperandKind>,
|
||||
}
|
||||
|
||||
impl SpirvGrammar {
|
||||
pub fn new<P: AsRef<Path> + ?Sized>(path: &P) -> Self {
|
||||
let mut reader = BufReader::new(File::open(path).unwrap());
|
||||
let mut json = String::new();
|
||||
reader.read_to_string(&mut json).unwrap();
|
||||
serde_json::from_str(&json).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct SpirvInstruction {
|
||||
pub opname: String,
|
||||
pub class: String,
|
||||
pub opcode: u16,
|
||||
#[serde(default)]
|
||||
pub operands: Vec<SpirvOperand>,
|
||||
#[serde(default)]
|
||||
pub capabilities: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub extensions: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct SpirvOperand {
|
||||
pub kind: String,
|
||||
pub quantifier: Option<char>,
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct SpirvOperandKind {
|
||||
pub category: String,
|
||||
pub kind: String,
|
||||
#[serde(default)]
|
||||
pub enumerants: Vec<SpirvKindEnumerant>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct SpirvKindEnumerant {
|
||||
pub enumerant: String,
|
||||
pub value: Value,
|
||||
#[serde(default)]
|
||||
pub parameters: Vec<SpirvParameter>,
|
||||
#[serde(default)]
|
||||
pub capabilities: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct SpirvParameter {
|
||||
pub kind: String,
|
||||
pub name: Option<String>,
|
||||
}
|
11799
vulkano/spirv.core.grammar.json
Normal file
11799
vulkano/spirv.core.grammar.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -90,6 +90,7 @@ pub mod memory;
|
||||
pub mod pipeline;
|
||||
pub mod query;
|
||||
pub mod sampler;
|
||||
pub mod spirv;
|
||||
pub mod swapchain;
|
||||
pub mod sync;
|
||||
|
||||
|
@ -358,11 +358,7 @@ impl<'a> DeviceMemoryBuilder<'a> {
|
||||
.lock()
|
||||
.expect("Poisoned mutex");
|
||||
|
||||
if *allocation_count
|
||||
>= physical_device
|
||||
.properties()
|
||||
.max_memory_allocation_count
|
||||
{
|
||||
if *allocation_count >= physical_device.properties().max_memory_allocation_count {
|
||||
return Err(DeviceMemoryAllocError::TooManyObjects);
|
||||
}
|
||||
let fns = self.device.fns();
|
||||
|
782
vulkano/src/spirv.rs
Normal file
782
vulkano/src/spirv.rs
Normal file
@ -0,0 +1,782 @@
|
||||
// Copyright (c) 2021 The Vulkano developers
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE or
|
||||
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
|
||||
// license <LICENSE-MIT or https://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.
|
||||
|
||||
//! Parsing and analysis utilities for SPIR-V shader binaries.
|
||||
//!
|
||||
//! This can be used to inspect and validate a SPIR-V module at runtime. The `Spirv` type does some
|
||||
//! validation, but you should not assume that code that is read successfully is valid.
|
||||
//!
|
||||
//! For more information about SPIR-V modules, instructions and types, see the
|
||||
//! [SPIR-V specification](https://www.khronos.org/registry/SPIR-V/specs/unified1/SPIRV.html).
|
||||
|
||||
use crate::Version;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
error::Error,
|
||||
fmt::{self, Display, Formatter},
|
||||
ops::Range,
|
||||
string::FromUtf8Error,
|
||||
};
|
||||
|
||||
// Generated by build.rs
|
||||
include!(concat!(env!("OUT_DIR"), "/spirv.rs"));
|
||||
|
||||
/// A parsed and analyzed SPIR-V module.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Spirv {
|
||||
version: Version,
|
||||
bound: u32,
|
||||
instructions: Vec<Instruction>,
|
||||
ids: HashMap<Id, IdDataIndices>,
|
||||
|
||||
// Items described in the spec section "Logical Layout of a Module"
|
||||
range_capability: Range<usize>,
|
||||
range_extension: Range<usize>,
|
||||
range_ext_inst_import: Range<usize>,
|
||||
memory_model: usize,
|
||||
range_entry_point: Range<usize>,
|
||||
range_execution_mode: Range<usize>,
|
||||
range_name: Range<usize>,
|
||||
range_decoration: Range<usize>,
|
||||
range_global: Range<usize>,
|
||||
}
|
||||
|
||||
impl Spirv {
|
||||
/// Parses a SPIR-V document from a list of words.
|
||||
pub fn new(words: &[u32]) -> Result<Spirv, SpirvError> {
|
||||
if words.len() < 5 {
|
||||
return Err(SpirvError::InvalidHeader);
|
||||
}
|
||||
|
||||
if words[0] != 0x07230203 {
|
||||
return Err(SpirvError::InvalidHeader);
|
||||
}
|
||||
|
||||
let version = Version {
|
||||
major: (words[1] & 0x00ff0000) >> 16,
|
||||
minor: (words[1] & 0x0000ff00) >> 8,
|
||||
patch: words[1] & 0x000000ff,
|
||||
};
|
||||
|
||||
let bound = words[3];
|
||||
|
||||
let instructions = {
|
||||
let mut ret = Vec::new();
|
||||
let mut rest = &words[5..];
|
||||
while !rest.is_empty() {
|
||||
let word_count = (rest[0] >> 16) as usize;
|
||||
assert!(word_count >= 1);
|
||||
|
||||
if rest.len() < word_count {
|
||||
return Err(ParseError {
|
||||
instruction: ret.len(),
|
||||
word: rest.len(),
|
||||
error: ParseErrors::UnexpectedEOF,
|
||||
words: rest.to_owned(),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
|
||||
let mut reader = InstructionReader::new(&rest[0..word_count], ret.len());
|
||||
let instruction = Instruction::parse(&mut reader)?;
|
||||
|
||||
if !reader.is_empty() {
|
||||
return Err(reader.map_err(ParseErrors::LeftoverOperands).into());
|
||||
}
|
||||
|
||||
ret.push(instruction);
|
||||
rest = &rest[word_count..];
|
||||
}
|
||||
ret
|
||||
};
|
||||
|
||||
// It is impossible for a valid SPIR-V file to contain more Ids than instructions, so put
|
||||
// a sane upper limit on the allocation. This prevents a malicious file from causing huge
|
||||
// memory allocations.
|
||||
let mut ids = HashMap::with_capacity(instructions.len().min(bound as usize));
|
||||
let mut range_capability: Option<Range<usize>> = None;
|
||||
let mut range_extension: Option<Range<usize>> = None;
|
||||
let mut range_ext_inst_import: Option<Range<usize>> = None;
|
||||
let mut range_memory_model: Option<Range<usize>> = None;
|
||||
let mut range_entry_point: Option<Range<usize>> = None;
|
||||
let mut range_execution_mode: Option<Range<usize>> = None;
|
||||
let mut range_name: Option<Range<usize>> = None;
|
||||
let mut range_decoration: Option<Range<usize>> = None;
|
||||
let mut range_global: Option<Range<usize>> = None;
|
||||
let mut in_function = false;
|
||||
|
||||
fn set_range(range: &mut Option<Range<usize>>, index: usize) -> Result<(), SpirvError> {
|
||||
if let Some(range) = range {
|
||||
if range.end != index {
|
||||
return Err(SpirvError::BadLayout { index });
|
||||
}
|
||||
|
||||
range.end = index + 1;
|
||||
} else {
|
||||
*range = Some(Range {
|
||||
start: index,
|
||||
end: index + 1,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
for (index, instruction) in instructions.iter().enumerate() {
|
||||
if let Some(id) = instruction.result_id() {
|
||||
if u32::from(id) >= bound {
|
||||
return Err(SpirvError::IdOutOfBounds { id, index, bound });
|
||||
}
|
||||
|
||||
let members = if let Instruction::TypeStruct { member_types, .. } = instruction {
|
||||
member_types
|
||||
.iter()
|
||||
.map(|_| StructMemberDataIndices::default())
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let data = IdDataIndices {
|
||||
index,
|
||||
names: Vec::new(),
|
||||
decorations: Vec::new(),
|
||||
members,
|
||||
};
|
||||
if let Some(first) = ids.insert(id, data) {
|
||||
return Err(SpirvError::DuplicateId {
|
||||
id,
|
||||
first_index: first.index,
|
||||
second_index: index,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
match instruction {
|
||||
Instruction::Capability { .. } => set_range(&mut range_capability, index)?,
|
||||
Instruction::Extension { .. } => set_range(&mut range_extension, index)?,
|
||||
Instruction::ExtInstImport { .. } => set_range(&mut range_ext_inst_import, index)?,
|
||||
Instruction::MemoryModel { .. } => set_range(&mut range_memory_model, index)?,
|
||||
Instruction::EntryPoint { .. } => set_range(&mut range_entry_point, index)?,
|
||||
Instruction::ExecutionMode { .. } | Instruction::ExecutionModeId { .. } => {
|
||||
set_range(&mut range_execution_mode, index)?
|
||||
}
|
||||
Instruction::Name { .. } | Instruction::MemberName { .. } => {
|
||||
set_range(&mut range_name, index)?
|
||||
}
|
||||
Instruction::Decorate { .. }
|
||||
| Instruction::MemberDecorate { .. }
|
||||
| Instruction::DecorationGroup { .. }
|
||||
| Instruction::GroupDecorate { .. }
|
||||
| Instruction::GroupMemberDecorate { .. }
|
||||
| Instruction::DecorateId { .. }
|
||||
| Instruction::DecorateString { .. }
|
||||
| Instruction::MemberDecorateString { .. } => {
|
||||
set_range(&mut range_decoration, index)?
|
||||
}
|
||||
Instruction::TypeVoid { .. }
|
||||
| Instruction::TypeBool { .. }
|
||||
| Instruction::TypeInt { .. }
|
||||
| Instruction::TypeFloat { .. }
|
||||
| Instruction::TypeVector { .. }
|
||||
| Instruction::TypeMatrix { .. }
|
||||
| Instruction::TypeImage { .. }
|
||||
| Instruction::TypeSampler { .. }
|
||||
| Instruction::TypeSampledImage { .. }
|
||||
| Instruction::TypeArray { .. }
|
||||
| Instruction::TypeRuntimeArray { .. }
|
||||
| Instruction::TypeStruct { .. }
|
||||
| Instruction::TypeOpaque { .. }
|
||||
| Instruction::TypePointer { .. }
|
||||
| Instruction::TypeFunction { .. }
|
||||
| Instruction::TypeEvent { .. }
|
||||
| Instruction::TypeDeviceEvent { .. }
|
||||
| Instruction::TypeReserveId { .. }
|
||||
| Instruction::TypeQueue { .. }
|
||||
| Instruction::TypePipe { .. }
|
||||
| Instruction::TypeForwardPointer { .. }
|
||||
| Instruction::TypePipeStorage { .. }
|
||||
| Instruction::TypeNamedBarrier { .. }
|
||||
| Instruction::TypeRayQueryKHR { .. }
|
||||
| Instruction::TypeAccelerationStructureKHR { .. }
|
||||
| Instruction::TypeCooperativeMatrixNV { .. }
|
||||
| Instruction::TypeVmeImageINTEL { .. }
|
||||
| Instruction::TypeAvcImePayloadINTEL { .. }
|
||||
| Instruction::TypeAvcRefPayloadINTEL { .. }
|
||||
| Instruction::TypeAvcSicPayloadINTEL { .. }
|
||||
| Instruction::TypeAvcMcePayloadINTEL { .. }
|
||||
| Instruction::TypeAvcMceResultINTEL { .. }
|
||||
| Instruction::TypeAvcImeResultINTEL { .. }
|
||||
| Instruction::TypeAvcImeResultSingleReferenceStreamoutINTEL { .. }
|
||||
| Instruction::TypeAvcImeResultDualReferenceStreamoutINTEL { .. }
|
||||
| Instruction::TypeAvcImeSingleReferenceStreaminINTEL { .. }
|
||||
| Instruction::TypeAvcImeDualReferenceStreaminINTEL { .. }
|
||||
| Instruction::TypeAvcRefResultINTEL { .. }
|
||||
| Instruction::TypeAvcSicResultINTEL { .. }
|
||||
| Instruction::ConstantTrue { .. }
|
||||
| Instruction::ConstantFalse { .. }
|
||||
| Instruction::Constant { .. }
|
||||
| Instruction::ConstantComposite { .. }
|
||||
| Instruction::ConstantSampler { .. }
|
||||
| Instruction::ConstantNull { .. }
|
||||
| Instruction::ConstantPipeStorage { .. }
|
||||
| Instruction::SpecConstantTrue { .. }
|
||||
| Instruction::SpecConstantFalse { .. }
|
||||
| Instruction::SpecConstant { .. }
|
||||
| Instruction::SpecConstantComposite { .. }
|
||||
| Instruction::SpecConstantOp { .. } => set_range(&mut range_global, index)?,
|
||||
Instruction::Variable { storage_class, .. }
|
||||
if *storage_class != StorageClass::Function =>
|
||||
{
|
||||
set_range(&mut range_global, index)?
|
||||
}
|
||||
Instruction::Function { .. } => {
|
||||
in_function = true;
|
||||
}
|
||||
Instruction::Line { .. } | Instruction::NoLine { .. } => {
|
||||
if !in_function {
|
||||
set_range(&mut range_global, index)?
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
let mut spirv = Spirv {
|
||||
version,
|
||||
bound,
|
||||
instructions,
|
||||
ids,
|
||||
|
||||
range_capability: range_capability.unwrap_or_default(),
|
||||
range_extension: range_extension.unwrap_or_default(),
|
||||
range_ext_inst_import: range_ext_inst_import.unwrap_or_default(),
|
||||
memory_model: if let Some(range) = range_memory_model {
|
||||
if range.end - range.start != 1 {
|
||||
return Err(SpirvError::MemoryModelInvalid);
|
||||
}
|
||||
|
||||
range.start
|
||||
} else {
|
||||
return Err(SpirvError::MemoryModelInvalid);
|
||||
},
|
||||
range_entry_point: range_entry_point.unwrap_or_default(),
|
||||
range_execution_mode: range_execution_mode.unwrap_or_default(),
|
||||
range_name: range_name.unwrap_or_default(),
|
||||
range_decoration: range_decoration.unwrap_or_default(),
|
||||
range_global: range_global.unwrap_or_default(),
|
||||
};
|
||||
|
||||
for index in spirv.range_name.clone() {
|
||||
match &spirv.instructions[index] {
|
||||
Instruction::Name { target, .. } => {
|
||||
spirv.ids.get_mut(target).unwrap().names.push(index);
|
||||
}
|
||||
Instruction::MemberName { ty, member, .. } => {
|
||||
spirv.ids.get_mut(ty).unwrap().members[*member as usize]
|
||||
.names
|
||||
.push(index);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
// First handle all regular decorations, including those targeting decoration groups.
|
||||
for index in spirv.range_decoration.clone() {
|
||||
match &spirv.instructions[index] {
|
||||
Instruction::Decorate { target, .. }
|
||||
| Instruction::DecorateId { target, .. }
|
||||
| Instruction::DecorateString { target, .. } => {
|
||||
spirv.ids.get_mut(target).unwrap().decorations.push(index);
|
||||
}
|
||||
Instruction::MemberDecorate {
|
||||
structure_type: target,
|
||||
member,
|
||||
..
|
||||
}
|
||||
| Instruction::MemberDecorateString {
|
||||
struct_type: target,
|
||||
member,
|
||||
..
|
||||
} => {
|
||||
spirv.ids.get_mut(target).unwrap().members[*member as usize]
|
||||
.decorations
|
||||
.push(index);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
// Then, with decoration groups having their lists complete, handle group decorates.
|
||||
for index in spirv.range_decoration.clone() {
|
||||
match &spirv.instructions[index] {
|
||||
Instruction::GroupDecorate {
|
||||
decoration_group,
|
||||
targets,
|
||||
..
|
||||
} => {
|
||||
let indices = {
|
||||
let data = &spirv.ids[decoration_group];
|
||||
if !matches!(
|
||||
spirv.instructions[data.index],
|
||||
Instruction::DecorationGroup { .. }
|
||||
) {
|
||||
return Err(SpirvError::GroupDecorateNotGroup { index });
|
||||
};
|
||||
data.decorations.clone()
|
||||
};
|
||||
|
||||
for target in targets {
|
||||
spirv
|
||||
.ids
|
||||
.get_mut(target)
|
||||
.unwrap()
|
||||
.decorations
|
||||
.extend(&indices);
|
||||
}
|
||||
}
|
||||
Instruction::GroupMemberDecorate {
|
||||
decoration_group,
|
||||
targets,
|
||||
..
|
||||
} => {
|
||||
let indices = {
|
||||
let data = &spirv.ids[decoration_group];
|
||||
if !matches!(
|
||||
spirv.instructions[data.index],
|
||||
Instruction::DecorationGroup { .. }
|
||||
) {
|
||||
return Err(SpirvError::GroupDecorateNotGroup { index });
|
||||
};
|
||||
data.decorations.clone()
|
||||
};
|
||||
|
||||
for (target, member) in targets {
|
||||
spirv.ids.get_mut(target).unwrap().members[*member as usize]
|
||||
.decorations
|
||||
.extend(&indices);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(spirv)
|
||||
}
|
||||
|
||||
/// Returns a reference to the instructions in the module.
|
||||
#[inline]
|
||||
pub fn instructions(&self) -> &[Instruction] {
|
||||
&self.instructions
|
||||
}
|
||||
|
||||
/// Returns the SPIR-V version that the module is compiled for.
|
||||
#[inline]
|
||||
pub fn version(&self) -> Version {
|
||||
self.version
|
||||
}
|
||||
|
||||
/// Returns the upper bound of `Id`s. All `Id`s should have a numeric value strictly less than
|
||||
/// this value.
|
||||
#[inline]
|
||||
pub fn bound(&self) -> u32 {
|
||||
self.bound
|
||||
}
|
||||
|
||||
/// Returns information about an `Id`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// - Panics if `id` is not defined in this module. This can in theory only happpen if you are
|
||||
/// mixing `Id`s from different modules.
|
||||
#[inline]
|
||||
pub fn id<'a>(&'a self, id: Id) -> IdInfo<'a> {
|
||||
IdInfo {
|
||||
data_indices: &self.ids[&id],
|
||||
instructions: &self.instructions,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator over all `Capability` instructions.
|
||||
#[inline]
|
||||
pub fn iter_capability(&self) -> impl ExactSizeIterator<Item = &Instruction> {
|
||||
self.instructions[self.range_capability.clone()].iter()
|
||||
}
|
||||
|
||||
/// Returns an iterator over all `Extension` instructions.
|
||||
#[inline]
|
||||
pub fn iter_extension(&self) -> impl ExactSizeIterator<Item = &Instruction> {
|
||||
self.instructions[self.range_extension.clone()].iter()
|
||||
}
|
||||
|
||||
/// Returns an iterator over all `ExtInstImport` instructions.
|
||||
#[inline]
|
||||
pub fn iter_ext_inst_import(&self) -> impl ExactSizeIterator<Item = &Instruction> {
|
||||
self.instructions[self.range_ext_inst_import.clone()].iter()
|
||||
}
|
||||
|
||||
/// Returns the `MemoryModel` instruction.
|
||||
#[inline]
|
||||
pub fn memory_model(&self) -> &Instruction {
|
||||
&self.instructions[self.memory_model]
|
||||
}
|
||||
|
||||
/// Returns an iterator over all `EntryPoint` instructions.
|
||||
#[inline]
|
||||
pub fn iter_entry_point(&self) -> impl ExactSizeIterator<Item = &Instruction> {
|
||||
self.instructions[self.range_entry_point.clone()].iter()
|
||||
}
|
||||
|
||||
/// Returns an iterator over all execution mode instructions.
|
||||
#[inline]
|
||||
pub fn iter_execution_mode(&self) -> impl ExactSizeIterator<Item = &Instruction> {
|
||||
self.instructions[self.range_execution_mode.clone()].iter()
|
||||
}
|
||||
|
||||
/// Returns an iterator over all name debug instructions.
|
||||
#[inline]
|
||||
pub fn iter_name(&self) -> impl ExactSizeIterator<Item = &Instruction> {
|
||||
self.instructions[self.range_name.clone()].iter()
|
||||
}
|
||||
|
||||
/// Returns an iterator over all decoration instructions.
|
||||
#[inline]
|
||||
pub fn iter_decoration(&self) -> impl ExactSizeIterator<Item = &Instruction> {
|
||||
self.instructions[self.range_decoration.clone()].iter()
|
||||
}
|
||||
|
||||
/// Returns an iterator over all global declaration instructions: types,
|
||||
/// constants and global variables.
|
||||
///
|
||||
/// Note: This can also include `Line` and `NoLine` instructions.
|
||||
#[inline]
|
||||
pub fn iter_global(&self) -> impl ExactSizeIterator<Item = &Instruction> {
|
||||
self.instructions[self.range_global.clone()].iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct IdDataIndices {
|
||||
index: usize,
|
||||
names: Vec<usize>,
|
||||
decorations: Vec<usize>,
|
||||
members: Vec<StructMemberDataIndices>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct StructMemberDataIndices {
|
||||
names: Vec<usize>,
|
||||
decorations: Vec<usize>,
|
||||
}
|
||||
|
||||
/// Information associated with an `Id`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct IdInfo<'a> {
|
||||
data_indices: &'a IdDataIndices,
|
||||
instructions: &'a [Instruction],
|
||||
}
|
||||
|
||||
impl<'a> IdInfo<'a> {
|
||||
/// Returns the instruction that defines this `Id` with a `result_id` operand.
|
||||
#[inline]
|
||||
pub fn instruction(&self) -> &'a Instruction {
|
||||
&self.instructions[self.data_indices.index]
|
||||
}
|
||||
|
||||
/// Returns an iterator over all name debug instructions that target this `Id`.
|
||||
#[inline]
|
||||
pub fn iter_name(&self) -> impl ExactSizeIterator<Item = &'a Instruction> {
|
||||
let instructions = self.instructions;
|
||||
self.data_indices
|
||||
.names
|
||||
.iter()
|
||||
.map(move |&index| &instructions[index])
|
||||
}
|
||||
|
||||
/// Returns an iterator over all decorate instructions, that target this `Id`. This includes any
|
||||
/// decorate instructions that target this `Id` indirectly via a `DecorationGroup`.
|
||||
#[inline]
|
||||
pub fn iter_decoration(&self) -> impl ExactSizeIterator<Item = &'a Instruction> {
|
||||
let instructions = self.instructions;
|
||||
self.data_indices
|
||||
.decorations
|
||||
.iter()
|
||||
.map(move |&index| &instructions[index])
|
||||
}
|
||||
|
||||
/// If this `Id` refers to a `TypeStruct`, returns an iterator of information about each member
|
||||
/// of the struct. Empty otherwise.
|
||||
#[inline]
|
||||
pub fn iter_members(&self) -> impl ExactSizeIterator<Item = StructMemberInfo<'a>> {
|
||||
let instructions = self.instructions;
|
||||
self.data_indices
|
||||
.members
|
||||
.iter()
|
||||
.map(move |data_indices| StructMemberInfo {
|
||||
data_indices,
|
||||
instructions,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Information associated with a member of a `TypeStruct` instruction.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct StructMemberInfo<'a> {
|
||||
data_indices: &'a StructMemberDataIndices,
|
||||
instructions: &'a [Instruction],
|
||||
}
|
||||
|
||||
impl<'a> StructMemberInfo<'a> {
|
||||
/// Returns an iterator over all name debug instructions that target this struct member.
|
||||
#[inline]
|
||||
pub fn iter_name(&self) -> impl ExactSizeIterator<Item = &'a Instruction> {
|
||||
let instructions = self.instructions;
|
||||
self.data_indices
|
||||
.names
|
||||
.iter()
|
||||
.map(move |&index| &instructions[index])
|
||||
}
|
||||
|
||||
/// Returns an iterator over all decorate instructions that target this struct member. This
|
||||
/// includes any decorate instructions that target this member indirectly via a
|
||||
/// `DecorationGroup`.
|
||||
#[inline]
|
||||
pub fn iter_decoration(&self) -> impl ExactSizeIterator<Item = &'a Instruction> {
|
||||
let instructions = self.instructions;
|
||||
self.data_indices
|
||||
.decorations
|
||||
.iter()
|
||||
.map(move |&index| &instructions[index])
|
||||
}
|
||||
}
|
||||
|
||||
/// Used in SPIR-V to refer to the result of another instruction.
|
||||
///
|
||||
/// Ids are global across a module, and are always assigned by exactly one instruction.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct Id(u32);
|
||||
|
||||
impl From<Id> for u32 {
|
||||
#[inline]
|
||||
fn from(id: Id) -> u32 {
|
||||
id.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Id {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "%{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper type for parsing the words of an instruction.
|
||||
#[derive(Debug)]
|
||||
struct InstructionReader<'a> {
|
||||
words: &'a [u32],
|
||||
next_word: usize,
|
||||
instruction: usize,
|
||||
}
|
||||
|
||||
impl<'a> InstructionReader<'a> {
|
||||
/// Constructs a new reader from a slice of words for a single instruction, including the opcode
|
||||
/// word. `instruction` is the number of the instruction currently being read, and is used for
|
||||
/// error reporting.
|
||||
#[inline]
|
||||
fn new(words: &'a [u32], instruction: usize) -> Self {
|
||||
debug_assert!(!words.is_empty());
|
||||
Self {
|
||||
words,
|
||||
next_word: 0,
|
||||
instruction,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether the reader has reached the end of the current instruction.
|
||||
#[inline]
|
||||
fn is_empty(&self) -> bool {
|
||||
self.next_word >= self.words.len()
|
||||
}
|
||||
|
||||
/// Converts the `ParseErrors` enum to the `ParseError` struct, adding contextual information.
|
||||
#[inline]
|
||||
fn map_err(&self, error: ParseErrors) -> ParseError {
|
||||
ParseError {
|
||||
instruction: self.instruction,
|
||||
word: self.next_word - 1, // -1 because the word has already been read
|
||||
error,
|
||||
words: self.words.to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the next word in the sequence.
|
||||
#[inline]
|
||||
fn next_u32(&mut self) -> Result<u32, ParseError> {
|
||||
let word = *self.words.get(self.next_word).ok_or(ParseError {
|
||||
instruction: self.instruction,
|
||||
word: self.next_word, // No -1 because we didn't advance yet
|
||||
error: ParseErrors::MissingOperands,
|
||||
words: self.words.to_owned(),
|
||||
})?;
|
||||
self.next_word += 1;
|
||||
Ok(word)
|
||||
}
|
||||
|
||||
/// Returns the next two words as a single `u64`.
|
||||
#[inline]
|
||||
fn next_u64(&mut self) -> Result<u64, ParseError> {
|
||||
Ok(self.next_u32()? as u64 | (self.next_u32()? as u64) << 32)
|
||||
}
|
||||
|
||||
/// Reads a nul-terminated string.
|
||||
fn next_string(&mut self) -> Result<String, ParseError> {
|
||||
let mut bytes = Vec::new();
|
||||
loop {
|
||||
let word = self.next_u32()?.to_le_bytes();
|
||||
|
||||
if let Some(nul) = word.iter().position(|&b| b == 0) {
|
||||
bytes.extend(&word[0..nul]);
|
||||
break;
|
||||
} else {
|
||||
bytes.extend(word);
|
||||
}
|
||||
}
|
||||
String::from_utf8(bytes).map_err(|err| self.map_err(ParseErrors::FromUtf8Error(err)))
|
||||
}
|
||||
|
||||
/// Reads all remaining words.
|
||||
#[inline]
|
||||
fn remainder(&mut self) -> Vec<u32> {
|
||||
let vec = self.words[self.next_word..].to_owned();
|
||||
self.next_word = self.words.len();
|
||||
vec
|
||||
}
|
||||
}
|
||||
|
||||
/// Error that can happen when reading a SPIR-V module.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum SpirvError {
|
||||
BadLayout {
|
||||
index: usize,
|
||||
},
|
||||
DuplicateId {
|
||||
id: Id,
|
||||
first_index: usize,
|
||||
second_index: usize,
|
||||
},
|
||||
GroupDecorateNotGroup {
|
||||
index: usize,
|
||||
},
|
||||
IdOutOfBounds {
|
||||
id: Id,
|
||||
index: usize,
|
||||
bound: u32,
|
||||
},
|
||||
InvalidHeader,
|
||||
MemoryModelInvalid,
|
||||
ParseError(ParseError),
|
||||
}
|
||||
|
||||
impl Display for SpirvError {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::BadLayout { index } => write!(
|
||||
f,
|
||||
"the instruction at index {} does not follow the logical layout of a module",
|
||||
index
|
||||
),
|
||||
Self::DuplicateId {
|
||||
id,
|
||||
first_index,
|
||||
second_index,
|
||||
} => write!(
|
||||
f,
|
||||
"id {} is assigned more than once, by instructions {} and {}",
|
||||
id, first_index, second_index
|
||||
),
|
||||
Self::GroupDecorateNotGroup { index } => write!(f, "a GroupDecorate or GroupMemberDecorate instruction at index {} referred to an Id that was not a DecorationGroup", index),
|
||||
Self::IdOutOfBounds { id, bound, index, } => write!(f, "id {}, assigned at instruction {}, is not below the maximum bound {}", id, index, bound),
|
||||
Self::InvalidHeader => write!(f, "the SPIR-V module header is invalid"),
|
||||
Self::MemoryModelInvalid => {
|
||||
write!(f, "the MemoryModel instruction is not present exactly once")
|
||||
}
|
||||
Self::ParseError(_) => write!(f, "parse error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for SpirvError {
|
||||
#[inline]
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
match self {
|
||||
Self::ParseError(err) => Some(err),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseError> for SpirvError {
|
||||
#[inline]
|
||||
fn from(err: ParseError) -> Self {
|
||||
Self::ParseError(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// Error that can happen when parsing SPIR-V instructions into Rust data structures.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ParseError {
|
||||
/// The instruction number the error happened at, starting from 0.
|
||||
pub instruction: usize,
|
||||
/// The word from the start of the instruction that the error happened at, starting from 0.
|
||||
pub word: usize,
|
||||
/// The error.
|
||||
pub error: ParseErrors,
|
||||
/// The words of the instruction.
|
||||
pub words: Vec<u32>,
|
||||
}
|
||||
|
||||
impl Display for ParseError {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"at instruction {}, word {}: {}",
|
||||
self.instruction, self.word, self.error
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseError {}
|
||||
|
||||
/// Individual types of parse error that can happen.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ParseErrors {
|
||||
FromUtf8Error(FromUtf8Error),
|
||||
LeftoverOperands,
|
||||
MissingOperands,
|
||||
UnexpectedEOF,
|
||||
UnknownEnumerant(&'static str, u32),
|
||||
UnknownOpcode(u16),
|
||||
UnknownSpecConstantOpcode(u16),
|
||||
}
|
||||
|
||||
impl Display for ParseErrors {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::FromUtf8Error(err) => write!(f, "invalid UTF-8 in string literal"),
|
||||
Self::LeftoverOperands => write!(f, "unparsed operands remaining"),
|
||||
Self::MissingOperands => write!(f, "the instruction and its operands require more words than are present in the instruction"),
|
||||
Self::UnexpectedEOF => write!(f, "encountered unexpected end of file"),
|
||||
Self::UnknownEnumerant(ty, enumerant) => write!(f, "invalid enumerant {} for enum {}", enumerant, ty),
|
||||
Self::UnknownOpcode(opcode) => write!(f, "invalid instruction opcode {}", opcode),
|
||||
Self::UnknownSpecConstantOpcode(opcode) => write!(f, "invalid spec constant instruction opcode {}", opcode),
|
||||
}
|
||||
}
|
||||
}
|
@ -304,8 +304,8 @@ impl From<OomError> for SemaphoreError {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::VulkanObject;
|
||||
use crate::sync::Semaphore;
|
||||
use crate::VulkanObject;
|
||||
|
||||
#[test]
|
||||
fn semaphore_create() {
|
||||
|
@ -27,6 +27,9 @@ impl Version {
|
||||
pub const V1_0: Version = Version::major_minor(1, 0);
|
||||
pub const V1_1: Version = Version::major_minor(1, 1);
|
||||
pub const V1_2: Version = Version::major_minor(1, 2);
|
||||
pub const V1_3: Version = Version::major_minor(1, 3);
|
||||
pub const V1_4: Version = Version::major_minor(1, 4);
|
||||
pub const V1_5: Version = Version::major_minor(1, 5);
|
||||
|
||||
/// Constructs a `Version` from the given major and minor version numbers.
|
||||
#[inline]
|
||||
|
Loading…
Reference in New Issue
Block a user