From 5d9489871d95610029243ef1ef18fe0764877540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Capucho?= Date: Sat, 22 Aug 2020 15:50:54 +0100 Subject: [PATCH] Add support for glsl es in the glsl backend (#128) * Initial glsl es 300 support * Set float default precision * Generate interface blocks and check storage class in es * Added more checks on es and fixed uniform blocks with same name type * Added image checks * Added the test to the makefile * applied comments --- Makefile | 14 +- examples/convert.rs | 22 +- src/back/glsl.rs | 559 ++++++++++++++++++++++++++++++++++---------- 3 files changed, 470 insertions(+), 125 deletions(-) diff --git a/Makefile b/Makefile index 89a899515e..f4ecbca408 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ all: clean: - rm *.metal *.air *.metallib + rm *.metal *.air *.metallib *.vert *.frag *.comp %.metal: test-data/%.wgsl $(wildcard src/*.rs src/**/*.rs examples/*.rs) cargo run --example convert -- $< $@ @@ -14,3 +14,15 @@ clean: %.metallib: %.air xcrun -sdk macosx metallib $< -o $@ + +%.vert: test-data/%.wgsl $(wildcard src/*.rs src/**/*.rs examples/*.rs) + cargo run --example convert --features glsl-out -- $< $@ + +%.frag: test-data/%.wgsl $(wildcard src/*.rs src/**/*.rs examples/*.rs) + cargo run --example convert --features glsl-out -- $< $@ + +%.comp: test-data/%.wgsl $(wildcard src/*.rs src/**/*.rs examples/*.rs) + cargo run --example convert --features glsl-out -- $< $@ + +%.glsl_validate: %.vert %.frag %.comp + glslangValidator $< diff --git a/examples/convert.rs b/examples/convert.rs index ab47a0375f..1fb5f428d6 100644 --- a/examples/convert.rs +++ b/examples/convert.rs @@ -189,8 +189,11 @@ fn main() { fs::write(&args[2], bytes.as_slice()).unwrap(); } #[cfg(feature = "glsl-out")] - "vert" | "frag" => { - use naga::back::glsl; + stage @ "vert" | stage @ "frag" | stage @ "comp" => { + use naga::{ + back::glsl::{self, Options, Version}, + ShaderStage, + }; let mut file = fs::OpenOptions::new() .write(true) @@ -199,7 +202,20 @@ fn main() { .open(&args[2]) .unwrap(); - glsl::write(&module, &mut file).unwrap(); + let options = Options { + version: Version::Embedded(310), + entry_point: ( + String::from("main"), + match stage { + "vert" => ShaderStage::Vertex, + "frag" => ShaderStage::Fragment, + "comp" => ShaderStage::Compute, + _ => unreachable!(), + }, + ), + }; + + glsl::write(&module, &mut file, options).unwrap(); } #[cfg(feature = "serialize")] "ron" => { diff --git a/src/back/glsl.rs b/src/back/glsl.rs index fdd0a32bb7..d41c0952a4 100644 --- a/src/back/glsl.rs +++ b/src/back/glsl.rs @@ -1,8 +1,8 @@ use crate::{ Arena, ArraySize, BinaryOperator, BuiltIn, Constant, ConstantInner, DerivativeAxis, Expression, FastHashMap, Function, FunctionOrigin, GlobalVariable, Handle, ImageFlags, Interpolation, - IntrinsicFunction, LocalVariable, Module, ScalarKind, Statement, StorageClass, Type, TypeInner, - UnaryOperator, + IntrinsicFunction, LocalVariable, Module, ScalarKind, ShaderStage, Statement, StorageClass, + StructMember, Type, TypeInner, UnaryOperator, }; use std::{ borrow::Cow, @@ -39,26 +39,126 @@ impl fmt::Display for Error { } } -pub fn write(module: &Module, out: &mut impl Write) -> Result<(), Error> { - writeln!(out, "#version 450 core")?; +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum Version { + Desktop(u16), + Embedded(u16), +} + +impl fmt::Display for Version { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Version::Desktop(v) => write!(f, "{} core", v), + Version::Embedded(v) => write!(f, "{} es", v), + } + } +} + +#[derive(Debug, Clone)] +pub struct Options { + pub version: Version, + pub entry_point: (String, ShaderStage), +} + +const SUPPORTED_CORE_VERSIONS: &[u16] = &[450, 460]; +const SUPPORTED_ES_VERSIONS: &[u16] = &[300, 310]; + +bitflags::bitflags! { + struct SupportedFeatures: u32 { + const BUFFER_STORAGE = 1; + const SHARED_STORAGE = 1 << 1; + const SEPARATE_IMAGE_SAMPLER = 1 << 2; + const DOUBLE_TYPE = 1 << 3; + const NON_FLOAT_MATRICES = 1 << 4; + const MULTISAMPLED_TEXTURES = 1 << 5; + const MULTISAMPLED_TEXTURE_ARRAYS = 1 << 6; + const NON_2D_TEXTURE_ARRAYS = 1 << 7; + } +} + +pub fn write<'a>(module: &'a Module, out: &mut impl Write, options: Options) -> Result<(), Error> { + let (version, es) = match options.version { + Version::Desktop(v) => (v, false), + Version::Embedded(v) => (v, true), + }; + + if (!es && !SUPPORTED_CORE_VERSIONS.contains(&version)) + || (es && !SUPPORTED_ES_VERSIONS.contains(&version)) + { + return Err(Error::Custom(format!( + "Version not supported {}", + options.version + ))); + } + + writeln!(out, "#version {}", options.version)?; + + if es { + writeln!(out, "precision highp float;")?; + } let mut counter = 0; let mut names = FastHashMap::default(); - let mut namer = |name: Option<&String>| { + let mut namer = |name: Option<&'a String>| { if let Some(name) = name { - names.insert(name.clone(), ()); - name.clone() + if name.starts_with("gl_") || name == "main" || names.get(name.as_str()).is_some() { + counter += 1; + while names.get(format!("_{}", counter).as_str()).is_some() { + counter += 1; + } + format!("_{}", counter) + } else { + names.insert(name.as_str(), ()); + name.clone() + } } else { counter += 1; - while names.get(&format!("_{}", counter)).is_some() { + while names.get(format!("_{}", counter).as_str()).is_some() { counter += 1; } format!("_{}", counter) } }; + let entry_point = module + .entry_points + .iter() + .find(|entry| entry.name == options.entry_point.0 && entry.stage == options.entry_point.1) + .ok_or_else(|| Error::Custom(String::from("Entry point not found")))?; + let func = &module.functions[entry_point.function]; + + if entry_point.stage == ShaderStage::Compute { + if (es && version < 310) || (!es && version < 430) { + return Err(Error::Custom(format!( + "Version {} doesn't support compute shaders", + options.version + ))); + } + + if !es && version < 460 { + writeln!(out, "#extension ARB_compute_shader : require")?; + } + } + + let mut features = SupportedFeatures::empty(); + + if !es && version > 440 { + features |= SupportedFeatures::SEPARATE_IMAGE_SAMPLER; + features |= SupportedFeatures::DOUBLE_TYPE; + features |= SupportedFeatures::NON_FLOAT_MATRICES; + features |= SupportedFeatures::MULTISAMPLED_TEXTURE_ARRAYS; + features |= SupportedFeatures::NON_2D_TEXTURE_ARRAYS; + } + + if !es || version > 300 { + features |= SupportedFeatures::BUFFER_STORAGE; + features |= SupportedFeatures::SHARED_STORAGE; + features |= SupportedFeatures::MULTISAMPLED_TEXTURES; + } + let mut structs = FastHashMap::default(); + let mut built_structs = FastHashMap::default(); // Do a first pass to collect names for (handle, ty) in module.types.iter() { @@ -72,23 +172,40 @@ pub fn write(module: &Module, out: &mut impl Write) -> Result<(), Error> { } } + for ((_, global), usage) in module.global_variables.iter().zip(func.global_usage.iter()) { + if usage.is_empty() { + continue; + } + + let block = matches!( + global.class, + StorageClass::Input + | StorageClass::Output + | StorageClass::StorageBuffer + | StorageClass::Uniform + ); + + match module.types[global.ty].inner { + TypeInner::Struct { .. } if block => { + built_structs.insert(global.ty, ()); + } + _ => {} + } + } + // Do a second pass to build the structs - // TODO: glsl is order dependent so we need to build structs in order for (handle, ty) in module.types.iter() { match ty.inner { TypeInner::Struct { ref members } => { - let name = structs.get(&handle).unwrap(); - - writeln!(out, "struct {} {{", name)?; - for (idx, member) in members.iter().enumerate() { - writeln!( - out, - " {} {};", - write_type(member.ty, &module.types, &structs)?, - member.name.clone().unwrap_or_else(|| idx.to_string()) - )?; - } - writeln!(out, "}};")?; + write_struct( + handle, + members, + module, + &structs, + out, + &mut built_structs, + features, + )?; } _ => continue, } @@ -96,10 +213,14 @@ pub fn write(module: &Module, out: &mut impl Write) -> Result<(), Error> { let mut globals_lookup = FastHashMap::default(); - for (handle, global) in module.global_variables.iter() { + for ((handle, global), usage) in module.global_variables.iter().zip(func.global_usage.iter()) { + if usage.is_empty() { + continue; + } + if let Some(crate::Binding::BuiltIn(built_in)) = global.binding { let semantic = match built_in { - BuiltIn::Position => "gl_position", + BuiltIn::Position => "gl_Position", BuiltIn::GlobalInvocationId => "gl_GlobalInvocationID", BuiltIn::BaseInstance => "gl_BaseInstance", BuiltIn::BaseVertex => "gl_BaseVertex", @@ -120,21 +241,42 @@ pub fn write(module: &Module, out: &mut impl Write) -> Result<(), Error> { continue; } + let name = if !es { + namer(global.name.as_ref()) + } else { + global.name.clone().ok_or_else(|| { + Error::Custom(String::from("Global names must be specified in es")) + })? + }; + if let Some(ref binding) = global.binding { - write!(out, "layout({}) ", Binding(binding.clone()))?; + if !es { + write!(out, "layout({}) ", Binding(binding.clone()))?; + } } if let Some(interpolation) = global.interpolation { - write!(out, "{} ", write_interpolation(interpolation)?)?; + if (entry_point.stage == ShaderStage::Fragment && global.class == StorageClass::Input) + || (entry_point.stage == ShaderStage::Vertex + && global.class == StorageClass::Output) + { + write!(out, "{} ", write_interpolation(interpolation)?)?; + } } - let name = namer(global.name.as_ref()); + let block = match global.class { + StorageClass::Input + | StorageClass::Output + | StorageClass::StorageBuffer + | StorageClass::Uniform => Some(namer(None)), + _ => None, + }; writeln!( out, "{}{} {};", - write_storage_class(global.class)?, - write_type(global.ty, &module.types, &structs)?, + write_storage_class(global.class, features)?, + write_type(global.ty, &module.types, &structs, block, features)?, name )?; @@ -143,28 +285,57 @@ pub fn write(module: &Module, out: &mut impl Write) -> Result<(), Error> { let mut functions = FastHashMap::default(); - // Do a first pass to collect names for (handle, func) in module.functions.iter() { - functions.insert(handle, namer(func.name.as_ref())); + // Discard all entry points + if entry_point.function != handle + && module + .entry_points + .iter() + .any(|entry| entry.function == handle) + { + continue; + } + + let name = if entry_point.function != handle { + namer(func.name.as_ref()) + } else { + String::from("main") + }; + + writeln!( + out, + "{} {}({});", + func.return_type + .map(|ty| write_type(ty, &module.types, &structs, None, features)) + .transpose()? + .as_deref() + .unwrap_or("void"), + name, + func.parameter_types + .iter() + .map(|ty| write_type(*ty, &module.types, &structs, None, features)) + .collect::, _>>()? + .join(","), + )?; + + functions.insert(handle, name); } - // TODO: glsl is order dependent so we need to build functions in order - for (handle, func) in module.functions.iter() { - let name = functions.get(&handle).unwrap(); + for (handle, name) in functions.iter() { + let func = &module.functions[*handle]; writeln!( out, "{} {}({}) {{", func.return_type - .map_or(Ok(String::from("void")), |ty| write_type( - ty, - &module.types, - &structs - ))?, + .map(|ty| write_type(ty, &module.types, &structs, None, features)) + .transpose()? + .as_deref() + .unwrap_or("void"), name, func.parameter_types .iter() - .map(|ty| write_type(*ty, &module.types, &structs)) + .map(|ty| write_type(*ty, &module.types, &structs, None, features)) .collect::, _>>()? .join(","), )?; @@ -175,6 +346,21 @@ pub fn write(module: &Module, out: &mut impl Write) -> Result<(), Error> { .map(|(handle, local)| (handle, namer(local.name.as_ref()))) .collect(); + for (handle, name) in locals.iter() { + writeln!( + out, + "{} {};", + write_type( + func.local_variables[*handle].ty, + &module.types, + &structs, + None, + features + )?, + name + )?; + } + let mut builder = StatementBuilder { functions: &functions, globals: &globals_lookup, @@ -188,24 +374,9 @@ pub fn write(module: &Module, out: &mut impl Write) -> Result<(), Error> { .collect(), expressions: &func.expressions, locals: &func.local_variables, + features, }; - for (handle, name) in locals.iter() { - let ty = write_type(func.local_variables[*handle].ty, &module.types, &structs)?; - let init = func.local_variables[*handle].init; - if let Some(init) = init { - writeln!( - out, - "{} {} = {init};", - ty, - name, - init = write_expression(&func.expressions[init], &module, &mut builder)?.0, - )?; - } else { - writeln!(out, "{} {};", ty, name,)?; - } - } - for sta in func.body.iter() { writeln!(out, "{}", write_statement(sta, module, &mut builder)?)?; } @@ -237,6 +408,7 @@ struct StatementBuilder<'a> { pub args: &'a FastHashMap)>, pub expressions: &'a Arena, pub locals: &'a Arena, + pub features: SupportedFeatures, } fn write_statement( @@ -398,7 +570,12 @@ fn write_expression<'a>( } } Expression::Constant(constant) => ( - write_constant(&module.constants[*constant], module, builder)?, + write_constant( + &module.constants[*constant], + module, + builder, + builder.features, + )?, Cow::Borrowed(&module.types[module.constants[*constant].ty].inner), ), Expression::Compose { ty, components } => { @@ -445,12 +622,15 @@ fn write_expression<'a>( columns as u8, rows as u8, ), - TypeInner::Array { .. } => write_type(*ty, &module.types, builder.structs)?, + TypeInner::Array { .. } => { + write_type(*ty, &module.types, builder.structs, None, builder.features)? + .into_owned() + } TypeInner::Struct { .. } => builder.structs.get(ty).unwrap().clone(), _ => { return Err(Error::Custom(format!( "Cannot compose type {}", - write_type(*ty, &module.types, builder.structs)? + write_type(*ty, &module.types, builder.structs, None, builder.features)? ))) } }; @@ -514,7 +694,13 @@ fn write_expression<'a>( _ => { return Err(Error::Custom(format!( "Cannot build image of {}", - write_type(*base, &module.types, builder.structs)? + write_type( + *base, + &module.types, + builder.structs, + None, + builder.features + )? ))) } }, @@ -751,10 +937,11 @@ fn write_constant( constant: &Constant, module: &Module, builder: &StatementBuilder<'_>, + features: SupportedFeatures, ) -> Result { Ok(match constant.inner { ConstantInner::Sint(int) => int.to_string(), - ConstantInner::Uint(int) => int.to_string(), + ConstantInner::Uint(int) => format!("{}u", int), ConstantInner::Float(float) => format!("{:?}", float), ConstantInner::Bool(boolean) => boolean.to_string(), ConstantInner::Composite(ref components) => format!( @@ -764,34 +951,43 @@ fn write_constant( TypeInner::Matrix { columns, rows, .. } => format!("mat{}x{}", columns as u8, rows as u8,), TypeInner::Struct { .. } => builder.structs.get(&constant.ty).unwrap().clone(), - TypeInner::Array { .. } => write_type(constant.ty, &module.types, builder.structs)?, + TypeInner::Array { .. } => + write_type(constant.ty, &module.types, builder.structs, None, features)? + .into_owned(), _ => return Err(Error::Custom(format!( "Cannot build constant of type {}", - write_type(constant.ty, &module.types, builder.structs)? + write_type(constant.ty, &module.types, builder.structs, None, features)? ))), }, components .iter() - .map(|component| write_constant(&module.constants[*component], module, builder)) + .map(|component| write_constant( + &module.constants[*component], + module, + builder, + features + )) .collect::, _>>()? .join(","), ), }) } -fn write_type<'a>( +fn write_type( ty: Handle, - types: &'a Arena, - structs: &'a FastHashMap, String>, -) -> Result { + types: &Arena, + structs: &FastHashMap, String>, + block: Option, + features: SupportedFeatures, +) -> Result, Error> { Ok(match types[ty].inner { TypeInner::Scalar { kind, width } => match kind { - ScalarKind::Sint => String::from("int"), - ScalarKind::Uint => String::from("uint"), + ScalarKind::Sint => Cow::Borrowed("int"), + ScalarKind::Uint => Cow::Borrowed("uint"), ScalarKind::Float => match width { - 4 => String::from("float"), - 8 => String::from("double"), + 4 => Cow::Borrowed("float"), + 8 if features.contains(SupportedFeatures::DOUBLE_TYPE) => Cow::Borrowed("double"), _ => { return Err(Error::Custom(format!( "Cannot build float of width {}", @@ -799,16 +995,16 @@ fn write_type<'a>( ))) } }, - ScalarKind::Bool => String::from("bool"), + ScalarKind::Bool => Cow::Borrowed("bool"), }, - TypeInner::Vector { size, kind, width } => format!( + TypeInner::Vector { size, kind, width } => Cow::Owned(format!( "{}vec{}", match kind { ScalarKind::Sint => "i", ScalarKind::Uint => "u", ScalarKind::Float => match width { 4 => "", - 8 => "d", + 8 if features.contains(SupportedFeatures::DOUBLE_TYPE) => "d", _ => return Err(Error::Custom(format!( "Cannot build float of width {}", @@ -818,65 +1014,104 @@ fn write_type<'a>( ScalarKind::Bool => "b", }, size as u8 - ), + )), TypeInner::Matrix { columns, rows, kind, width, - } => format!( + } => Cow::Owned(format!( "{}mat{}x{}", match kind { - ScalarKind::Sint => "i", - ScalarKind::Uint => "u", + ScalarKind::Sint if features.contains(SupportedFeatures::NON_FLOAT_MATRICES) => "i", + ScalarKind::Uint if features.contains(SupportedFeatures::NON_FLOAT_MATRICES) => "u", ScalarKind::Float => match width { 4 => "", - 8 => "d", + 8 if features.contains( + SupportedFeatures::DOUBLE_TYPE & SupportedFeatures::NON_FLOAT_MATRICES + ) => + "d", _ => return Err(Error::Custom(format!( "Cannot build float of width {}", width ))), }, - ScalarKind::Bool => "b", + ScalarKind::Bool if features.contains(SupportedFeatures::NON_FLOAT_MATRICES) => "b", + _ => + return Err(Error::Custom(format!( + "Cannot build matrix of base type {:?}", + kind + ))), }, columns as u8, rows as u8 - ), - TypeInner::Pointer { base, .. } => write_type(base, types, structs)?, - TypeInner::Array { base, size, .. } => format!( + )), + TypeInner::Pointer { base, .. } => write_type(base, types, structs, None, features)?, + TypeInner::Array { base, size, .. } => Cow::Owned(format!( "{}[{}]", - write_type(base, types, structs)?, + write_type(base, types, structs, None, features)?, write_array_size(size)? - ), - TypeInner::Struct { .. } => structs.get(&ty).unwrap().clone(), - TypeInner::Image { base, dim, flags } => format!( - "{}texture{}{}", - match types[base].inner { - TypeInner::Scalar { kind, .. } => match kind { - ScalarKind::Sint => "i", - ScalarKind::Uint => "u", - ScalarKind::Float => "", + )), + TypeInner::Struct { ref members } => { + if let Some(name) = block { + let mut out = String::new(); + writeln!(&mut out, "{} {{", name)?; + + for (idx, member) in members.iter().enumerate() { + writeln!( + &mut out, + "{} {};", + write_type(member.ty, types, structs, None, features)?, + member.name.clone().unwrap_or_else(|| idx.to_string()) + )?; + } + + write!(&mut out, "}}")?; + + Cow::Owned(out) + } else { + Cow::Owned(structs.get(&ty).unwrap().clone()) + } + } + TypeInner::Image { base, dim, flags } => { + if flags.contains(ImageFlags::ARRAYED) + && dim != crate::ImageDimension::D2 + && !features.contains(SupportedFeatures::NON_2D_TEXTURE_ARRAYS) + { + return Err(Error::Custom(String::from( + "Arrayed non 2d images aren't supported", + ))); + } + + Cow::Owned(format!( + "{}texture{}{}", + match types[base].inner { + TypeInner::Scalar { kind, .. } => match kind { + ScalarKind::Sint => "i", + ScalarKind::Uint => "u", + ScalarKind::Float => "", + ScalarKind::Bool => + return Err(Error::Custom(String::from( + "Cannot build image of booleans", + ))), + }, _ => - return Err(Error::Custom(String::from( - "Cannot build image of booleans", + return Err(Error::Custom(format!( + "Cannot build image of type {}", + write_type(base, types, structs, block, features)? ))), }, - _ => - return Err(Error::Custom(format!( - "Cannot build image of type {}", - write_type(base, types, structs)? - ))), - }, - ImageDimension(dim), - write_image_flags(flags)? - ), - TypeInner::DepthImage { dim, arrayed } => format!( + ImageDimension(dim), + write_image_flags(flags, features)? + )) + } + TypeInner::DepthImage { dim, arrayed } => Cow::Owned(format!( "texture{}{}", ImageDimension(dim), if arrayed { "Array" } else { "" } - ), - TypeInner::Sampler { comparison } => String::from(if comparison { + )), + TypeInner::Sampler { comparison } => Cow::Borrowed(if comparison { "sampler" } else { "samplerShadow" @@ -884,17 +1119,36 @@ fn write_type<'a>( }) } -fn write_storage_class(class: StorageClass) -> Result { - Ok(String::from(match class { +fn write_storage_class( + class: StorageClass, + features: SupportedFeatures, +) -> Result<&'static str, Error> { + Ok(match class { StorageClass::Constant => "const ", StorageClass::Function => "", StorageClass::Input => "in ", StorageClass::Output => "out ", StorageClass::Private => "", - StorageClass::StorageBuffer => "buffer ", + StorageClass::StorageBuffer => { + if features.contains(SupportedFeatures::BUFFER_STORAGE) { + "buffer " + } else { + return Err(Error::Custom(String::from( + "buffer storage class isn't supported in glsl es", + ))); + } + } StorageClass::Uniform => "uniform ", - StorageClass::WorkGroup => "shared ", - })) + StorageClass::WorkGroup => { + if features.contains(SupportedFeatures::SHARED_STORAGE) { + "shared " + } else { + return Err(Error::Custom(String::from( + "workgroup storage class isn't supported in glsl es", + ))); + } + } + }) } fn write_interpolation(interpolation: Interpolation) -> Result<&'static str, Error> { @@ -919,18 +1173,34 @@ fn write_array_size(size: ArraySize) -> Result { }) } -fn write_image_flags(flags: ImageFlags) -> Result { - let mut out = String::new(); +fn write_image_flags(flags: ImageFlags, features: SupportedFeatures) -> Result { + let ms = if flags.contains(ImageFlags::MULTISAMPLED) { + if features.contains(SupportedFeatures::MULTISAMPLED_TEXTURES) { + "MS" + } else { + return Err(Error::Custom(String::from( + "Multi sampled textures aren't supported", + ))); + } + } else { + "" + }; - if flags.contains(ImageFlags::MULTISAMPLED) { - write!(out, "MS")?; + let array = if flags.contains(ImageFlags::ARRAYED) { + "Array" + } else { + "" + }; + + if flags.contains(ImageFlags::ARRAYED & ImageFlags::MULTISAMPLED) + && !features.contains(SupportedFeatures::MULTISAMPLED_TEXTURE_ARRAYS) + { + return Err(Error::Custom(String::from( + "Multi sampled texture arrays aren't supported", + ))); } - if flags.contains(ImageFlags::ARRAYED) { - write!(out, "Array")?; - } - - Ok(out) + Ok(format!("{}{}", ms, array)) } struct ImageDimension(crate::ImageDimension); @@ -948,3 +1218,50 @@ impl fmt::Display for ImageDimension { ) } } + +fn write_struct( + handle: Handle, + members: &[StructMember], + module: &Module, + structs: &FastHashMap, String>, + out: &mut impl Write, + built_structs: &mut FastHashMap, ()>, + features: SupportedFeatures, +) -> Result<(), Error> { + if built_structs.get(&handle).is_some() { + return Ok(()); + } + + let mut tmp = String::new(); + + let name = structs.get(&handle).unwrap(); + + writeln!(&mut tmp, "struct {} {{", name)?; + for (idx, member) in members.iter().enumerate() { + if let TypeInner::Struct { ref members } = module.types[member.ty].inner { + write_struct( + member.ty, + members, + module, + structs, + out, + built_structs, + features, + )?; + } + + writeln!( + &mut tmp, + " {} {};", + write_type(member.ty, &module.types, &structs, None, features)?, + member.name.clone().unwrap_or_else(|| idx.to_string()) + )?; + } + writeln!(&mut tmp, "}};")?; + + built_structs.insert(handle, ()); + + writeln!(out, "{}", tmp)?; + + Ok(()) +}