use crate::parse_declarations::{ParseDeclarationsResult, ParseFieldResult};
use fnv::FnvHashMap;
use std::sync::Arc;
fn next_power_of_2(mut v: usize) -> usize {
v -= 1;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v |= v >> 32;
v += 1;
v
}
#[derive(Debug)]
pub(crate) enum StructOrBinding {
Struct(usize),
Binding(usize),
}
#[derive(Debug)]
pub(crate) struct TypeAlignmentInfo {
pub(crate) rust_type: String,
pub(crate) size: usize,
pub(crate) align: usize,
pub(crate) std140_alignment: usize, pub(crate) std430_alignment: usize,
}
#[derive(Debug)]
pub(crate) struct UserType {
pub(crate) _struct_or_binding: StructOrBinding,
pub(crate) type_name: String,
pub(crate) fields: Arc<Vec<ParseFieldResult>>,
pub(crate) export_uniform_layout: bool,
pub(crate) export_push_constant_layout: bool,
pub(crate) export_buffer_layout: bool,
}
pub(crate) fn recursive_modify_user_type<F: Fn(&mut UserType) -> bool>(
user_types: &mut FnvHashMap<String, UserType>,
type_name: &str,
f: &F,
) {
let user_type = user_types.get_mut(type_name);
let recurse = if let Some(user_type) = user_type {
(f)(user_type)
} else {
return;
};
if recurse {
if let Some(fields) = user_types.get(type_name).map(|x| x.fields.clone()) {
for field in &*fields {
recursive_modify_user_type(user_types, &field.type_name, f);
}
}
}
}
pub(crate) fn create_user_type_lookup(
parsed_declarations: &ParseDeclarationsResult
) -> Result<FnvHashMap<String, UserType>, String> {
let mut user_types = FnvHashMap::<String, UserType>::default();
for (index, s) in parsed_declarations.structs.iter().enumerate() {
let old = user_types.insert(
s.parsed.type_name.clone(),
UserType {
_struct_or_binding: StructOrBinding::Struct(index),
type_name: s.parsed.type_name.clone(),
fields: s.parsed.fields.clone(),
export_uniform_layout: false,
export_push_constant_layout: false,
export_buffer_layout: false,
},
);
if old.is_some() {
return Err(format!(
"Duplicate user-defined struct type {}",
s.parsed.type_name
));
}
}
for (index, b) in parsed_declarations.bindings.iter().enumerate() {
if let Some(fields) = &b.parsed.fields {
let old = user_types.insert(
b.parsed.type_name.clone(),
UserType {
_struct_or_binding: StructOrBinding::Binding(index),
type_name: b.parsed.type_name.clone(),
fields: fields.clone(),
export_uniform_layout: false,
export_push_constant_layout: false,
export_buffer_layout: false,
},
);
if old.is_some() {
return Err(format!(
"Duplicate user-defined binding type {}",
b.parsed.type_name
));
}
}
}
Ok(user_types)
}
fn add_type_alignment_info<T>(
type_alignment_infos: &mut FnvHashMap<String, TypeAlignmentInfo>,
type_name: &str,
rust_type: &str,
) {
let align = std::mem::align_of::<T>();
let size = std::mem::size_of::<T>();
let type_alignment_info = TypeAlignmentInfo {
rust_type: rust_type.to_string(),
size,
align,
std140_alignment: next_power_of_2(size.min(16).max(4)),
std430_alignment: next_power_of_2(size.min(16).max(4)),
};
log::trace!("built in type: {:?}", type_alignment_info);
let old = type_alignment_infos.insert(type_name.to_string(), type_alignment_info);
assert!(old.is_none());
}
#[rustfmt::skip]
pub(crate) fn create_builtin_type_lookup() -> FnvHashMap<String, TypeAlignmentInfo> {
let mut builtin_types = FnvHashMap::<String, TypeAlignmentInfo>::default();
add_type_alignment_info::<u32>(&mut builtin_types, "int", "i32");
add_type_alignment_info::<u32>(&mut builtin_types, "uint", "u32");
add_type_alignment_info::<u32>(&mut builtin_types, "bool", "u32");
add_type_alignment_info::<f32>(&mut builtin_types, "float", "f32");
add_type_alignment_info::<[f32; 2]>(&mut builtin_types, "vec2", "[f32; 2]");
add_type_alignment_info::<[f32; 3]>(&mut builtin_types, "vec3", "[f32; 3]");
add_type_alignment_info::<[f32; 4]>(&mut builtin_types, "vec4", "[f32; 4]");
add_type_alignment_info::<[[f32; 4]; 4]>(&mut builtin_types, "mat4", "[[f32; 4]; 4]");
add_type_alignment_info::<[u32; 4]>(&mut builtin_types, "uvec4", "[u32; 4]");
builtin_types
}
#[derive(Copy, Clone, Debug)]
pub enum MemoryLayout {
Std140,
Std430,
C,
}
#[derive(Debug)]
pub struct StructMember {
pub name: String,
pub ty: String,
pub size: usize,
pub offset: usize,
pub align: usize,
pub default_value: String,
}
#[derive(Debug)]
pub struct GenerateStructResult {
pub name: String,
pub size: usize,
pub align: usize,
pub members: Vec<StructMember>,
}
pub(crate) fn generate_struct(
builtin_types: &FnvHashMap<String, TypeAlignmentInfo>,
user_types: &FnvHashMap<String, UserType>,
type_name: &str,
user_type: &UserType,
layout: MemoryLayout,
) -> Result<GenerateStructResult, String> {
let mut members = Vec::default();
let mut pad_var_count = 0;
let struct_name = get_rust_type_name_non_array(builtin_types, user_types, &type_name, layout)?;
let mut gpu_offset = 0;
let mut rust_offset = 0;
for f in &*user_type.fields {
log::trace!(" get gpu required offset");
let gpu_alignment = determine_alignment(
builtin_types,
user_types,
&f.type_name,
&f.array_sizes,
layout,
)?;
log::trace!(" offset: {} align to {}", gpu_offset, gpu_alignment);
gpu_offset = align_offset(gpu_offset, gpu_alignment);
let maybe_gpu_size = determine_size(
builtin_types,
user_types,
&f.type_name,
&f.array_sizes,
gpu_offset,
gpu_offset,
&f.type_name,
layout,
)?;
if maybe_gpu_size.is_none() {
return Err("Variable sized struct found, export is not supported as these can't be represented in rust")?;
}
let gpu_size = maybe_gpu_size.unwrap() - gpu_offset;
log::trace!(" get rust required offset");
let rust_alignment = determine_alignment(
builtin_types,
user_types,
&f.type_name,
&f.array_sizes,
MemoryLayout::C,
)?;
log::trace!(" offset: {} align to {}", rust_offset, rust_alignment);
let rust_required_offset = align_offset(rust_offset, rust_alignment);
if rust_required_offset > gpu_offset {
let required_padding = rust_required_offset - gpu_offset;
return Err(format!(
"Field {}::{} ({}{}) requires {} bytes of padding in front of it. (The GPU memory layout has less padding that rust). Previous field ended at byte offset: {}",
type_name,
f.field_name,
f.type_name,
format_array_sizes(&f.array_sizes),
required_padding,
gpu_offset
));
}
if rust_required_offset < gpu_offset {
log::trace!(
"Field {}::{} ({}{}) requires {} bytes of padding in front of it. (The GPU memory layout has more padding than rust). Previous field ended at byte offset: {}",
type_name,
f.field_name,
f.type_name,
format_array_sizes(&f.array_sizes),
gpu_offset - rust_required_offset,
rust_offset
);
let required_padding = gpu_offset - rust_required_offset;
let struct_member = StructMember {
name: format!("_padding{}", pad_var_count),
ty: format!("[u8; {}]", required_padding),
size: required_padding,
align: 1,
offset: rust_offset,
default_value: format!("[u8::default(); {}]", required_padding),
};
log::trace!("member: {:?}", struct_member);
members.push(struct_member);
pad_var_count += 1;
rust_offset += required_padding;
log::trace!(
"RUST: advance by {} bytes to offset {} (due to padding)",
required_padding,
rust_offset
);
}
assert_eq!(rust_offset, gpu_offset);
let maybe_rust_size = determine_size_of_member_in_rust(
builtin_types,
user_types,
&f.type_name,
&f.array_sizes,
rust_offset,
rust_offset,
&f.type_name,
layout,
)?;
if maybe_rust_size.is_none() {
return Err("Variable sized struct found, export is not supported as these can't be represented in rust")?;
}
let rust_size = maybe_rust_size.unwrap() - rust_offset;
assert!(rust_size <= gpu_size);
if !f.array_sizes.is_empty() && rust_size != gpu_size {
return Err(format!(
"Field {}::{} ({}{}) is an array of elements where the element type's GPU memory layout ({} size in bytes) does not match rust's memory layout ({} size in bytes). This is not currently supported. Try wrapping in a struct.",
type_name,
f.field_name,
f.type_name,
format_array_sizes(&f.array_sizes),
gpu_size,
rust_size
));
}
let rust_type_name =
get_rust_type_name_non_array(builtin_types, user_types, &f.type_name, layout)?;
let struct_member = StructMember {
name: f.field_name.clone(),
ty: wrap_in_array(&rust_type_name, &f.array_sizes),
size: rust_size,
align: rust_alignment,
offset: rust_offset,
default_value: wrap_in_array(
&format!("<{}>::default()", &rust_type_name),
&f.array_sizes,
),
};
log::trace!("member: {:?}", struct_member);
members.push(struct_member);
rust_offset += rust_size;
gpu_offset += gpu_size;
log::trace!(
"RUST: advance by {} bytes to offset {}",
rust_size,
rust_offset
);
log::trace!(
"GPU: advance by {} bytes to offset {}",
gpu_size,
gpu_offset
);
}
let maybe_full_gpu_size = determine_size(
builtin_types,
user_types,
&type_name,
&[],
0,
0,
&type_name,
layout,
)?;
if maybe_full_gpu_size.is_none() {
return Err("Variable sized struct found, export is not supported as these can't be represented in rust")?;
}
let full_gpu_size = maybe_full_gpu_size.unwrap();
assert!(rust_offset <= full_gpu_size);
if rust_offset < full_gpu_size {
let required_padding = full_gpu_size - rust_offset;
let struct_member = StructMember {
name: format!("_padding{}", pad_var_count),
ty: format!("[u8; {}]", required_padding),
size: required_padding,
align: 1,
offset: rust_offset,
default_value: format!("[u8::default(); {}]", required_padding),
};
log::trace!("member: {:?}", struct_member);
members.push(struct_member);
}
let struct_align = determine_alignment_c(builtin_types, user_types, &type_name, &[])?;
Ok(GenerateStructResult {
name: struct_name,
size: full_gpu_size,
align: struct_align,
members,
})
}
pub(crate) fn wrap_in_array(
inner: &str,
array_sizes: &[usize],
) -> String {
let mut wrapped = inner.to_string();
for array_size in array_sizes.iter().rev() {
wrapped = format!("[{}; {}]", wrapped, array_size);
}
wrapped
}
pub(crate) fn get_rust_type_name_non_array(
builtin_types: &FnvHashMap<String, TypeAlignmentInfo>,
user_types: &FnvHashMap<String, UserType>,
name: &str,
layout: MemoryLayout,
) -> Result<String, String> {
let type_name = if let Some(builtin_type) = builtin_types.get(name) {
builtin_type.rust_type.clone()
} else if let Some(user_type) = user_types.get(name) {
format!("{}{:?}", user_type.type_name.clone(), layout)
} else {
return Err(format!("Could not find type {}. Is this a built in type that needs to be added to create_builtin_type_lookup()?", name));
};
Ok(type_name)
}
pub(crate) fn format_array_sizes(sizes: &[usize]) -> String {
let mut s = String::default();
for size in sizes {
s += &format!("[{}]", size);
}
s
}
fn align_offset(
offset: usize,
alignment: usize,
) -> usize {
(offset + alignment - 1) / alignment * alignment
}
pub(crate) fn element_count(array_sizes: &[usize]) -> usize {
if let Some(&last) = array_sizes.last() {
if last == 0 {
return 0;
}
}
let mut element_count = 1;
for x in array_sizes {
assert!(*x != 0);
element_count *= x;
}
element_count
}
fn determine_size_of_member_in_rust(
builtin_types: &FnvHashMap<String, TypeAlignmentInfo>,
user_types: &FnvHashMap<String, UserType>,
query_type: &str,
array_sizes: &[usize],
offset: usize,
logging_offset: usize,
logging_name: &str,
gpu_layout: MemoryLayout,
) -> Result<Option<usize>, String> {
let memory_layout = if builtin_types.contains_key(query_type) {
MemoryLayout::C
} else if user_types.contains_key(query_type) {
gpu_layout
} else {
return Err(format!("Could not find type {}. Is this a built in type that needs to be added to create_builtin_type_lookup()?", query_type));
};
determine_size(
builtin_types,
user_types,
query_type,
array_sizes,
offset,
logging_offset,
logging_name,
memory_layout,
)
}
fn determine_size(
builtin_types: &FnvHashMap<String, TypeAlignmentInfo>,
user_types: &FnvHashMap<String, UserType>,
query_type: &str,
array_sizes: &[usize],
mut offset: usize,
logging_offset: usize,
logging_name: &str,
layout: MemoryLayout,
) -> Result<Option<usize>, String> {
let element_count = element_count(array_sizes);
if element_count == 0 {
return Ok(None);
}
let alignment =
determine_alignment(builtin_types, user_types, query_type, array_sizes, layout)?;
assert_eq!(offset % alignment, 0);
if let Some(builtin_type) = builtin_types.get(query_type) {
log::trace!(
" {} +{} (size: {}) [{} elements of size {}, alignment: {}, name: {}]",
query_type,
logging_offset,
element_count * builtin_type.size,
element_count,
builtin_type.size,
alignment,
logging_name
);
if array_sizes.is_empty() {
offset += builtin_type.size;
} else {
let padded_size = align_offset(builtin_type.size, alignment);
offset += padded_size * element_count;
}
Ok(Some(offset))
} else if let Some(user_type) = user_types.get(query_type) {
let mut offset_within_struct = 0;
for f in &*user_type.fields {
let field_alignment = determine_alignment(
builtin_types,
user_types,
&f.type_name,
&f.array_sizes,
layout,
)?;
offset_within_struct = align_offset(offset_within_struct, field_alignment);
let maybe_offset_within_struct = determine_size(
builtin_types,
user_types,
&f.type_name,
&f.array_sizes,
offset_within_struct,
offset + offset_within_struct,
&f.field_name,
layout,
)?;
if maybe_offset_within_struct.is_none() {
return Ok(None);
}
offset_within_struct = maybe_offset_within_struct.unwrap();
}
let padded_size = align_offset(offset_within_struct, alignment);
log::trace!(
" struct {} total size: {} [{} elements of size {} padded to {}]",
logging_name,
padded_size * element_count,
element_count,
offset_within_struct,
padded_size
);
offset += padded_size * element_count;
Ok(Some(offset))
} else {
return Err(format!("Could not find type {}. Is this a built in type that needs to be added to create_builtin_type_lookup()?", query_type));
}
}
fn determine_alignment(
builtin_types: &FnvHashMap<String, TypeAlignmentInfo>,
user_types: &FnvHashMap<String, UserType>,
query_type: &str,
array_sizes: &[usize],
layout: MemoryLayout,
) -> Result<usize, String> {
match layout {
MemoryLayout::Std140 => {
determine_alignment_std140(builtin_types, user_types, query_type, array_sizes)
}
MemoryLayout::Std430 => {
determine_alignment_std430(builtin_types, user_types, query_type, array_sizes)
}
MemoryLayout::C => {
determine_alignment_c(builtin_types, user_types, query_type, array_sizes)
}
}
}
fn determine_alignment_std140(
builtin_types: &FnvHashMap<String, TypeAlignmentInfo>,
user_types: &FnvHashMap<String, UserType>,
query_type: &str,
array_sizes: &[usize],
) -> Result<usize, String> {
if let Some(builtin_type) = builtin_types.get(query_type) {
if !array_sizes.is_empty() {
Ok(align_offset(builtin_type.std140_alignment, 16))
} else {
Ok(builtin_type.std140_alignment)
}
} else if let Some(user_type) = user_types.get(query_type) {
let mut alignment = 16;
for f in &*user_type.fields {
let field_alignment = determine_alignment_std140(
builtin_types,
user_types,
&f.type_name,
&f.array_sizes,
)?;
alignment = alignment.max(field_alignment);
}
Ok(align_offset(alignment, 16))
} else {
return Err(format!("Could not find type {}. Is this a built in type that needs to be added to create_builtin_type_lookup()?", query_type));
}
}
fn determine_alignment_std430(
builtin_types: &FnvHashMap<String, TypeAlignmentInfo>,
user_types: &FnvHashMap<String, UserType>,
query_type: &str,
_array_sizes: &[usize],
) -> Result<usize, String> {
if let Some(builtin_type) = builtin_types.get(query_type) {
Ok(builtin_type.std430_alignment)
} else if let Some(user_type) = user_types.get(query_type) {
let mut alignment = 4;
for f in &*user_type.fields {
let field_alignment = determine_alignment_std430(
builtin_types,
user_types,
&f.type_name,
&f.array_sizes,
)?;
alignment = alignment.max(field_alignment);
}
Ok(alignment)
} else {
return Err(format!("Could not find type {}. Is this a built in type that needs to be added to create_builtin_type_lookup()?", query_type));
}
}
fn determine_alignment_c(
builtin_types: &FnvHashMap<String, TypeAlignmentInfo>,
user_types: &FnvHashMap<String, UserType>,
query_type: &str,
_array_sizes: &[usize],
) -> Result<usize, String> {
if let Some(builtin_type) = builtin_types.get(query_type) {
Ok(builtin_type.align)
} else if let Some(user_type) = user_types.get(query_type) {
let mut alignment = 1;
for f in &*user_type.fields {
let field_alignment =
determine_alignment_c(builtin_types, user_types, &f.type_name, &f.array_sizes)?;
alignment = alignment.max(field_alignment);
}
Ok(alignment)
} else {
return Err(format!("Could not find type {}. Is this a built in type that needs to be added to create_builtin_type_lookup()?", query_type));
}
}
#[cfg(test)]
pub(crate) fn verify_all_binding_layouts(
builtin_types: &FnvHashMap<String, TypeAlignmentInfo>,
user_types: &FnvHashMap<String, UserType>,
reflect_data: &spirv_reflect::ShaderModule,
) -> Result<(), String> {
for binding in reflect_data.enumerate_descriptor_bindings(None).unwrap() {
use spirv_reflect::types::ReflectDescriptorType;
let type_description = binding.type_description.as_ref().unwrap();
match binding.descriptor_type {
ReflectDescriptorType::UniformBuffer => {
verify_layout(
builtin_types,
user_types,
&type_description.type_name,
&binding.block,
MemoryLayout::Std140,
)?;
}
ReflectDescriptorType::StorageBuffer => {
verify_layout(
builtin_types,
user_types,
&type_description.type_name,
&binding.block,
MemoryLayout::Std430,
)?;
}
_ => {
}
}
}
Ok(())
}
#[cfg(test)]
fn verify_layout(
builtin_types: &FnvHashMap<String, TypeAlignmentInfo>,
user_types: &FnvHashMap<String, UserType>,
type_name: &str,
block: &spirv_reflect::types::ReflectBlockVariable,
layout: MemoryLayout,
) -> Result<(), String> {
if !type_name.is_empty() {
let array_sizes: Vec<usize> = block.array.dims.iter().map(|x| *x as usize).collect();
let size = determine_size(
builtin_types,
user_types,
type_name,
&array_sizes,
0,
0,
type_name,
layout,
)?;
if !size.is_none() {
let size = size.unwrap();
let alignment =
determine_alignment(builtin_types, user_types, type_name, &array_sizes, layout)?;
if block.padded_size != 0 {
let padded_size = align_offset(block.size as usize, alignment);
if size != padded_size {
fn print_block_members(
reflect_block_variable: &spirv_reflect::types::ReflectBlockVariable
) {
for member in &reflect_block_variable.members {
log::info!("+{} (size {}) {}", member.offset, member.size, member.name);
print_block_members(&member);
}
}
print_block_members(block);
return Err(format!(
"Found a mismatch between logic and compiled spv alignments in type {} for layout {:?}. Logic size: {} SPV size is: {}",
type_name,
layout,
size,
block.size
));
}
} else {
let mut size_from_reflection = block
.members
.last()
.map(|x| x.offset + x.padded_size)
.unwrap_or(0) as usize;
size_from_reflection = align_offset(size_from_reflection, alignment);
let element_count = element_count(&array_sizes);
if size != size_from_reflection * element_count {
println!("alignment {}", alignment);
return Err(format!(
"Found a mismatch between logic and compiled spv alignments in type {} for layout {:?}. Logic size: {} SPV size is: {} ({} * {} elements, array sizes {:?})",
type_name,
layout,
size,
size_from_reflection * element_count,
size_from_reflection,
element_count,
array_sizes
));
}
}
}
}
for block in &block.members {
verify_layout(
builtin_types,
user_types,
&block.type_description.as_ref().unwrap().type_name,
&block,
layout,
)?;
}
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
use crate::parse_source::{FileToProcess, PreprocessorState};
use crate::shader_types::{create_builtin_type_lookup, create_user_type_lookup};
fn verify_all_binding_layouts_in_test(
reflect_data: spirv_reflect::ShaderModule,
parsed_declarations: &ParseDeclarationsResult,
) {
let user_types = create_user_type_lookup(parsed_declarations).unwrap();
let builtin_types = create_builtin_type_lookup();
verify_all_binding_layouts(&builtin_types, &user_types, &reflect_data).unwrap();
}
#[test]
fn test_uniform_layout() {
let shader_code = r#"
#version 450
struct PointLight {
vec3 position_ws;
vec3 position_vs;
vec4 color;
float range;
float intensity;
};
struct DirectionalLight {
vec3 direction_ws;
vec3 direction_vs;
vec4 color;
float intensity;
};
struct SpotLight {
vec3 position_ws;
vec3 direction_ws;
vec3 position_vs;
vec3 direction_vs[2];
vec4 color;
float spotlight_half_angle;
float range[5];
float intensity[5][6];
};
// @[export]
layout (set = 0, binding = 0) uniform PerViewData {
vec4 ambient_light;
uint point_light_count;
uint directional_light_count;
uint spot_light_count;
PointLight point_lights[16];
DirectionalLight directional_lights[16];
SpotLight spot_lights[16];
} per_frame_data;
layout (location = 0) out vec4 out_color;
void main() {
out_color = vec4(per_frame_data.ambient_light);
}
"#;
let (reflect_data, parsed_declarations) = compile_code_for_test(&shader_code);
verify_all_binding_layouts_in_test(reflect_data, &parsed_declarations)
}
#[test]
fn test_buffer_layout() {
let shader_code = r#"
#version 450
struct PointLight {
vec3 position_ws;
vec3 position_vs;
vec4 color;
float range;
float intensity;
};
struct DirectionalLight {
vec3 direction_ws;
vec3 direction_vs;
vec4 color;
float intensity;
};
struct SpotLight {
vec3 position_ws;
vec3 direction_ws;
vec3 position_vs;
vec3 direction_vs[2];
vec4 color;
float spotlight_half_angle;
float range[5];
float intensity[5][6];
};
// @[export]
layout (set = 0, binding = 0) buffer PerViewData {
vec4 ambient_light;
uint point_light_count;
uint directional_light_count;
uint spot_light_count;
PointLight point_lights[16];
DirectionalLight directional_lights[16];
SpotLight spot_lights[16];
} per_frame_data;
layout (location = 0) out vec4 out_color;
void main() {
out_color = vec4(per_frame_data.ambient_light);
}
"#;
let (reflect_data, parsed_declarations) = compile_code_for_test(&shader_code);
verify_all_binding_layouts_in_test(reflect_data, &parsed_declarations)
}
#[test]
fn test_push_constant_layout() {
let shader_code = r#"
#version 450
struct PointLight {
vec3 position_ws;
vec3 position_vs;
vec4 color;
float range;
float intensity;
};
struct DirectionalLight {
vec3 direction_ws;
vec3 direction_vs;
vec4 color;
float intensity;
};
struct SpotLight {
vec3 position_ws;
vec3 direction_ws;
vec3 position_vs;
vec3 direction_vs[2];
vec4 color;
float spotlight_half_angle;
float range[5];
float intensity[5][6];
};
// @[export]
layout (push_constant) uniform PerViewData {
vec4 ambient_light;
uint point_light_count;
uint directional_light_count;
uint spot_light_count;
PointLight point_lights[16];
DirectionalLight directional_lights[16];
SpotLight spot_lights[16];
} per_frame_data;
layout (location = 0) out vec4 out_color;
void main() {
out_color = vec4(per_frame_data.ambient_light);
}
"#;
let (reflect_data, parsed_declarations) = compile_code_for_test(&shader_code);
verify_all_binding_layouts_in_test(reflect_data, &parsed_declarations)
}
#[test]
fn test_all_layout() {
let shader_code = r#"
#version 450
struct PointLight {
vec3 position_ws;
vec3 position_vs;
vec4 color;
float range;
float intensity;
};
struct DirectionalLight {
vec3 direction_ws;
vec3 direction_vs;
vec4 color;
float intensity;
};
struct SpotLight {
vec3 position_ws;
vec3 direction_ws;
vec3 position_vs;
vec3 direction_vs[2];
vec4 color;
float spotlight_half_angle;
float range[5];
float intensity[5][6];
};
// @[export]
layout (set = 0, binding = 0) uniform PerViewDataUbo {
vec4 ambient_light;
uint point_light_count;
uint directional_light_count;
uint spot_light_count;
PointLight point_lights[16];
DirectionalLight directional_lights[16];
SpotLight spot_lights[16];
} per_frame_data_uniform;
// @[export]
layout (set = 0, binding = 1) buffer PerViewDataSbo {
vec4 ambient_light;
uint point_light_count;
uint directional_light_count;
uint spot_light_count;
PointLight point_lights[16];
DirectionalLight directional_lights[16];
SpotLight spot_lights[16];
} per_frame_data_buffer;
// glsl required unique name for uniform blocks but reusing in uniform/buffers is allowed
// we kind of have to support this anyways because the same struct can be used in all
// 3 cases
// @[export]
layout (push_constant) uniform PerViewDataPC {
vec4 ambient_light;
uint point_light_count;
uint directional_light_count;
uint spot_light_count;
PointLight point_lights[16];
DirectionalLight directional_lights[16];
SpotLight spot_lights[16];
} per_frame_data_push_constant;
layout (location = 0) out vec4 out_color;
void main() {
out_color = vec4(per_frame_data_uniform.ambient_light + per_frame_data_buffer.ambient_light + per_frame_data_push_constant.ambient_light);
}
"#;
let (reflect_data, parsed_declarations) = compile_code_for_test(&shader_code);
verify_all_binding_layouts_in_test(reflect_data, &parsed_declarations)
}
fn compile_code_for_test(
shader_code: &str
) -> (
spirv_reflect::ShaderModule,
crate::parse_declarations::ParseDeclarationsResult,
) {
let compiler = shaderc::Compiler::new().unwrap();
let result = compiler
.compile_into_spirv(
&shader_code,
shaderc::ShaderKind::Fragment,
"",
"main",
None,
)
.unwrap();
let reflect_data = spirv_reflect::create_shader_module(result.as_binary_u8()).unwrap();
let file_to_process = FileToProcess {
path: "".into(),
include_type: crate::IncludeType::Relative,
requested_from: "".into(),
include_depth: 0,
};
let mut declarations = Vec::default();
let mut included_files = Default::default();
let code: Vec<char> = shader_code.chars().collect();
let mut preprocessor_state = PreprocessorState::default();
crate::parse_source::parse_shader_source_text(
&file_to_process,
&mut declarations,
&mut included_files,
&mut preprocessor_state,
&code,
)
.unwrap();
let parsed_declarations =
crate::parse_declarations::parse_declarations(&declarations).unwrap();
(reflect_data, parsed_declarations)
}
}