Add unit tests for descriptor set calculations

Signed-off-by: Arc-blroth <45273859+Arc-blroth@users.noreply.github.com>
This commit is contained in:
Arc-blroth 2021-02-28 23:10:40 -08:00
parent 963061c3c4
commit 4875bcc204
2 changed files with 248 additions and 55 deletions

View File

@ -16,6 +16,14 @@ use crate::parse::{Instruction, Spirv};
use crate::{spirv_search, TypesMeta};
use std::collections::HashSet;
struct Descriptor {
set: u32,
binding: u32,
desc_ty: TokenStream,
array_count: u64,
readonly: bool,
}
pub(super) fn write_descriptor_sets(
doc: &Spirv,
entry_point_layout_name: &Ident,
@ -26,61 +34,7 @@ pub(super) fn write_descriptor_sets(
// TODO: somewhat implemented correctly
// Finding all the descriptors.
let mut descriptors = Vec::new();
struct Descriptor {
set: u32,
binding: u32,
desc_ty: TokenStream,
array_count: u64,
readonly: bool,
}
// 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 = {
let mut found_variables: HashSet<u32> = interface.iter().cloned().collect();
let mut inspected_functions: HashSet<u32> = HashSet::new();
find_variables_in_function(&doc, entrypoint_id, &mut inspected_functions, &mut found_variables);
found_variables
};
// Looping to find all the interface elements that have the `DescriptorSet` decoration.
for set_decoration in doc.get_decorations(Decoration::DecorationDescriptorSet) {
let variable_id = set_decoration.target_id;
if !variables.contains(&variable_id) {
continue;
}
let set = set_decoration.params[0];
// 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);
// 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::DecorationBinding)
.unwrap()[0];
// Find information about the kind of binding for this descriptor.
let (desc_ty, readonly, array_count) =
descriptor_infos(doc, pointed_ty, storage_class, false).expect(&format!(
"Couldn't find relevant type for uniform `{}` (type {}, maybe unimplemented)",
name, pointed_ty
));
descriptors.push(Descriptor {
desc_ty,
set,
binding,
array_count,
readonly,
});
}
let descriptors = find_descriptors(doc, entrypoint_id, interface);
// Looping to find all the push constant structs.
let mut push_constants_size = 0;
@ -183,6 +137,63 @@ pub(super) fn write_descriptor_sets(
}
}
fn find_descriptors(
doc: &Spirv,
entrypoint_id: u32,
interface: &[u32],
) -> 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 = {
let mut found_variables: HashSet<u32> = interface.iter().cloned().collect();
let mut inspected_functions: HashSet<u32> = HashSet::new();
find_variables_in_function(&doc, entrypoint_id, &mut inspected_functions, &mut found_variables);
found_variables
};
// Looping to find all the interface elements that have the `DescriptorSet` decoration.
for set_decoration in doc.get_decorations(Decoration::DecorationDescriptorSet) {
let variable_id = set_decoration.target_id;
if !variables.contains(&variable_id) {
continue;
}
let set = set_decoration.params[0];
// 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);
// 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::DecorationBinding)
.unwrap()[0];
// Find information about the kind of binding for this descriptor.
let (desc_ty, readonly, array_count) =
descriptor_infos(doc, pointed_ty, storage_class, false).expect(&format!(
"Couldn't find relevant type for uniform `{}` (type {}, maybe unimplemented)",
name, pointed_ty
));
descriptors.push(Descriptor {
desc_ty,
set,
binding,
array_count,
readonly,
});
}
descriptors
}
// Recursively finds every pointer variable used in the execution of a function.
fn find_variables_in_function(
doc: &Spirv,
@ -482,3 +493,185 @@ fn descriptor_infos(
})
.next()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::codegen::compile;
use std::path::{PathBuf, Path};
use shaderc::ShaderKind;
use crate::parse;
/// `entrypoint1.frag.glsl`:
/// ```glsl
/// #version 450
///
/// layout(set = 0, binding = 0) uniform Uniform {
/// uint data;
/// } ubo;
///
/// layout(set = 0, binding = 1) buffer Buffer {
/// uint data;
/// } bo;
///
/// layout(set = 0, binding = 2) uniform sampler textureSampler;
/// layout(set = 0, binding = 3) uniform texture2D imageTexture;
///
/// layout(push_constant) uniform PushConstant {
/// uint data;
/// } push;
///
/// layout(input_attachment_index = 0, set = 0, binding = 4) uniform subpassInput inputAttachment;
///
/// layout(location = 0) out vec4 outColor;
///
/// void entrypoint1() {
/// bo.data = 12;
/// outColor = vec4(
/// float(ubo.data),
/// float(push.data),
/// texture(sampler2D(imageTexture, textureSampler), vec2(0.0, 0.0)).x,
/// subpassLoad(inputAttachment).x
/// );
/// }
/// ```
///
/// `entrypoint2.frag.glsl`:
/// ```glsl
/// #version 450
///
/// layout(input_attachment_index = 0, set = 0, binding = 0) uniform subpassInput inputAttachment2;
///
/// layout(set = 0, binding = 1) buffer Buffer {
/// uint data;
/// } bo2;
///
/// layout(set = 0, binding = 2) uniform Uniform {
/// uint data;
/// } ubo2;
///
/// layout(push_constant) uniform PushConstant {
/// uint data;
/// } push2;
///
/// void entrypoint2() {
/// bo2.data = ubo2.data + push2.data + int(subpassLoad(inputAttachment2).y);
/// }
/// ```
///
/// Compiled and linked with:
/// ```sh
/// glslangvalidator -e entrypoint1 --source-entrypoint entrypoint1 -V100 entrypoint1.frag.glsl -o entrypoint1.spv
/// glslangvalidator -e entrypoint2 --source-entrypoint entrypoint2 -V100 entrypoint2.frag.glsl -o entrypoint2.spv
/// spirv-link entrypoint1.spv entrypoint2.spv -o multiple_entrypoints.spv
/// ```
#[test]
fn test_descriptor_calculation_with_multiple_entrypoints() {
let data = include_bytes!("../tests/multiple_entrypoints.spv");
let instructions: Vec<u32> = data
.chunks(4)
.map(|c| {
((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 mut descriptors = Vec::new();
for instruction in doc.instructions.iter() {
if let &Instruction::EntryPoint {
id,
ref interface,
..
} = instruction {
descriptors.push(find_descriptors(&doc, id, interface));
}
}
// Check first entrypoint
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));
}
assert_eq!(e1_bindings.len(), 5);
assert!(e1_bindings.contains(&(0, 0)));
assert!(e1_bindings.contains(&(0, 1)));
assert!(e1_bindings.contains(&(0, 2)));
assert!(e1_bindings.contains(&(0, 3)));
assert!(e1_bindings.contains(&(0, 4)));
// Check second entrypoint
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));
}
assert_eq!(e2_bindings.len(), 3);
assert!(e2_bindings.contains(&(0, 0)));
assert!(e2_bindings.contains(&(0, 1)));
assert!(e2_bindings.contains(&(0, 2)));
}
#[test]
fn test_descriptor_calculation_with_multiple_functions() {
let includes: [PathBuf; 0] = [];
let defines: [(String, String); 0] = [];
let comp = compile(
None,
&Path::new(""),
"
#version 450
layout(set = 1, binding = 0) buffer Buffer {
vec3 data;
} bo;
layout(set = 2, binding = 0) uniform Uniform {
float data;
} ubo;
layout(set = 3, binding = 1) uniform sampler textureSampler;
layout(set = 3, binding = 2) uniform texture2D imageTexture;
float withMagicSparkles(float data) {
return texture(sampler2D(imageTexture, textureSampler), vec2(data, data)).x;
}
vec3 makeSecretSauce() {
return vec3(withMagicSparkles(ubo.data));
}
void main() {
bo.data = makeSecretSauce();
}
",
ShaderKind::Vertex,
&includes,
&defines,
)
.unwrap();
let doc = parse::parse_spirv(comp.as_binary()).unwrap();
for instruction in doc.instructions.iter() {
if let &Instruction::EntryPoint {
id,
ref interface,
..
} = instruction {
let descriptors = find_descriptors(&doc, id, interface);
let mut bindings = Vec::new();
for d in descriptors {
bindings.push((d.set, d.binding));
}
assert_eq!(bindings.len(), 4);
assert!(bindings.contains(&(1, 0)));
assert!(bindings.contains(&(2, 0)));
assert!(bindings.contains(&(3, 1)));
assert!(bindings.contains(&(3, 2)));
return;
}
}
panic!("Could not find entrypoint");
}
}

Binary file not shown.