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:
Rua 2021-09-13 14:39:17 +02:00 committed by GitHub
parent ce110006bc
commit 8d321430cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 14471 additions and 2108 deletions

View File

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

View File

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

View File

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

View File

@ -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) {

View File

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

View File

@ -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,11 +310,13 @@ 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 {
for instruction in spirv
.iter_entry_point()
.filter(|instruction| matches!(instruction, Instruction::EntryPoint { .. }))
{
let entry_point = entry_point::write_entry_point(
prefix,
&doc,
&spirv,
instruction,
types_meta,
exact_entrypoint_interface,
@ -322,7 +324,6 @@ where
);
entry_points_inside_impl.push(entry_point);
}
}
let include_bytes = input_paths.map(|s| {
quote! {
@ -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]

View File

@ -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
),
};
(*result_id, *real_type, storage_class)
}
_ => return None,
};
if exact && !variables.as_ref().unwrap().contains(&variable_id) {
continue;
return None;
}
let set = set_decoration.params[0];
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 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 binding_num = variable_id_info
.iter_decoration()
.find_map(|instruction| match instruction {
Instruction::Decorate {
decoration: Decoration::Binding { binding_point },
..
} => Some(*binding_point),
_ => None,
})
.unwrap();
// 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 name = variable_id_info
.iter_name()
.find_map(|instruction| match instruction {
Instruction::Name { name, .. } => Some(name.as_str()),
_ => None,
})
.unwrap_or("__unnamed");
let nonwritable = doc
.get_decoration_params(variable_id, Decoration::NonWritable)
.is_some();
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(doc, pointed_ty, storage_class, false).expect(&format!(
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 {
Some(Descriptor {
desc_ty,
set,
binding,
set_num,
binding_num,
descriptor_count,
mutable: !nonwritable && mutable,
variable_count: variable_count,
});
}
descriptors
})
})
.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,69 +359,60 @@ 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 {
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()
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)
@ -410,23 +424,19 @@ fn descriptor_infos(
Some((ty, mutable, 1, false))
}
&Instruction::TypeImage {
result_id,
ref dim,
arrayed,
ms,
sampled,
ref format,
ref image_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);
} => {
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::DimSubpassData => {
Dim::SubpassData => {
// VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT
assert!(
!force_combined_image_sampled,
@ -435,11 +445,11 @@ fn descriptor_infos(
SubpassData"
);
assert!(
*format == ImageFormat::Unknown,
*image_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!(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 {
@ -449,10 +459,11 @@ fn descriptor_infos(
Some((desc, true, 1, false)) // Never writable.
}
Dim::DimBuffer => {
let (ty, mutable) = if sampled {
Dim::Buffer => {
let (ty, mutable) = if sampled == 1 {
// VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER
(quote! { DescriptorDescTy::UniformTexelBuffer }, false) // Uniforms are never mutable.
(quote! { DescriptorDescTy::UniformTexelBuffer }, false)
// Uniforms are never mutable.
} else {
// VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER
(quote! { DescriptorDescTy::StorageTexelBuffer }, true)
@ -468,15 +479,15 @@ fn descriptor_infos(
}
_ => {
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"),
(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!(),
};
@ -491,7 +502,10 @@ fn descriptor_infos(
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");
assert!(
sampled == 1,
"A combined image sampler must not reference a storage image"
);
(
quote! {
@ -503,7 +517,7 @@ fn descriptor_infos(
false, // Sampled images are never mutable.
)
} else {
let (ty, mutable) = if sampled {
let (ty, mutable) = if sampled == 1 {
// VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE
(quote! { DescriptorDescTy::SampledImage }, false) // Sampled images are never mutable.
} else {
@ -526,53 +540,39 @@ fn descriptor_infos(
}
}
&Instruction::TypeSampledImage {
result_id,
image_type_id,
} if result_id == pointed_ty => {
descriptor_infos(doc, image_type_id, pointer_storage.clone(), true)
&Instruction::TypeSampledImage { image_type, .. } => {
descriptor_infos(spirv, image_type, pointer_storage, true)
}
&Instruction::TypeSampler { result_id } if result_id == pointed_ty => {
&Instruction::TypeSampler { .. } => {
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 => {
element_type,
length,
..
} => {
let (desc, mutable, arr, variable_count) =
match descriptor_infos(doc, type_id, pointer_storage.clone(), false) {
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 = 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 = 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 {
result_id,
type_id,
} if result_id == pointed_ty => {
&Instruction::TypeRuntimeArray { element_type, .. } => {
let (desc, mutable, arr, variable_count) =
match descriptor_infos(doc, type_id, pointer_storage.clone(), false) {
match descriptor_infos(spirv, element_type, pointer_storage, false) {
None => return None,
Some(v) => v,
};
@ -584,15 +584,12 @@ fn descriptor_infos(
_ => None, // TODO: other types
}
})
.next()
}
#[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) },

View File

@ -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,
let execution_mode =
spirv
.iter_execution_mode()
.find_map(|instruction| match instruction {
&Instruction::ExecutionMode {
entry_point,
ref mode,
..
} = instruction
{
if target_id == id {
execution_mode = match 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,44 +238,59 @@ 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 {
for &interface in interface.iter() {
let interface_info = spirv.id(interface);
match interface_info.instruction() {
&Instruction::Variable {
result_type_id,
result_id,
ref storage_class,
..
} if &result_id == interface => {
if spirv_search::is_builtin(doc, result_id) {
} => {
if spirv_search::is_builtin(spirv, result_id) {
continue;
}
let id_info = spirv.id(result_id);
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 = spirv_search::name_from_id(doc, result_id);
if name == "__unnamed" {
continue;
} // FIXME: hack
let location = match doc.get_decoration_params(result_id, Decoration::Location)
{
Some(l) => l[0],
None => panic!(
"Attribute `{}` (id {}) is missing a location",
name, result_id
),
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);
spirv_search::format_from_id(spirv, result_type_id, ignore_first_array);
to_write.push(Element {
location,
name,
name: name.to_owned(),
format,
location_len,
});
@ -281,7 +298,6 @@ fn write_interfaces(
_ => (),
}
}
}
(
write_interface(&input_elements),

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -7,187 +7,153 @@
// 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 {
pub fn format_from_id(spirv: &Spirv, searched: Id, ignore_first_array: bool) -> (String, usize) {
let id_info = spirv.id(searched);
match id_info.instruction() {
&Instruction::TypeInt {
result_id,
width,
signedness,
} if result_id == searched => {
width, signedness, ..
} => {
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",
(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!(),
};
return (format.to_string(), 1);
(format.to_string(), 1)
}
&Instruction::TypeFloat { result_id, width } if result_id == searched => {
&Instruction::TypeFloat { width, .. } => {
assert!(!ignore_first_array);
let format = match width {
32 => "R32_SFLOAT",
64 => "R64_SFLOAT",
_ => panic!(),
};
return (format.to_string(), 1);
(format.to_string(), 1)
}
&Instruction::TypeVector {
result_id,
component_id,
count,
} if result_id == searched => {
component_type,
component_count,
..
} => {
assert!(!ignore_first_array);
let (format, sz) = format_from_id(doc, component_id, false);
let (format, sz) = format_from_id(spirv, component_type, false);
assert!(format.starts_with("R32"));
assert_eq!(sz, 1);
let format = match count {
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"),
};
return (format, sz);
(format, sz)
}
&Instruction::TypeMatrix {
result_id,
column_type_id,
column_type,
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);
let (format, sz) = format_from_id(spirv, column_type, false);
(format, sz * column_count as usize)
}
&Instruction::TypeArray {
result_id,
type_id,
length_id,
} if result_id == searched => {
element_type,
length,
..
} => {
if ignore_first_array {
return format_from_id(doc, type_id, false);
}
let (format, sz) = format_from_id(doc, type_id, false);
let len = doc
.instructions
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);
}
_ => (),
(format, sz * len as usize)
}
}
panic!("Type #{} not found or invalid", searched)
&Instruction::TypePointer { ty, .. } => format_from_id(spirv, ty, ignore_first_array),
_ => panic!("Type #{} not found or invalid", searched),
}
pub fn name_from_id(doc: &Spirv, searched: u32) -> String {
for instruction in &doc.instructions {
if let &Instruction::Name {
target_id,
ref name,
} = instruction
{
if target_id == searched {
return name.clone();
}
}
}
String::from("__unnamed")
}
pub fn member_name_from_id(doc: &Spirv, searched: u32, searched_member: u32) -> String {
for instruction in &doc.instructions {
if let &Instruction::MemberName {
target_id,
member,
ref name,
} = instruction
{
if target_id == searched && member == searched_member {
return name.clone();
}
}
}
String::from("__unnamed")
}
/// 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;
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 { .. },
..
}
if doc.get_member_decoration_builtin_params(id).is_some() {
)
}) {
return true;
}
for instruction in &doc.instructions {
match *instruction {
Instruction::Variable {
result_type_id,
result_id,
if id_info
.iter_members()
.flat_map(|member_info| member_info.iter_decoration())
.any(|instruction| {
matches!(
instruction,
Instruction::MemberDecorate {
decoration: Decoration::BuiltIn { .. },
..
} 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);
)
})
{
return true;
}
Instruction::TypeRuntimeArray { result_id, type_id } if result_id == id => {
return is_builtin(doc, type_id);
match id_info.instruction() {
Instruction::Variable { result_type_id, .. } => {
return is_builtin(spirv, *result_type_id);
}
Instruction::TypeStruct {
result_id,
ref member_types,
} if result_id == id => {
for &mem in member_types {
if is_builtin(doc, mem) {
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 {
result_id, type_id, ..
} if result_id == id => {
return is_builtin(doc, type_id);
Instruction::TypePointer { ty, .. } => {
return is_builtin(spirv, *ty);
}
_ => (),
}
}
false
}

View File

@ -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]);
}
}
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]);
result_id,
element_type,
..
}
| 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,21 +415,20 @@ 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 => {
let id_info = spirv.id(searched);
match id_info.instruction() {
Instruction::TypeBool { .. } => {
panic!("Can't put booleans in structs")
}
&Instruction::TypeInt {
result_id,
width,
signedness,
} if result_id == searched => match (width, signedness) {
(8, true) => {
Instruction::TypeInt {
width, signedness, ..
} => match (width, signedness) {
(8, 1) => {
#[repr(C)]
struct Foo {
data: i8,
@ -411,7 +441,7 @@ pub(super) fn type_from_id(
mem::align_of::<Foo>(),
);
}
(8, false) => {
(8, 0) => {
#[repr(C)]
struct Foo {
data: u8,
@ -424,7 +454,7 @@ pub(super) fn type_from_id(
mem::align_of::<Foo>(),
);
}
(16, true) => {
(16, 1) => {
#[repr(C)]
struct Foo {
data: i16,
@ -437,7 +467,7 @@ pub(super) fn type_from_id(
mem::align_of::<Foo>(),
);
}
(16, false) => {
(16, 0) => {
#[repr(C)]
struct Foo {
data: u16,
@ -450,7 +480,7 @@ pub(super) fn type_from_id(
mem::align_of::<Foo>(),
);
}
(32, true) => {
(32, 1) => {
#[repr(C)]
struct Foo {
data: i32,
@ -463,7 +493,7 @@ pub(super) fn type_from_id(
mem::align_of::<Foo>(),
);
}
(32, false) => {
(32, 0) => {
#[repr(C)]
struct Foo {
data: u32,
@ -476,7 +506,7 @@ pub(super) fn type_from_id(
mem::align_of::<Foo>(),
);
}
(64, true) => {
(64, 1) => {
#[repr(C)]
struct Foo {
data: i64,
@ -489,7 +519,7 @@ pub(super) fn type_from_id(
mem::align_of::<Foo>(),
);
}
(64, false) => {
(64, 0) => {
#[repr(C)]
struct Foo {
data: u64,
@ -504,7 +534,7 @@ pub(super) fn type_from_id(
}
_ => panic!("No Rust equivalent for an integer of width {}", width),
},
&Instruction::TypeFloat { result_id, width } if result_id == searched => match width {
Instruction::TypeFloat { width, .. } => match width {
32 => {
#[repr(C)]
struct Foo {
@ -534,15 +564,15 @@ pub(super) fn type_from_id(
_ => panic!("No Rust equivalent for a floating-point of width {}", width),
},
&Instruction::TypeVector {
result_id,
component_id,
count,
} if result_id == searched => {
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, doc, component_id, types_meta);
let array_length = count as usize;
let size = t_size.map(|s| s * count as usize);
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)),
@ -551,14 +581,13 @@ pub(super) fn type_from_id(
);
}
&Instruction::TypeMatrix {
result_id,
column_type_id,
column_type,
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 (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 (
@ -569,30 +598,28 @@ pub(super) fn type_from_id(
);
}
&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,
element_type,
length,
..
} if result_id == length_id => Some(data.clone()),
} => {
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,
})
.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];
.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 \
@ -608,9 +635,9 @@ pub(super) fn type_from_id(
t_align,
);
}
&Instruction::TypeRuntimeArray { result_id, type_id } if result_id == searched => {
&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, doc, type_id, types_meta);
let (ty, name, _, t_align) = type_from_id(shader, spirv, element_type, types_meta);
return (
quote! { [#ty] },
Cow::from(format!("[{}]", name)),
@ -618,26 +645,25 @@ pub(super) fn type_from_id(
t_align,
);
}
&Instruction::TypeStruct {
result_id,
ref member_types,
} if result_id == searched => {
Instruction::TypeStruct { member_types, .. } => {
// TODO: take the Offset member decorate into account?
let name_string = spirv_search::name_from_id(doc, result_id);
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, doc, result_id, member_types, types_meta, None);
let (_, size) = write_struct(shader, spirv, searched, member_types, types_meta, None);
let align = member_types
.iter()
.map(|&t| type_from_id(shader, doc, t, types_meta).3)
.map(|&t| type_from_id(shader, spirv, t, types_meta).3)
.max()
.unwrap_or(1);
return (ty, Cow::from(name_string), size, align);
return (ty, Cow::from(name_string.to_owned()), size, align);
}
_ => (),
_ => panic!("Type #{} not found", searched),
}
}
panic!("Type #{} not found", searched)
}

View File

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

View File

@ -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)),
);
}

View File

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

View File

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

View File

@ -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)),
);
}

View File

@ -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(&registry);
let registry = get_vk_registry("vk.xml");
let vk_data = VkRegistryData::new(&registry);
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(&registry);
let extensions = Self::get_extensions(&registry);
@ -85,7 +100,7 @@ impl<'r> RegistryData<'r> {
let types = Self::get_types(&registry, &aliases, &features, &extensions);
let header_version = Self::get_header_version(&registry);
RegistryData {
VkRegistryData {
header_version,
extensions,
features,
@ -106,11 +121,9 @@ impl<'r> RegistryData<'r> {
}
}
}
None
});
}
None
})
.unwrap()
@ -295,3 +308,74 @@ impl<'r> RegistryData<'r> {
.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
}
}

View File

@ -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
View 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(
&param.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(
&param.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()
}

View 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>,
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -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
View 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),
}
}
}

View File

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

View File

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