summaryrefslogtreecommitdiffstats
path: root/third_party/rust/naga/src/back
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/naga/src/back')
-rw-r--r--third_party/rust/naga/src/back/glsl.rs1579
-rw-r--r--third_party/rust/naga/src/back/glsl/keywords.rs204
-rw-r--r--third_party/rust/naga/src/back/mod.rs8
-rw-r--r--third_party/rust/naga/src/back/msl/keywords.rs102
-rw-r--r--third_party/rust/naga/src/back/msl/mod.rs211
-rw-r--r--third_party/rust/naga/src/back/msl/writer.rs990
-rw-r--r--third_party/rust/naga/src/back/spv/helpers.rs20
-rw-r--r--third_party/rust/naga/src/back/spv/instructions.rs708
-rw-r--r--third_party/rust/naga/src/back/spv/layout.rs91
-rw-r--r--third_party/rust/naga/src/back/spv/layout_tests.rs166
-rw-r--r--third_party/rust/naga/src/back/spv/mod.rs52
-rw-r--r--third_party/rust/naga/src/back/spv/test_framework.rs27
-rw-r--r--third_party/rust/naga/src/back/spv/writer.rs1776
13 files changed, 5934 insertions, 0 deletions
diff --git a/third_party/rust/naga/src/back/glsl.rs b/third_party/rust/naga/src/back/glsl.rs
new file mode 100644
index 0000000000..6a497a675b
--- /dev/null
+++ b/third_party/rust/naga/src/back/glsl.rs
@@ -0,0 +1,1579 @@
+//! OpenGL shading language backend
+//!
+//! The main structure is [`Writer`](struct.Writer.html), it maintains internal state that is used
+//! to output a `Module` into glsl
+//!
+//! # Supported versions
+//! ### Core
+//! - 330
+//! - 400
+//! - 410
+//! - 420
+//! - 430
+//! - 450
+//! - 460
+//!
+//! ### ES
+//! - 300
+//! - 310
+//!
+
+use crate::{
+ proc::{
+ CallGraph, CallGraphBuilder, Interface, NameKey, Namer, ResolveContext, ResolveError,
+ Typifier, Visitor,
+ },
+ Arena, ArraySize, BinaryOperator, BuiltIn, ConservativeDepth, Constant, ConstantInner,
+ DerivativeAxis, Expression, FastHashMap, Function, FunctionOrigin, GlobalVariable, Handle,
+ ImageClass, Interpolation, IntrinsicFunction, LocalVariable, Module, ScalarKind, ShaderStage,
+ Statement, StorageAccess, StorageClass, StorageFormat, StructMember, Type, TypeInner,
+ UnaryOperator,
+};
+use std::{
+ cmp::Ordering,
+ fmt::{self, Error as FmtError},
+ io::{Error as IoError, Write},
+};
+
+/// Contains a constant with a slice of all the reserved keywords RESERVED_KEYWORDS
+mod keywords;
+
+const SUPPORTED_CORE_VERSIONS: &[u16] = &[330, 400, 410, 420, 430, 440, 450];
+const SUPPORTED_ES_VERSIONS: &[u16] = &[300, 310];
+
+#[derive(Debug)]
+pub enum Error {
+ FormatError(FmtError),
+ IoError(IoError),
+ Type(ResolveError),
+ Custom(String),
+}
+
+impl From<FmtError> for Error {
+ fn from(err: FmtError) -> Self {
+ Error::FormatError(err)
+ }
+}
+
+impl From<IoError> for Error {
+ fn from(err: IoError) -> Self {
+ Error::IoError(err)
+ }
+}
+
+impl From<ResolveError> for Error {
+ fn from(err: ResolveError) -> Self {
+ Error::Type(err)
+ }
+}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Error::FormatError(err) => write!(f, "Formatting error {}", err),
+ Error::IoError(err) => write!(f, "Io error: {}", err),
+ Error::Type(err) => write!(f, "Type error: {:?}", err),
+ Error::Custom(err) => write!(f, "{}", err),
+ }
+ }
+}
+
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub enum Version {
+ Desktop(u16),
+ Embedded(u16),
+}
+
+impl Version {
+ fn is_es(&self) -> bool {
+ match self {
+ Version::Desktop(_) => false,
+ Version::Embedded(_) => true,
+ }
+ }
+
+ fn is_supported(&self) -> bool {
+ match self {
+ Version::Desktop(v) => SUPPORTED_CORE_VERSIONS.contains(v),
+ Version::Embedded(v) => SUPPORTED_ES_VERSIONS.contains(v),
+ }
+ }
+}
+
+impl PartialOrd for Version {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ match (*self, *other) {
+ (Version::Desktop(x), Version::Desktop(y)) => Some(x.cmp(&y)),
+ (Version::Embedded(x), Version::Embedded(y)) => Some(x.cmp(&y)),
+ _ => None,
+ }
+ }
+}
+
+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: (ShaderStage, String),
+}
+
+#[derive(Debug, Clone)]
+pub struct TextureMapping {
+ pub texture: Handle<GlobalVariable>,
+ pub sampler: Option<Handle<GlobalVariable>>,
+}
+
+bitflags::bitflags! {
+ struct Features: u32 {
+ const BUFFER_STORAGE = 1;
+ const ARRAY_OF_ARRAYS = 1 << 1;
+ const DOUBLE_TYPE = 1 << 2;
+ const FULL_IMAGE_FORMATS = 1 << 3;
+ const MULTISAMPLED_TEXTURES = 1 << 4;
+ const MULTISAMPLED_TEXTURE_ARRAYS = 1 << 5;
+ const CUBE_TEXTURES_ARRAY = 1 << 6;
+ const COMPUTE_SHADER = 1 << 7;
+ const IMAGE_LOAD_STORE = 1 << 8;
+ const CONSERVATIVE_DEPTH = 1 << 9;
+ const TEXTURE_1D = 1 << 10;
+ const PUSH_CONSTANT = 1 << 11;
+ }
+}
+
+struct FeaturesManager(Features);
+
+impl FeaturesManager {
+ pub fn new() -> Self {
+ Self(Features::empty())
+ }
+
+ pub fn request(&mut self, features: Features) {
+ self.0 |= features
+ }
+
+ #[allow(clippy::collapsible_if)]
+ pub fn write(&self, version: Version, mut out: impl Write) -> Result<(), Error> {
+ if self.0.contains(Features::COMPUTE_SHADER) {
+ if version < Version::Embedded(310) || version < Version::Desktop(420) {
+ return Err(Error::Custom(format!(
+ "Version {} doesn't support compute shaders",
+ version
+ )));
+ }
+
+ if !version.is_es() {
+ // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_compute_shader.txt
+ writeln!(out, "#extension GL_ARB_compute_shader : require")?;
+ }
+ }
+
+ if self.0.contains(Features::BUFFER_STORAGE) {
+ if version < Version::Embedded(310) || version < Version::Desktop(400) {
+ return Err(Error::Custom(format!(
+ "Version {} doesn't support buffer storage class",
+ version
+ )));
+ }
+
+ if let Version::Desktop(_) = version {
+ // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_shader_storage_buffer_object.txt
+ writeln!(
+ out,
+ "#extension GL_ARB_shader_storage_buffer_object : require"
+ )?;
+ }
+ }
+
+ if self.0.contains(Features::DOUBLE_TYPE) {
+ if version.is_es() || version < Version::Desktop(150) {
+ return Err(Error::Custom(format!(
+ "Version {} doesn't support doubles",
+ version
+ )));
+ }
+
+ if version < Version::Desktop(400) {
+ // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_gpu_shader_fp64.txt
+ writeln!(out, "#extension GL_ARB_gpu_shader_fp64 : require")?;
+ }
+ }
+
+ if self.0.contains(Features::CUBE_TEXTURES_ARRAY) {
+ if version < Version::Embedded(310) || version < Version::Desktop(130) {
+ return Err(Error::Custom(format!(
+ "Version {} doesn't support cube map array textures",
+ version
+ )));
+ }
+
+ if version.is_es() {
+ // https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_texture_cube_map_array.txt
+ writeln!(out, "#extension GL_EXT_texture_cube_map_array : require")?;
+ } else if version < Version::Desktop(400) {
+ // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_texture_cube_map_array.txt
+ writeln!(out, "#extension GL_ARB_texture_cube_map_array : require")?;
+ }
+ }
+
+ if self.0.contains(Features::MULTISAMPLED_TEXTURES) {
+ if version < Version::Embedded(300) {
+ return Err(Error::Custom(format!(
+ "Version {} doesn't support multi sampled textures",
+ version
+ )));
+ }
+ }
+
+ if self.0.contains(Features::MULTISAMPLED_TEXTURE_ARRAYS) {
+ if version < Version::Embedded(310) {
+ return Err(Error::Custom(format!(
+ "Version {} doesn't support multi sampled texture arrays",
+ version
+ )));
+ }
+
+ if version.is_es() {
+ // https://www.khronos.org/registry/OpenGL/extensions/OES/OES_texture_storage_multisample_2d_array.txt
+ writeln!(
+ out,
+ "#extension GL_OES_texture_storage_multisample_2d_array : require"
+ )?;
+ }
+ }
+
+ if self.0.contains(Features::ARRAY_OF_ARRAYS) {
+ if version < Version::Embedded(310) || version < Version::Desktop(120) {
+ return Err(Error::Custom(format!(
+ "Version {} doesn't arrays of arrays",
+ version
+ )));
+ }
+
+ if version < Version::Desktop(430) {
+ // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_arrays_of_arrays.txt
+ writeln!(out, "#extension ARB_arrays_of_arrays : require")?;
+ }
+ }
+
+ if self.0.contains(Features::IMAGE_LOAD_STORE) {
+ if version < Version::Embedded(310) || version < Version::Desktop(130) {
+ return Err(Error::Custom(format!(
+ "Version {} doesn't support images load/stores",
+ version
+ )));
+ }
+
+ if self.0.contains(Features::FULL_IMAGE_FORMATS) && version.is_es() {
+ // https://www.khronos.org/registry/OpenGL/extensions/NV/NV_image_formats.txt
+ writeln!(out, "#extension GL_NV_image_formats : require")?;
+ }
+
+ if version < Version::Desktop(420) {
+ // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_shader_image_load_store.txt
+ writeln!(out, "#extension GL_ARB_shader_image_load_store : require")?;
+ }
+ }
+
+ if self.0.contains(Features::CONSERVATIVE_DEPTH) {
+ if version < Version::Embedded(300) || version < Version::Desktop(130) {
+ return Err(Error::Custom(format!(
+ "Version {} doesn't support conservative depth",
+ version
+ )));
+ }
+
+ if version.is_es() {
+ // https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_conservative_depth.txt
+ writeln!(out, "#extension GL_EXT_conservative_depth : require")?;
+ }
+
+ if version < Version::Desktop(420) {
+ // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_conservative_depth.txt
+ writeln!(out, "#extension GL_ARB_conservative_depth : require")?;
+ }
+ }
+
+ if self.0.contains(Features::TEXTURE_1D) {
+ if version.is_es() {
+ return Err(Error::Custom(format!(
+ "Version {} doesn't support 1d textures",
+ version
+ )));
+ }
+ }
+
+ Ok(())
+ }
+}
+
+enum FunctionType {
+ Function(Handle<Function>),
+ EntryPoint(crate::proc::EntryPointIndex),
+}
+
+struct FunctionCtx<'a, 'b> {
+ func: FunctionType,
+ expressions: &'a Arena<Expression>,
+ typifier: &'b Typifier,
+}
+
+impl<'a, 'b> FunctionCtx<'a, 'b> {
+ fn name_key(&self, local: Handle<LocalVariable>) -> NameKey {
+ match self.func {
+ FunctionType::Function(handle) => NameKey::FunctionLocal(handle, local),
+ FunctionType::EntryPoint(idx) => NameKey::EntryPointLocal(idx, local),
+ }
+ }
+
+ fn get_arg<'c>(&self, arg: u32, names: &'c FastHashMap<NameKey, String>) -> &'c str {
+ match self.func {
+ FunctionType::Function(handle) => &names[&NameKey::FunctionArgument(handle, arg)],
+ FunctionType::EntryPoint(_) => unreachable!(),
+ }
+ }
+}
+
+/// Helper structure that generates a number
+#[derive(Default)]
+struct IdGenerator(u32);
+
+impl IdGenerator {
+ fn generate(&mut self) -> u32 {
+ let ret = self.0;
+ self.0 += 1;
+ ret
+ }
+}
+
+/// Main structure of the glsl backend responsible for all code generation
+pub struct Writer<'a, W> {
+ // Inputs
+ module: &'a Module,
+ out: W,
+ options: &'a Options,
+
+ // Internal State
+ features: FeaturesManager,
+ names: FastHashMap<NameKey, String>,
+ entry_point: &'a crate::EntryPoint,
+ entry_point_idx: crate::proc::EntryPointIndex,
+ call_graph: CallGraph,
+
+ /// Used to generate a unique number for blocks
+ block_id: IdGenerator,
+}
+
+impl<'a, W: Write> Writer<'a, W> {
+ pub fn new(out: W, module: &'a Module, options: &'a Options) -> Result<Self, Error> {
+ if !options.version.is_supported() {
+ return Err(Error::Custom(format!(
+ "Version not supported {}",
+ options.version
+ )));
+ }
+
+ let (ep_idx, ep) = module
+ .entry_points
+ .iter()
+ .enumerate()
+ .find_map(|(i, (key, entry_point))| {
+ Some((i as u16, entry_point)).filter(|_| &options.entry_point == key)
+ })
+ .ok_or_else(|| Error::Custom(String::from("Entry point not found")))?;
+
+ let mut names = FastHashMap::default();
+ Namer::process(module, keywords::RESERVED_KEYWORDS, &mut names);
+
+ let call_graph = CallGraphBuilder {
+ functions: &module.functions,
+ }
+ .process(&ep.function);
+
+ let mut this = Self {
+ module,
+ out,
+ options,
+
+ features: FeaturesManager::new(),
+ names,
+ entry_point: ep,
+ entry_point_idx: ep_idx,
+ call_graph,
+
+ block_id: IdGenerator::default(),
+ };
+
+ this.collect_required_features()?;
+
+ Ok(this)
+ }
+
+ fn collect_required_features(&mut self) -> Result<(), Error> {
+ let stage = self.options.entry_point.0;
+
+ if let Some(depth_test) = self.entry_point.early_depth_test {
+ self.features.request(Features::IMAGE_LOAD_STORE);
+
+ if depth_test.conservative.is_some() {
+ self.features.request(Features::CONSERVATIVE_DEPTH);
+ }
+ }
+
+ if let ShaderStage::Compute = stage {
+ self.features.request(Features::COMPUTE_SHADER)
+ }
+
+ for (_, ty) in self.module.types.iter() {
+ match ty.inner {
+ TypeInner::Scalar { kind, width } => self.scalar_required_features(kind, width),
+ TypeInner::Vector { kind, width, .. } => self.scalar_required_features(kind, width),
+ TypeInner::Matrix { .. } => self.scalar_required_features(ScalarKind::Float, 8),
+ TypeInner::Array { base, .. } => {
+ if let TypeInner::Array { .. } = self.module.types[base].inner {
+ self.features.request(Features::ARRAY_OF_ARRAYS)
+ }
+ }
+ TypeInner::Image {
+ dim,
+ arrayed,
+ class,
+ } => {
+ if arrayed && dim == crate::ImageDimension::Cube {
+ self.features.request(Features::CUBE_TEXTURES_ARRAY)
+ } else if dim == crate::ImageDimension::D1 {
+ self.features.request(Features::TEXTURE_1D)
+ }
+
+ match class {
+ ImageClass::Sampled { multi: true, .. } => {
+ self.features.request(Features::MULTISAMPLED_TEXTURES);
+ if arrayed {
+ self.features.request(Features::MULTISAMPLED_TEXTURE_ARRAYS);
+ }
+ }
+ ImageClass::Storage(format) => match format {
+ StorageFormat::R8Unorm
+ | StorageFormat::R8Snorm
+ | StorageFormat::R8Uint
+ | StorageFormat::R8Sint
+ | StorageFormat::R16Uint
+ | StorageFormat::R16Sint
+ | StorageFormat::R16Float
+ | StorageFormat::Rg8Unorm
+ | StorageFormat::Rg8Snorm
+ | StorageFormat::Rg8Uint
+ | StorageFormat::Rg8Sint
+ | StorageFormat::Rg16Uint
+ | StorageFormat::Rg16Sint
+ | StorageFormat::Rg16Float
+ | StorageFormat::Rgb10a2Unorm
+ | StorageFormat::Rg11b10Float
+ | StorageFormat::Rg32Uint
+ | StorageFormat::Rg32Sint
+ | StorageFormat::Rg32Float => {
+ self.features.request(Features::FULL_IMAGE_FORMATS)
+ }
+ _ => {}
+ },
+ _ => {}
+ }
+ }
+ _ => {}
+ }
+ }
+
+ for (_, global) in self.module.global_variables.iter() {
+ match global.class {
+ StorageClass::WorkGroup => self.features.request(Features::COMPUTE_SHADER),
+ StorageClass::Storage => self.features.request(Features::BUFFER_STORAGE),
+ StorageClass::PushConstant => self.features.request(Features::PUSH_CONSTANT),
+ _ => {}
+ }
+ }
+
+ Ok(())
+ }
+
+ fn scalar_required_features(&mut self, kind: ScalarKind, width: crate::Bytes) {
+ if kind == ScalarKind::Float && width == 8 {
+ self.features.request(Features::DOUBLE_TYPE);
+ }
+ }
+
+ pub fn write(&mut self) -> Result<FastHashMap<String, TextureMapping>, Error> {
+ let es = self.options.version.is_es();
+
+ writeln!(self.out, "#version {}", self.options.version)?;
+ self.features.write(self.options.version, &mut self.out)?;
+ writeln!(self.out)?;
+
+ if es {
+ writeln!(self.out, "precision highp float;\n")?;
+ }
+
+ if let Some(depth_test) = self.entry_point.early_depth_test {
+ writeln!(self.out, "layout(early_fragment_tests) in;\n")?;
+
+ if let Some(conservative) = depth_test.conservative {
+ writeln!(
+ self.out,
+ "layout (depth_{}) out float gl_FragDepth;\n",
+ match conservative {
+ ConservativeDepth::GreaterEqual => "greater",
+ ConservativeDepth::LessEqual => "less",
+ ConservativeDepth::Unchanged => "unchanged",
+ }
+ )?;
+ }
+ }
+
+ for (handle, ty) in self.module.types.iter() {
+ if let TypeInner::Struct { ref members } = ty.inner {
+ self.write_struct(handle, members)?
+ }
+ }
+
+ writeln!(self.out)?;
+
+ let texture_mappings = self.collect_texture_mapping(
+ self.call_graph
+ .raw_nodes()
+ .iter()
+ .map(|node| &self.module.functions[node.weight])
+ .chain(std::iter::once(&self.entry_point.function)),
+ )?;
+
+ for (handle, global) in self
+ .module
+ .global_variables
+ .iter()
+ .zip(&self.entry_point.function.global_usage)
+ .filter_map(|(global, usage)| Some(global).filter(|_| !usage.is_empty()))
+ {
+ if let Some(crate::Binding::BuiltIn(_)) = global.binding {
+ continue;
+ }
+
+ match self.module.types[global.ty].inner {
+ TypeInner::Image {
+ dim,
+ arrayed,
+ class,
+ } => {
+ if let TypeInner::Image {
+ class: ImageClass::Storage(format),
+ ..
+ } = self.module.types[global.ty].inner
+ {
+ write!(self.out, "layout({}) ", glsl_storage_format(format))?;
+ }
+
+ if global.storage_access == StorageAccess::LOAD {
+ write!(self.out, "readonly ")?;
+ } else if global.storage_access == StorageAccess::STORE {
+ write!(self.out, "writeonly ")?;
+ }
+
+ write!(self.out, "uniform ")?;
+
+ self.write_image_type(dim, arrayed, class)?;
+
+ writeln!(
+ self.out,
+ " {};",
+ self.names[&NameKey::GlobalVariable(handle)]
+ )?
+ }
+ TypeInner::Sampler { .. } => continue,
+ _ => self.write_global(handle, global)?,
+ }
+ }
+
+ writeln!(self.out)?;
+
+ // Sort the graph topologically so that functions calls are valid
+ // It's impossible for this to panic because the IR forbids cycles
+ let functions = petgraph::algo::toposort(&self.call_graph, None).unwrap();
+
+ for node in functions {
+ let handle = self.call_graph[node];
+ let name = self.names[&NameKey::Function(handle)].clone();
+ self.write_function(
+ FunctionType::Function(handle),
+ &self.module.functions[handle],
+ name,
+ )?;
+ }
+
+ self.write_function(
+ FunctionType::EntryPoint(self.entry_point_idx),
+ &self.entry_point.function,
+ "main",
+ )?;
+
+ Ok(texture_mappings)
+ }
+
+ fn write_global(
+ &mut self,
+ handle: Handle<GlobalVariable>,
+ global: &GlobalVariable,
+ ) -> Result<(), Error> {
+ if global.storage_access == StorageAccess::LOAD {
+ write!(self.out, "readonly ")?;
+ } else if global.storage_access == StorageAccess::STORE {
+ write!(self.out, "writeonly ")?;
+ }
+
+ if let Some(interpolation) = global.interpolation {
+ match (self.options.entry_point.0, global.class) {
+ (ShaderStage::Fragment, StorageClass::Input)
+ | (ShaderStage::Vertex, StorageClass::Output) => {
+ write!(self.out, "{} ", glsl_interpolation(interpolation)?)?;
+ }
+ _ => (),
+ };
+ }
+
+ let block = match global.class {
+ StorageClass::Storage | StorageClass::Uniform => {
+ let block_name = self.names[&NameKey::Type(global.ty)].clone();
+
+ Some(block_name)
+ }
+ _ => None,
+ };
+
+ write!(self.out, "{} ", glsl_storage_class(global.class))?;
+
+ self.write_type(global.ty, block)?;
+
+ let name = &self.names[&NameKey::GlobalVariable(handle)];
+ writeln!(self.out, " {};", name)?;
+
+ Ok(())
+ }
+
+ fn write_function<N: AsRef<str>>(
+ &mut self,
+ ty: FunctionType,
+ func: &Function,
+ name: N,
+ ) -> Result<(), Error> {
+ let mut typifier = Typifier::new();
+
+ typifier.resolve_all(
+ &func.expressions,
+ &self.module.types,
+ &ResolveContext {
+ constants: &self.module.constants,
+ global_vars: &self.module.global_variables,
+ local_vars: &func.local_variables,
+ functions: &self.module.functions,
+ arguments: &func.arguments,
+ },
+ )?;
+
+ let ctx = FunctionCtx {
+ func: ty,
+ expressions: &func.expressions,
+ typifier: &typifier,
+ };
+
+ self.write_fn_header(name.as_ref(), func, &ctx)?;
+ writeln!(self.out, " {{",)?;
+
+ for (handle, local) in func.local_variables.iter() {
+ write!(self.out, "\t")?;
+ self.write_type(local.ty, None)?;
+
+ write!(self.out, " {}", self.names[&ctx.name_key(handle)])?;
+
+ if let Some(init) = local.init {
+ write!(self.out, " = ",)?;
+
+ self.write_constant(&self.module.constants[init])?;
+ }
+
+ writeln!(self.out, ";")?
+ }
+
+ writeln!(self.out)?;
+
+ for sta in func.body.iter() {
+ self.write_stmt(sta, &ctx, 1)?;
+ }
+
+ Ok(writeln!(self.out, "}}")?)
+ }
+
+ fn write_slice<T, F: FnMut(&mut Self, u32, &T) -> Result<(), Error>>(
+ &mut self,
+ data: &[T],
+ mut f: F,
+ ) -> Result<(), Error> {
+ for (i, item) in data.iter().enumerate() {
+ f(self, i as u32, item)?;
+
+ if i != data.len().saturating_sub(1) {
+ write!(self.out, ",")?;
+ }
+ }
+
+ Ok(())
+ }
+
+ fn write_fn_header(
+ &mut self,
+ name: &str,
+ func: &Function,
+ ctx: &FunctionCtx<'_, '_>,
+ ) -> Result<(), Error> {
+ if let Some(ty) = func.return_type {
+ self.write_type(ty, None)?;
+ } else {
+ write!(self.out, "void")?;
+ }
+
+ write!(self.out, " {}(", name)?;
+
+ self.write_slice(&func.arguments, |this, i, arg| {
+ this.write_type(arg.ty, None)?;
+
+ let name = ctx.get_arg(i, &this.names);
+
+ Ok(write!(this.out, " {}", name)?)
+ })?;
+
+ write!(self.out, ")")?;
+
+ Ok(())
+ }
+
+ fn write_type(&mut self, ty: Handle<Type>, block: Option<String>) -> Result<(), Error> {
+ match self.module.types[ty].inner {
+ TypeInner::Scalar { kind, width } => {
+ write!(self.out, "{}", glsl_scalar(kind, width)?.full)?
+ }
+ TypeInner::Vector { size, kind, width } => write!(
+ self.out,
+ "{}vec{}",
+ glsl_scalar(kind, width)?.prefix,
+ size as u8
+ )?,
+ TypeInner::Matrix {
+ columns,
+ rows,
+ width,
+ } => write!(
+ self.out,
+ "{}mat{}x{}",
+ glsl_scalar(ScalarKind::Float, width)?.prefix,
+ columns as u8,
+ rows as u8
+ )?,
+ TypeInner::Pointer { base, .. } => self.write_type(base, None)?,
+ TypeInner::Array { base, size, .. } => {
+ self.write_type(base, None)?;
+
+ write!(self.out, "[")?;
+ self.write_array_size(size)?;
+ write!(self.out, "]")?
+ }
+ TypeInner::Struct { ref members } => {
+ if let Some(name) = block {
+ writeln!(self.out, "{}_block_{} {{", name, self.block_id.generate())?;
+
+ for (idx, member) in members.iter().enumerate() {
+ self.write_type(member.ty, None)?;
+
+ writeln!(
+ self.out,
+ " {};",
+ &self.names[&NameKey::StructMember(ty, idx as u32)]
+ )?;
+ }
+
+ write!(self.out, "}}")?
+ } else {
+ write!(self.out, "{}", &self.names[&NameKey::Type(ty)])?
+ }
+ }
+ _ => unreachable!(),
+ }
+
+ Ok(())
+ }
+
+ fn write_image_type(
+ &mut self,
+ dim: crate::ImageDimension,
+ arrayed: bool,
+ class: ImageClass,
+ ) -> Result<(), Error> {
+ let (base, kind, ms, comparison) = match class {
+ ImageClass::Sampled { kind, multi: true } => ("sampler", kind, "MS", ""),
+ ImageClass::Sampled { kind, multi: false } => ("sampler", kind, "", ""),
+ ImageClass::Depth => ("sampler", crate::ScalarKind::Float, "", "Shadow"),
+ ImageClass::Storage(format) => ("image", format.into(), "", ""),
+ };
+
+ Ok(write!(
+ self.out,
+ "{}{}{}{}{}{}",
+ glsl_scalar(kind, 4)?.prefix,
+ base,
+ ImageDimension(dim),
+ ms,
+ if arrayed { "Array" } else { "" },
+ comparison
+ )?)
+ }
+
+ fn write_array_size(&mut self, size: ArraySize) -> Result<(), Error> {
+ match size {
+ ArraySize::Constant(const_handle) => match self.module.constants[const_handle].inner {
+ ConstantInner::Uint(size) => write!(self.out, "{}", size)?,
+ _ => unreachable!(),
+ },
+ ArraySize::Dynamic => (),
+ }
+
+ Ok(())
+ }
+
+ fn collect_texture_mapping(
+ &self,
+ functions: impl Iterator<Item = &'a Function>,
+ ) -> Result<FastHashMap<String, TextureMapping>, Error> {
+ let mut mappings = FastHashMap::default();
+
+ for func in functions {
+ let mut interface = Interface {
+ expressions: &func.expressions,
+ local_variables: &func.local_variables,
+ visitor: TextureMappingVisitor {
+ names: &self.names,
+ expressions: &func.expressions,
+ map: &mut mappings,
+ error: None,
+ },
+ };
+ interface.traverse(&func.body);
+
+ if let Some(error) = interface.visitor.error {
+ return Err(error);
+ }
+ }
+
+ Ok(mappings)
+ }
+
+ fn write_struct(
+ &mut self,
+ handle: Handle<Type>,
+ members: &[StructMember],
+ ) -> Result<(), Error> {
+ writeln!(self.out, "struct {} {{", self.names[&NameKey::Type(handle)])?;
+
+ for (idx, member) in members.iter().enumerate() {
+ write!(self.out, "\t")?;
+ self.write_type(member.ty, None)?;
+ writeln!(
+ self.out,
+ " {};",
+ self.names[&NameKey::StructMember(handle, idx as u32)]
+ )?;
+ }
+
+ writeln!(self.out, "}};")?;
+ Ok(())
+ }
+
+ fn write_stmt(
+ &mut self,
+ sta: &Statement,
+ ctx: &FunctionCtx<'_, '_>,
+ indent: usize,
+ ) -> Result<(), Error> {
+ write!(self.out, "{}", "\t".repeat(indent))?;
+
+ match sta {
+ Statement::Block(block) => {
+ writeln!(self.out, "{{")?;
+ for sta in block.iter() {
+ self.write_stmt(sta, ctx, indent + 1)?
+ }
+ writeln!(self.out, "{}}}", "\t".repeat(indent))?
+ }
+ Statement::If {
+ condition,
+ accept,
+ reject,
+ } => {
+ write!(self.out, "if(")?;
+ self.write_expr(*condition, ctx)?;
+ writeln!(self.out, ") {{")?;
+
+ for sta in accept {
+ self.write_stmt(sta, ctx, indent + 1)?;
+ }
+
+ if !reject.is_empty() {
+ writeln!(self.out, "{}}} else {{", "\t".repeat(indent))?;
+
+ for sta in reject {
+ self.write_stmt(sta, ctx, indent + 1)?;
+ }
+ }
+
+ writeln!(self.out, "{}}}", "\t".repeat(indent))?
+ }
+ Statement::Switch {
+ selector,
+ cases,
+ default,
+ } => {
+ write!(self.out, "switch(")?;
+ self.write_expr(*selector, ctx)?;
+ writeln!(self.out, ") {{")?;
+
+ for (label, (block, fallthrough)) in cases {
+ writeln!(self.out, "{}case {}:", "\t".repeat(indent + 1), label)?;
+
+ for sta in block {
+ self.write_stmt(sta, ctx, indent + 2)?;
+ }
+
+ if fallthrough.is_none() {
+ writeln!(self.out, "{}break;", "\t".repeat(indent + 2))?;
+ }
+ }
+
+ if !default.is_empty() {
+ writeln!(self.out, "{}default:", "\t".repeat(indent + 1))?;
+
+ for sta in default {
+ self.write_stmt(sta, ctx, indent + 2)?;
+ }
+ }
+
+ writeln!(self.out, "{}}}", "\t".repeat(indent))?
+ }
+ Statement::Loop { body, continuing } => {
+ writeln!(self.out, "while(true) {{")?;
+
+ for sta in body.iter().chain(continuing.iter()) {
+ self.write_stmt(sta, ctx, indent + 1)?;
+ }
+
+ writeln!(self.out, "{}}}", "\t".repeat(indent))?
+ }
+ Statement::Break => writeln!(self.out, "break;")?,
+ Statement::Continue => writeln!(self.out, "continue;")?,
+ Statement::Return { value } => {
+ write!(self.out, "return")?;
+ if let Some(expr) = value {
+ write!(self.out, " ")?;
+ self.write_expr(*expr, ctx)?;
+ }
+ writeln!(self.out, ";")?;
+ }
+ Statement::Kill => writeln!(self.out, "discard;")?,
+ Statement::Store { pointer, value } => {
+ self.write_expr(*pointer, ctx)?;
+ write!(self.out, " = ")?;
+ self.write_expr(*value, ctx)?;
+ writeln!(self.out, ";")?
+ }
+ }
+
+ Ok(())
+ }
+
+ fn write_expr(
+ &mut self,
+ expr: Handle<Expression>,
+ ctx: &FunctionCtx<'_, '_>,
+ ) -> Result<(), Error> {
+ match ctx.expressions[expr] {
+ Expression::Access { base, index } => {
+ self.write_expr(base, ctx)?;
+ write!(self.out, "[")?;
+ self.write_expr(index, ctx)?;
+ write!(self.out, "]")?
+ }
+ Expression::AccessIndex { base, index } => {
+ self.write_expr(base, ctx)?;
+
+ match ctx.typifier.get(base, &self.module.types) {
+ TypeInner::Vector { .. }
+ | TypeInner::Matrix { .. }
+ | TypeInner::Array { .. } => write!(self.out, "[{}]", index)?,
+ TypeInner::Struct { .. } => {
+ let ty = ctx.typifier.get_handle(base).unwrap();
+
+ write!(
+ self.out,
+ ".{}",
+ &self.names[&NameKey::StructMember(ty, index)]
+ )?
+ }
+ ref other => return Err(Error::Custom(format!("Cannot index {:?}", other))),
+ }
+ }
+ Expression::Constant(constant) => {
+ self.write_constant(&self.module.constants[constant])?
+ }
+ Expression::Compose { ty, ref components } => {
+ match self.module.types[ty].inner {
+ TypeInner::Vector { .. }
+ | TypeInner::Matrix { .. }
+ | TypeInner::Array { .. }
+ | TypeInner::Struct { .. } => self.write_type(ty, None)?,
+ _ => unreachable!(),
+ }
+
+ write!(self.out, "(")?;
+ self.write_slice(components, |this, _, arg| this.write_expr(*arg, ctx))?;
+ write!(self.out, ")")?
+ }
+ Expression::FunctionArgument(pos) => {
+ write!(self.out, "{}", ctx.get_arg(pos, &self.names))?
+ }
+ Expression::GlobalVariable(handle) => {
+ if let Some(crate::Binding::BuiltIn(built_in)) =
+ self.module.global_variables[handle].binding
+ {
+ write!(self.out, "{}", glsl_built_in(built_in))?
+ } else {
+ write!(
+ self.out,
+ "{}",
+ &self.names[&NameKey::GlobalVariable(handle)]
+ )?
+ }
+ }
+ Expression::LocalVariable(handle) => {
+ write!(self.out, "{}", self.names[&ctx.name_key(handle)])?
+ }
+ Expression::Load { pointer } => self.write_expr(pointer, ctx)?,
+ Expression::ImageSample {
+ image,
+ coordinate,
+ level,
+ depth_ref,
+ ..
+ } => {
+ //TODO: handle MS
+ write!(
+ self.out,
+ "{}(",
+ match level {
+ crate::SampleLevel::Auto | crate::SampleLevel::Bias(_) => "texture",
+ crate::SampleLevel::Zero | crate::SampleLevel::Exact(_) => "textureLod",
+ }
+ )?;
+ self.write_expr(image, ctx)?;
+ write!(self.out, ", ")?;
+
+ let size = match *ctx.typifier.get(coordinate, &self.module.types) {
+ TypeInner::Vector { size, .. } => size,
+ ref other => {
+ return Err(Error::Custom(format!(
+ "Cannot sample with coordinates of type {:?}",
+ other
+ )))
+ }
+ };
+
+ if let Some(depth_ref) = depth_ref {
+ write!(self.out, "vec{}(", size as u8 + 1)?;
+ self.write_expr(coordinate, ctx)?;
+ write!(self.out, ", ")?;
+ self.write_expr(depth_ref, ctx)?;
+ write!(self.out, ")")?
+ } else {
+ self.write_expr(coordinate, ctx)?
+ }
+
+ match level {
+ crate::SampleLevel::Auto => (),
+ crate::SampleLevel::Zero => write!(self.out, ", 0")?,
+ crate::SampleLevel::Exact(expr) | crate::SampleLevel::Bias(expr) => {
+ write!(self.out, ", ")?;
+ self.write_expr(expr, ctx)?;
+ }
+ }
+
+ write!(self.out, ")")?
+ }
+ Expression::ImageLoad {
+ image,
+ coordinate,
+ index,
+ } => {
+ let class = match ctx.typifier.get(image, &self.module.types) {
+ TypeInner::Image { class, .. } => class,
+ _ => unreachable!(),
+ };
+
+ match class {
+ ImageClass::Sampled { .. } => write!(self.out, "texelFetch(")?,
+ ImageClass::Storage(_) => write!(self.out, "imageLoad(")?,
+ ImageClass::Depth => todo!(),
+ }
+
+ self.write_expr(image, ctx)?;
+ write!(self.out, ", ")?;
+ self.write_expr(coordinate, ctx)?;
+
+ match class {
+ ImageClass::Sampled { .. } => {
+ write!(self.out, ", ")?;
+ self.write_expr(index.unwrap(), ctx)?;
+ write!(self.out, ")")?
+ }
+ ImageClass::Storage(_) => write!(self.out, ")")?,
+ ImageClass::Depth => todo!(),
+ }
+ }
+ Expression::Unary { op, expr } => {
+ write!(
+ self.out,
+ "({} ",
+ match op {
+ UnaryOperator::Negate => "-",
+ UnaryOperator::Not => match *ctx.typifier.get(expr, &self.module.types) {
+ TypeInner::Scalar {
+ kind: ScalarKind::Sint,
+ ..
+ } => "~",
+ TypeInner::Scalar {
+ kind: ScalarKind::Uint,
+ ..
+ } => "~",
+ TypeInner::Scalar {
+ kind: ScalarKind::Bool,
+ ..
+ } => "!",
+ ref other =>
+ return Err(Error::Custom(format!(
+ "Cannot apply not to type {:?}",
+ other
+ ))),
+ },
+ }
+ )?;
+
+ self.write_expr(expr, ctx)?;
+
+ write!(self.out, ")")?
+ }
+ Expression::Binary { op, left, right } => {
+ write!(self.out, "(")?;
+ self.write_expr(left, ctx)?;
+
+ write!(
+ self.out,
+ " {} ",
+ match op {
+ BinaryOperator::Add => "+",
+ BinaryOperator::Subtract => "-",
+ BinaryOperator::Multiply => "*",
+ BinaryOperator::Divide => "/",
+ BinaryOperator::Modulo => "%",
+ BinaryOperator::Equal => "==",
+ BinaryOperator::NotEqual => "!=",
+ BinaryOperator::Less => "<",
+ BinaryOperator::LessEqual => "<=",
+ BinaryOperator::Greater => ">",
+ BinaryOperator::GreaterEqual => ">=",
+ BinaryOperator::And => "&",
+ BinaryOperator::ExclusiveOr => "^",
+ BinaryOperator::InclusiveOr => "|",
+ BinaryOperator::LogicalAnd => "&&",
+ BinaryOperator::LogicalOr => "||",
+ BinaryOperator::ShiftLeft => "<<",
+ BinaryOperator::ShiftRight => ">>",
+ }
+ )?;
+
+ self.write_expr(right, ctx)?;
+
+ write!(self.out, ")")?
+ }
+ Expression::Select {
+ condition,
+ accept,
+ reject,
+ } => {
+ write!(self.out, "(")?;
+ self.write_expr(condition, ctx)?;
+ write!(self.out, " ? ")?;
+ self.write_expr(accept, ctx)?;
+ write!(self.out, " : ")?;
+ self.write_expr(reject, ctx)?;
+ write!(self.out, ")")?
+ }
+ Expression::Intrinsic { fun, argument } => {
+ write!(
+ self.out,
+ "{}(",
+ match fun {
+ IntrinsicFunction::IsFinite => "!isinf",
+ IntrinsicFunction::IsInf => "isinf",
+ IntrinsicFunction::IsNan => "isnan",
+ IntrinsicFunction::IsNormal => "!isnan",
+ IntrinsicFunction::All => "all",
+ IntrinsicFunction::Any => "any",
+ }
+ )?;
+
+ self.write_expr(argument, ctx)?;
+
+ write!(self.out, ")")?
+ }
+ Expression::Transpose(matrix) => {
+ write!(self.out, "transpose(")?;
+ self.write_expr(matrix, ctx)?;
+ write!(self.out, ")")?
+ }
+ Expression::DotProduct(left, right) => {
+ write!(self.out, "dot(")?;
+ self.write_expr(left, ctx)?;
+ write!(self.out, ", ")?;
+ self.write_expr(right, ctx)?;
+ write!(self.out, ")")?
+ }
+ Expression::CrossProduct(left, right) => {
+ write!(self.out, "cross(")?;
+ self.write_expr(left, ctx)?;
+ write!(self.out, ", ")?;
+ self.write_expr(right, ctx)?;
+ write!(self.out, ")")?
+ }
+ Expression::As {
+ expr,
+ kind,
+ convert,
+ } => {
+ if convert {
+ self.write_type(ctx.typifier.get_handle(expr).unwrap(), None)?;
+ } else {
+ let source_kind = match *ctx.typifier.get(expr, &self.module.types) {
+ TypeInner::Scalar {
+ kind: source_kind, ..
+ } => source_kind,
+ TypeInner::Vector {
+ kind: source_kind, ..
+ } => source_kind,
+ _ => unreachable!(),
+ };
+
+ write!(
+ self.out,
+ "{}",
+ match (source_kind, kind) {
+ (ScalarKind::Float, ScalarKind::Sint) => "floatBitsToInt",
+ (ScalarKind::Float, ScalarKind::Uint) => "floatBitsToUInt",
+ (ScalarKind::Sint, ScalarKind::Float) => "intBitsToFloat",
+ (ScalarKind::Uint, ScalarKind::Float) => "uintBitsToFloat",
+ _ => {
+ return Err(Error::Custom(format!(
+ "Cannot bitcast {:?} to {:?}",
+ source_kind, kind
+ )));
+ }
+ }
+ )?;
+ }
+
+ write!(self.out, "(")?;
+ self.write_expr(expr, ctx)?;
+ write!(self.out, ")")?
+ }
+ Expression::Derivative { axis, expr } => {
+ write!(
+ self.out,
+ "{}(",
+ match axis {
+ DerivativeAxis::X => "dFdx",
+ DerivativeAxis::Y => "dFdy",
+ DerivativeAxis::Width => "fwidth",
+ }
+ )?;
+ self.write_expr(expr, ctx)?;
+ write!(self.out, ")")?
+ }
+ Expression::Call {
+ origin: FunctionOrigin::Local(ref function),
+ ref arguments,
+ } => {
+ write!(self.out, "{}(", &self.names[&NameKey::Function(*function)])?;
+ self.write_slice(arguments, |this, _, arg| this.write_expr(*arg, ctx))?;
+ write!(self.out, ")")?
+ }
+ Expression::Call {
+ origin: crate::FunctionOrigin::External(ref name),
+ ref arguments,
+ } => match name.as_str() {
+ "cos" | "normalize" | "sin" | "length" | "abs" | "floor" | "inverse"
+ | "distance" | "dot" | "min" | "max" | "reflect" | "pow" | "step" | "cross"
+ | "fclamp" | "clamp" | "mix" | "smoothstep" => {
+ let name = match name.as_str() {
+ "fclamp" => "clamp",
+ name => name,
+ };
+
+ write!(self.out, "{}(", name)?;
+ self.write_slice(arguments, |this, _, arg| this.write_expr(*arg, ctx))?;
+ write!(self.out, ")")?
+ }
+ "atan2" => {
+ write!(self.out, "atan(")?;
+ self.write_expr(arguments[1], ctx)?;
+ write!(self.out, ", ")?;
+ self.write_expr(arguments[0], ctx)?;
+ write!(self.out, ")")?
+ }
+ other => {
+ return Err(Error::Custom(format!(
+ "Unsupported function call {}",
+ other
+ )))
+ }
+ },
+ Expression::ArrayLength(expr) => {
+ write!(self.out, "uint(")?;
+ self.write_expr(expr, ctx)?;
+ write!(self.out, ".length())")?
+ }
+ }
+
+ Ok(())
+ }
+
+ fn write_constant(&mut self, constant: &Constant) -> Result<(), Error> {
+ match constant.inner {
+ ConstantInner::Sint(int) => write!(self.out, "{}", int)?,
+ ConstantInner::Uint(int) => write!(self.out, "{}u", int)?,
+ ConstantInner::Float(float) => write!(self.out, "{:?}", float)?,
+ ConstantInner::Bool(boolean) => write!(self.out, "{}", boolean)?,
+ ConstantInner::Composite(ref components) => {
+ self.write_type(constant.ty, None)?;
+ write!(self.out, "(")?;
+ self.write_slice(components, |this, _, arg| {
+ this.write_constant(&this.module.constants[*arg])
+ })?;
+ write!(self.out, ")")?
+ }
+ }
+
+ Ok(())
+ }
+}
+
+struct ScalarString<'a> {
+ prefix: &'a str,
+ full: &'a str,
+}
+
+fn glsl_scalar(kind: ScalarKind, width: crate::Bytes) -> Result<ScalarString<'static>, Error> {
+ Ok(match kind {
+ ScalarKind::Sint => ScalarString {
+ prefix: "i",
+ full: "int",
+ },
+ ScalarKind::Uint => ScalarString {
+ prefix: "u",
+ full: "uint",
+ },
+ ScalarKind::Float => match width {
+ 4 => ScalarString {
+ prefix: "",
+ full: "float",
+ },
+ 8 => ScalarString {
+ prefix: "d",
+ full: "double",
+ },
+ _ => {
+ return Err(Error::Custom(format!(
+ "Cannot build float of width {}",
+ width
+ )))
+ }
+ },
+ ScalarKind::Bool => ScalarString {
+ prefix: "b",
+ full: "bool",
+ },
+ })
+}
+
+fn glsl_built_in(built_in: BuiltIn) -> &'static str {
+ match built_in {
+ BuiltIn::Position => "gl_Position",
+ BuiltIn::GlobalInvocationId => "gl_GlobalInvocationID",
+ BuiltIn::BaseInstance => "gl_BaseInstance",
+ BuiltIn::BaseVertex => "gl_BaseVertex",
+ BuiltIn::ClipDistance => "gl_ClipDistance",
+ BuiltIn::InstanceIndex => "gl_InstanceIndex",
+ BuiltIn::VertexIndex => "gl_VertexIndex",
+ BuiltIn::PointSize => "gl_PointSize",
+ BuiltIn::FragCoord => "gl_FragCoord",
+ BuiltIn::FrontFacing => "gl_FrontFacing",
+ BuiltIn::SampleIndex => "gl_SampleID",
+ BuiltIn::FragDepth => "gl_FragDepth",
+ BuiltIn::LocalInvocationId => "gl_LocalInvocationID",
+ BuiltIn::LocalInvocationIndex => "gl_LocalInvocationIndex",
+ BuiltIn::WorkGroupId => "gl_WorkGroupID",
+ }
+}
+
+fn glsl_storage_class(class: StorageClass) -> &'static str {
+ match class {
+ StorageClass::Function => "",
+ StorageClass::Input => "in",
+ StorageClass::Output => "out",
+ StorageClass::Private => "",
+ StorageClass::Storage => "buffer",
+ StorageClass::Uniform => "uniform",
+ StorageClass::Handle => "uniform",
+ StorageClass::WorkGroup => "shared",
+ StorageClass::PushConstant => "",
+ }
+}
+
+fn glsl_interpolation(interpolation: Interpolation) -> Result<&'static str, Error> {
+ Ok(match interpolation {
+ Interpolation::Perspective => "smooth",
+ Interpolation::Linear => "noperspective",
+ Interpolation::Flat => "flat",
+ Interpolation::Centroid => "centroid",
+ Interpolation::Sample => "sample",
+ Interpolation::Patch => {
+ return Err(Error::Custom(
+ "patch interpolation qualifier not supported".to_string(),
+ ))
+ }
+ })
+}
+
+struct ImageDimension(crate::ImageDimension);
+impl fmt::Display for ImageDimension {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "{}",
+ match self.0 {
+ crate::ImageDimension::D1 => "1D",
+ crate::ImageDimension::D2 => "2D",
+ crate::ImageDimension::D3 => "3D",
+ crate::ImageDimension::Cube => "Cube",
+ }
+ )
+ }
+}
+
+fn glsl_storage_format(format: StorageFormat) -> &'static str {
+ match format {
+ StorageFormat::R8Unorm => "r8",
+ StorageFormat::R8Snorm => "r8_snorm",
+ StorageFormat::R8Uint => "r8ui",
+ StorageFormat::R8Sint => "r8i",
+ StorageFormat::R16Uint => "r16ui",
+ StorageFormat::R16Sint => "r16i",
+ StorageFormat::R16Float => "r16f",
+ StorageFormat::Rg8Unorm => "rg8",
+ StorageFormat::Rg8Snorm => "rg8_snorm",
+ StorageFormat::Rg8Uint => "rg8ui",
+ StorageFormat::Rg8Sint => "rg8i",
+ StorageFormat::R32Uint => "r32ui",
+ StorageFormat::R32Sint => "r32i",
+ StorageFormat::R32Float => "r32f",
+ StorageFormat::Rg16Uint => "rg16ui",
+ StorageFormat::Rg16Sint => "rg16i",
+ StorageFormat::Rg16Float => "rg16f",
+ StorageFormat::Rgba8Unorm => "rgba8ui",
+ StorageFormat::Rgba8Snorm => "rgba8_snorm",
+ StorageFormat::Rgba8Uint => "rgba8ui",
+ StorageFormat::Rgba8Sint => "rgba8i",
+ StorageFormat::Rgb10a2Unorm => "rgb10_a2ui",
+ StorageFormat::Rg11b10Float => "r11f_g11f_b10f",
+ StorageFormat::Rg32Uint => "rg32ui",
+ StorageFormat::Rg32Sint => "rg32i",
+ StorageFormat::Rg32Float => "rg32f",
+ StorageFormat::Rgba16Uint => "rgba16ui",
+ StorageFormat::Rgba16Sint => "rgba16i",
+ StorageFormat::Rgba16Float => "rgba16f",
+ StorageFormat::Rgba32Uint => "rgba32ui",
+ StorageFormat::Rgba32Sint => "rgba32i",
+ StorageFormat::Rgba32Float => "rgba32f",
+ }
+}
+
+struct TextureMappingVisitor<'a> {
+ names: &'a FastHashMap<NameKey, String>,
+ expressions: &'a Arena<Expression>,
+ map: &'a mut FastHashMap<String, TextureMapping>,
+ error: Option<Error>,
+}
+
+impl<'a> Visitor for TextureMappingVisitor<'a> {
+ fn visit_expr(&mut self, expr: &crate::Expression) {
+ match expr {
+ Expression::ImageSample { image, sampler, .. } => {
+ let tex_handle = match self.expressions[*image] {
+ Expression::GlobalVariable(global) => global,
+ _ => unreachable!(),
+ };
+ let tex_name = self.names[&NameKey::GlobalVariable(tex_handle)].clone();
+
+ let sampler_handle = match self.expressions[*sampler] {
+ Expression::GlobalVariable(global) => global,
+ _ => unreachable!(),
+ };
+
+ let mapping = self.map.entry(tex_name).or_insert(TextureMapping {
+ texture: tex_handle,
+ sampler: Some(sampler_handle),
+ });
+
+ if mapping.sampler != Some(sampler_handle) {
+ self.error = Some(Error::Custom(String::from(
+ "Cannot use texture with two different samplers",
+ )));
+ }
+ }
+ Expression::ImageLoad { image, .. } => {
+ let tex_handle = match self.expressions[*image] {
+ Expression::GlobalVariable(global) => global,
+ _ => unreachable!(),
+ };
+ let tex_name = self.names[&NameKey::GlobalVariable(tex_handle)].clone();
+
+ let mapping = self.map.entry(tex_name).or_insert(TextureMapping {
+ texture: tex_handle,
+ sampler: None,
+ });
+
+ if mapping.sampler != None {
+ self.error = Some(Error::Custom(String::from(
+ "Cannot use texture with two different samplers",
+ )));
+ }
+ }
+ _ => {}
+ }
+ }
+}
diff --git a/third_party/rust/naga/src/back/glsl/keywords.rs b/third_party/rust/naga/src/back/glsl/keywords.rs
new file mode 100644
index 0000000000..5a2836c189
--- /dev/null
+++ b/third_party/rust/naga/src/back/glsl/keywords.rs
@@ -0,0 +1,204 @@
+pub const RESERVED_KEYWORDS: &[&str] = &[
+ "attribute",
+ "const",
+ "uniform",
+ "varying",
+ "buffer",
+ "shared",
+ "coherent",
+ "volatile",
+ "restrict",
+ "readonly",
+ "writeonly",
+ "atomic_uint",
+ "layout",
+ "centroid",
+ "flat",
+ "smooth",
+ "noperspective",
+ "patch",
+ "sample",
+ "break",
+ "continue",
+ "do",
+ "for",
+ "while",
+ "switch",
+ "case",
+ "default",
+ "if",
+ "else",
+ "subroutine",
+ "in",
+ "out",
+ "inout",
+ "float",
+ "double",
+ "int",
+ "void",
+ "bool",
+ "true",
+ "false",
+ "invariant",
+ "precise",
+ "discard",
+ "return",
+ "mat2",
+ "mat3",
+ "mat4",
+ "dmat2",
+ "dmat3",
+ "dmat4",
+ "mat2x2",
+ "mat2x3",
+ "mat2x4",
+ "dmat2x2",
+ "dmat2x3",
+ "dmat2x4",
+ "mat3x2",
+ "mat3x3",
+ "mat3x4",
+ "dmat3x2",
+ "dmat3x3",
+ "dmat3x4",
+ "mat4x2",
+ "mat4x3",
+ "mat4x4",
+ "dmat4x2",
+ "dmat4x3",
+ "dmat4x4",
+ "vec2",
+ "vec3",
+ "vec4",
+ "ivec2",
+ "ivec3",
+ "ivec4",
+ "bvec2",
+ "bvec3",
+ "bvec4",
+ "dvec2",
+ "dvec3",
+ "dvec4",
+ "uint",
+ "uvec2",
+ "uvec3",
+ "uvec4",
+ "lowp",
+ "mediump",
+ "highp",
+ "precision",
+ "sampler1D",
+ "sampler2D",
+ "sampler3D",
+ "samplerCube",
+ "sampler1DShadow",
+ "sampler2DShadow",
+ "samplerCubeShadow",
+ "sampler1DArray",
+ "sampler2DArray",
+ "sampler1DArrayShadow",
+ "sampler2DArrayShadow",
+ "isampler1D",
+ "isampler2D",
+ "isampler3D",
+ "isamplerCube",
+ "isampler1DArray",
+ "isampler2DArray",
+ "usampler1D",
+ "usampler2D",
+ "usampler3D",
+ "usamplerCube",
+ "usampler1DArray",
+ "usampler2DArray",
+ "sampler2DRect",
+ "sampler2DRectShadow",
+ "isampler2D",
+ "Rect",
+ "usampler2DRect",
+ "samplerBuffer",
+ "isamplerBuffer",
+ "usamplerBuffer",
+ "sampler2DMS",
+ "isampler2DMS",
+ "usampler2DMS",
+ "sampler2DMSArray",
+ "isampler2DMSArray",
+ "usampler2DMSArray",
+ "samplerCubeArray",
+ "samplerCubeArrayShadow",
+ "isamplerCubeArray",
+ "usamplerCubeArray",
+ "image1D",
+ "iimage1D",
+ "uimage1D",
+ "image2D",
+ "iimage2D",
+ "uimage2D",
+ "image3D",
+ "iimage3D",
+ "uimage3D",
+ "image2DRect",
+ "iimage2DRect",
+ "uimage2DRect",
+ "imageCube",
+ "iimageCube",
+ "uimageCube",
+ "imageBuffer",
+ "iimageBuffer",
+ "uimageBuffer",
+ "image1DArray",
+ "iimage1DArray",
+ "uimage1DArray",
+ "image2DArray",
+ "iimage2DArray",
+ "uimage2DArray",
+ "imageCubeArray",
+ "iimageCubeArray",
+ "uimageCubeArray",
+ "image2DMS",
+ "iimage2DMS",
+ "uimage2DMS",
+ "image2DMSArray",
+ "iimage2DMSArray",
+ "uimage2DMSArraystruct",
+ "common",
+ "partition",
+ "active",
+ "asm",
+ "class",
+ "union",
+ "enum",
+ "typedef",
+ "template",
+ "this",
+ "resource",
+ "goto",
+ "inline",
+ "noinline",
+ "public",
+ "static",
+ "extern",
+ "external",
+ "interface",
+ "long",
+ "short",
+ "half",
+ "fixed",
+ "unsigned",
+ "superp",
+ "input",
+ "output",
+ "hvec2",
+ "hvec3",
+ "hvec4",
+ "fvec2",
+ "fvec3",
+ "fvec4",
+ "sampler3DRect",
+ "filter",
+ "sizeof",
+ "cast",
+ "namespace",
+ "using",
+ "main",
+];
diff --git a/third_party/rust/naga/src/back/mod.rs b/third_party/rust/naga/src/back/mod.rs
new file mode 100644
index 0000000000..bc96dd3496
--- /dev/null
+++ b/third_party/rust/naga/src/back/mod.rs
@@ -0,0 +1,8 @@
+//! Functions which export shader modules into binary and text formats.
+
+#[cfg(feature = "glsl-out")]
+pub mod glsl;
+#[cfg(feature = "msl-out")]
+pub mod msl;
+#[cfg(feature = "spv-out")]
+pub mod spv;
diff --git a/third_party/rust/naga/src/back/msl/keywords.rs b/third_party/rust/naga/src/back/msl/keywords.rs
new file mode 100644
index 0000000000..cd074ab43f
--- /dev/null
+++ b/third_party/rust/naga/src/back/msl/keywords.rs
@@ -0,0 +1,102 @@
+//TODO: find a complete list
+pub const RESERVED: &[&str] = &[
+ // control flow
+ "break",
+ "if",
+ "else",
+ "continue",
+ "goto",
+ "do",
+ "while",
+ "for",
+ "switch",
+ "case",
+ // types and values
+ "void",
+ "unsigned",
+ "signed",
+ "bool",
+ "char",
+ "int",
+ "long",
+ "float",
+ "double",
+ "char8_t",
+ "wchar_t",
+ "true",
+ "false",
+ "nullptr",
+ "union",
+ "class",
+ "struct",
+ "enum",
+ // other
+ "main",
+ "using",
+ "decltype",
+ "sizeof",
+ "typeof",
+ "typedef",
+ "explicit",
+ "export",
+ "friend",
+ "namespace",
+ "operator",
+ "public",
+ "template",
+ "typename",
+ "typeid",
+ "co_await",
+ "co_return",
+ "co_yield",
+ "module",
+ "import",
+ "ray_data",
+ "vec_step",
+ "visible",
+ "as_type",
+ // qualifiers
+ "mutable",
+ "static",
+ "volatile",
+ "restrict",
+ "const",
+ "non-temporal",
+ "dereferenceable",
+ "invariant",
+ // exceptions
+ "throw",
+ "try",
+ "catch",
+ // operators
+ "const_cast",
+ "dynamic_cast",
+ "reinterpret_cast",
+ "static_cast",
+ "new",
+ "delete",
+ "and",
+ "and_eq",
+ "bitand",
+ "bitor",
+ "compl",
+ "not",
+ "not_eq",
+ "or",
+ "or_eq",
+ "xor",
+ "xor_eq",
+ "compl",
+ // Metal-specific
+ "constant",
+ "device",
+ "threadgroup",
+ "threadgroup_imageblock",
+ "kernel",
+ "compute",
+ "vertex",
+ "fragment",
+ "read_only",
+ "write_only",
+ "read_write",
+];
diff --git a/third_party/rust/naga/src/back/msl/mod.rs b/third_party/rust/naga/src/back/msl/mod.rs
new file mode 100644
index 0000000000..493e7d0c85
--- /dev/null
+++ b/third_party/rust/naga/src/back/msl/mod.rs
@@ -0,0 +1,211 @@
+/*! Metal Shading Language (MSL) backend
+
+## Binding model
+
+Metal's bindings are flat per resource. Since there isn't an obvious mapping
+from SPIR-V's descriptor sets, we require a separate mapping provided in the options.
+This mapping may have one or more resource end points for each descriptor set + index
+pair.
+
+## Outputs
+
+In Metal, built-in shader outputs can not be nested into structures within
+the output struct. If there is a structure in the outputs, and it contains any built-ins,
+we move them up to the root output structure that we define ourselves.
+!*/
+
+use crate::{arena::Handle, proc::ResolveError, FastHashMap};
+use std::{
+ io::{Error as IoError, Write},
+ string::FromUtf8Error,
+};
+
+mod keywords;
+mod writer;
+
+pub use writer::Writer;
+
+#[derive(Clone, Debug, Default, PartialEq)]
+pub struct BindTarget {
+ pub buffer: Option<u8>,
+ pub texture: Option<u8>,
+ pub sampler: Option<u8>,
+ pub mutable: bool,
+}
+
+#[derive(Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
+pub struct BindSource {
+ pub stage: crate::ShaderStage,
+ pub group: u32,
+ pub binding: u32,
+}
+
+pub type BindingMap = FastHashMap<BindSource, BindTarget>;
+
+enum ResolvedBinding {
+ BuiltIn(crate::BuiltIn),
+ Attribute(u32),
+ Color(u32),
+ User { prefix: &'static str, index: u32 },
+ Resource(BindTarget),
+}
+
+// Note: some of these should be removed in favor of proper IR validation.
+
+#[derive(Debug)]
+pub enum Error {
+ IO(IoError),
+ Utf8(FromUtf8Error),
+ Type(ResolveError),
+ UnexpectedLocation,
+ MissingBinding(Handle<crate::GlobalVariable>),
+ MissingBindTarget(BindSource),
+ InvalidImageAccess(crate::StorageAccess),
+ MutabilityViolation(Handle<crate::GlobalVariable>),
+ BadName(String),
+ UnexpectedGlobalType(Handle<crate::Type>),
+ UnimplementedBindTarget(BindTarget),
+ UnsupportedCompose(Handle<crate::Type>),
+ UnsupportedBinaryOp(crate::BinaryOperator),
+ UnexpectedSampleLevel(crate::SampleLevel),
+ UnsupportedCall(String),
+ UnsupportedDynamicArrayLength,
+ UnableToReturnValue(Handle<crate::Expression>),
+ /// The source IR is not valid.
+ Validation,
+}
+
+impl From<IoError> for Error {
+ fn from(e: IoError) -> Self {
+ Error::IO(e)
+ }
+}
+
+impl From<FromUtf8Error> for Error {
+ fn from(e: FromUtf8Error) -> Self {
+ Error::Utf8(e)
+ }
+}
+
+impl From<ResolveError> for Error {
+ fn from(e: ResolveError) -> Self {
+ Error::Type(e)
+ }
+}
+
+#[derive(Clone, Copy, Debug)]
+enum LocationMode {
+ VertexInput,
+ FragmentOutput,
+ Intermediate,
+ Uniform,
+}
+
+#[derive(Debug, Default, Clone)]
+pub struct Options {
+ /// (Major, Minor) target version of the Metal Shading Language.
+ pub lang_version: (u8, u8),
+ /// Make it possible to link different stages via SPIRV-Cross.
+ pub spirv_cross_compatibility: bool,
+ /// Binding model mapping to Metal.
+ pub binding_map: BindingMap,
+}
+
+impl Options {
+ fn resolve_binding(
+ &self,
+ stage: crate::ShaderStage,
+ binding: &crate::Binding,
+ mode: LocationMode,
+ ) -> Result<ResolvedBinding, Error> {
+ match *binding {
+ crate::Binding::BuiltIn(built_in) => Ok(ResolvedBinding::BuiltIn(built_in)),
+ crate::Binding::Location(index) => match mode {
+ LocationMode::VertexInput => Ok(ResolvedBinding::Attribute(index)),
+ LocationMode::FragmentOutput => Ok(ResolvedBinding::Color(index)),
+ LocationMode::Intermediate => Ok(ResolvedBinding::User {
+ prefix: if self.spirv_cross_compatibility {
+ "locn"
+ } else {
+ "loc"
+ },
+ index,
+ }),
+ LocationMode::Uniform => Err(Error::UnexpectedLocation),
+ },
+ crate::Binding::Resource { group, binding } => {
+ let source = BindSource {
+ stage,
+ group,
+ binding,
+ };
+ self.binding_map
+ .get(&source)
+ .cloned()
+ .map(ResolvedBinding::Resource)
+ .ok_or(Error::MissingBindTarget(source))
+ }
+ }
+ }
+}
+
+impl ResolvedBinding {
+ fn try_fmt<W: Write>(&self, out: &mut W) -> Result<(), Error> {
+ match *self {
+ ResolvedBinding::BuiltIn(built_in) => {
+ use crate::BuiltIn as Bi;
+ let name = match built_in {
+ // vertex
+ Bi::BaseInstance => "base_instance",
+ Bi::BaseVertex => "base_vertex",
+ Bi::ClipDistance => "clip_distance",
+ Bi::InstanceIndex => "instance_id",
+ Bi::PointSize => "point_size",
+ Bi::Position => "position",
+ Bi::VertexIndex => "vertex_id",
+ // fragment
+ Bi::FragCoord => "position",
+ Bi::FragDepth => "depth(any)",
+ Bi::FrontFacing => "front_facing",
+ Bi::SampleIndex => "sample_id",
+ // compute
+ Bi::GlobalInvocationId => "thread_position_in_grid",
+ Bi::LocalInvocationId => "thread_position_in_threadgroup",
+ Bi::LocalInvocationIndex => "thread_index_in_threadgroup",
+ Bi::WorkGroupId => "threadgroup_position_in_grid",
+ };
+ Ok(write!(out, "{}", name)?)
+ }
+ ResolvedBinding::Attribute(index) => Ok(write!(out, "attribute({})", index)?),
+ ResolvedBinding::Color(index) => Ok(write!(out, "color({})", index)?),
+ ResolvedBinding::User { prefix, index } => {
+ Ok(write!(out, "user({}{})", prefix, index)?)
+ }
+ ResolvedBinding::Resource(ref target) => {
+ if let Some(id) = target.buffer {
+ Ok(write!(out, "buffer({})", id)?)
+ } else if let Some(id) = target.texture {
+ Ok(write!(out, "texture({})", id)?)
+ } else if let Some(id) = target.sampler {
+ Ok(write!(out, "sampler({})", id)?)
+ } else {
+ Err(Error::UnimplementedBindTarget(target.clone()))
+ }
+ }
+ }
+ }
+
+ fn try_fmt_decorated<W: Write>(&self, out: &mut W, terminator: &str) -> Result<(), Error> {
+ write!(out, " [[")?;
+ self.try_fmt(out)?;
+ write!(out, "]]")?;
+ write!(out, "{}", terminator)?;
+ Ok(())
+ }
+}
+
+pub fn write_string(module: &crate::Module, options: &Options) -> Result<String, Error> {
+ let mut w = writer::Writer::new(Vec::new());
+ w.write(module, options)?;
+ Ok(String::from_utf8(w.finish())?)
+}
diff --git a/third_party/rust/naga/src/back/msl/writer.rs b/third_party/rust/naga/src/back/msl/writer.rs
new file mode 100644
index 0000000000..e2adbea6b7
--- /dev/null
+++ b/third_party/rust/naga/src/back/msl/writer.rs
@@ -0,0 +1,990 @@
+use super::{keywords::RESERVED, Error, LocationMode, Options, ResolvedBinding};
+use crate::{
+ arena::Handle,
+ proc::{EntryPointIndex, NameKey, Namer, ResolveContext, Typifier},
+ FastHashMap,
+};
+use std::{
+ fmt::{Display, Error as FmtError, Formatter},
+ io::Write,
+};
+
+struct Level(usize);
+impl Level {
+ fn next(&self) -> Self {
+ Level(self.0 + 1)
+ }
+}
+impl Display for Level {
+ fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), FmtError> {
+ (0..self.0).map(|_| formatter.write_str("\t")).collect()
+ }
+}
+
+struct TypedGlobalVariable<'a> {
+ module: &'a crate::Module,
+ names: &'a FastHashMap<NameKey, String>,
+ handle: Handle<crate::GlobalVariable>,
+ usage: crate::GlobalUse,
+}
+
+impl<'a> TypedGlobalVariable<'a> {
+ fn try_fmt<W: Write>(&self, out: &mut W) -> Result<(), Error> {
+ let var = &self.module.global_variables[self.handle];
+ let name = &self.names[&NameKey::GlobalVariable(self.handle)];
+ let ty = &self.module.types[var.ty];
+ let ty_name = &self.names[&NameKey::Type(var.ty)];
+
+ let (space_qualifier, reference) = match ty.inner {
+ crate::TypeInner::Struct { .. } => match var.class {
+ crate::StorageClass::Uniform | crate::StorageClass::Storage => {
+ let space = if self.usage.contains(crate::GlobalUse::STORE) {
+ "device "
+ } else {
+ "constant "
+ };
+ (space, "&")
+ }
+ _ => ("", ""),
+ },
+ _ => ("", ""),
+ };
+ Ok(write!(
+ out,
+ "{}{}{} {}",
+ space_qualifier, ty_name, reference, name
+ )?)
+ }
+}
+
+pub struct Writer<W> {
+ out: W,
+ names: FastHashMap<NameKey, String>,
+ typifier: Typifier,
+}
+
+fn scalar_kind_string(kind: crate::ScalarKind) -> &'static str {
+ match kind {
+ crate::ScalarKind::Float => "float",
+ crate::ScalarKind::Sint => "int",
+ crate::ScalarKind::Uint => "uint",
+ crate::ScalarKind::Bool => "bool",
+ }
+}
+
+fn vector_size_string(size: crate::VectorSize) -> &'static str {
+ match size {
+ crate::VectorSize::Bi => "2",
+ crate::VectorSize::Tri => "3",
+ crate::VectorSize::Quad => "4",
+ }
+}
+
+const OUTPUT_STRUCT_NAME: &str = "output";
+const LOCATION_INPUT_STRUCT_NAME: &str = "input";
+const COMPONENTS: &[char] = &['x', 'y', 'z', 'w'];
+
+fn separate(is_last: bool) -> &'static str {
+ if is_last {
+ ""
+ } else {
+ ","
+ }
+}
+
+enum FunctionOrigin {
+ Handle(Handle<crate::Function>),
+ EntryPoint(EntryPointIndex),
+}
+
+struct ExpressionContext<'a> {
+ function: &'a crate::Function,
+ origin: FunctionOrigin,
+ module: &'a crate::Module,
+}
+
+impl<W: Write> Writer<W> {
+ /// Creates a new `Writer` instance.
+ pub fn new(out: W) -> Self {
+ Writer {
+ out,
+ names: FastHashMap::default(),
+ typifier: Typifier::new(),
+ }
+ }
+
+ /// Finishes writing and returns the output.
+ pub fn finish(self) -> W {
+ self.out
+ }
+
+ fn put_call(
+ &mut self,
+ name: &str,
+ parameters: &[Handle<crate::Expression>],
+ context: &ExpressionContext,
+ ) -> Result<(), Error> {
+ if !name.is_empty() {
+ write!(self.out, "metal::{}", name)?;
+ }
+ write!(self.out, "(")?;
+ for (i, &handle) in parameters.iter().enumerate() {
+ if i != 0 {
+ write!(self.out, ", ")?;
+ }
+ self.put_expression(handle, context)?;
+ }
+ write!(self.out, ")")?;
+ Ok(())
+ }
+
+ fn put_expression(
+ &mut self,
+ expr_handle: Handle<crate::Expression>,
+ context: &ExpressionContext,
+ ) -> Result<(), Error> {
+ let expression = &context.function.expressions[expr_handle];
+ log::trace!("expression {:?} = {:?}", expr_handle, expression);
+ match *expression {
+ crate::Expression::Access { base, index } => {
+ self.put_expression(base, context)?;
+ write!(self.out, "[")?;
+ self.put_expression(index, context)?;
+ write!(self.out, "]")?;
+ }
+ crate::Expression::AccessIndex { base, index } => {
+ self.put_expression(base, context)?;
+ let resolved = self.typifier.get(base, &context.module.types);
+ match *resolved {
+ crate::TypeInner::Struct { .. } => {
+ let base_ty = self.typifier.get_handle(base).unwrap();
+ let name = &self.names[&NameKey::StructMember(base_ty, index)];
+ write!(self.out, ".{}", name)?;
+ }
+ crate::TypeInner::Matrix { .. } | crate::TypeInner::Vector { .. } => {
+ write!(self.out, ".{}", COMPONENTS[index as usize])?;
+ }
+ crate::TypeInner::Array { .. } => {
+ write!(self.out, "[{}]", index)?;
+ }
+ _ => {
+ // unexpected indexing, should fail validation
+ }
+ }
+ }
+ crate::Expression::Constant(handle) => self.put_constant(handle, context.module)?,
+ crate::Expression::Compose { ty, ref components } => {
+ let inner = &context.module.types[ty].inner;
+ match *inner {
+ crate::TypeInner::Vector { size, kind, .. } => {
+ write!(
+ self.out,
+ "{}{}",
+ scalar_kind_string(kind),
+ vector_size_string(size)
+ )?;
+ self.put_call("", components, context)?;
+ }
+ crate::TypeInner::Scalar { width: 4, kind } if components.len() == 1 => {
+ write!(self.out, "{}", scalar_kind_string(kind),)?;
+ self.put_call("", components, context)?;
+ }
+ _ => return Err(Error::UnsupportedCompose(ty)),
+ }
+ }
+ crate::Expression::FunctionArgument(index) => {
+ let fun_handle = match context.origin {
+ FunctionOrigin::Handle(handle) => handle,
+ FunctionOrigin::EntryPoint(_) => unreachable!(),
+ };
+ let name = &self.names[&NameKey::FunctionArgument(fun_handle, index)];
+ write!(self.out, "{}", name)?;
+ }
+ crate::Expression::GlobalVariable(handle) => {
+ let var = &context.module.global_variables[handle];
+ match var.class {
+ crate::StorageClass::Output => {
+ if let crate::TypeInner::Struct { .. } = context.module.types[var.ty].inner
+ {
+ return Ok(());
+ }
+ write!(self.out, "{}.", OUTPUT_STRUCT_NAME)?;
+ }
+ crate::StorageClass::Input => {
+ if let Some(crate::Binding::Location(_)) = var.binding {
+ write!(self.out, "{}.", LOCATION_INPUT_STRUCT_NAME)?;
+ }
+ }
+ _ => {}
+ }
+ let name = &self.names[&NameKey::GlobalVariable(handle)];
+ write!(self.out, "{}", name)?;
+ }
+ crate::Expression::LocalVariable(handle) => {
+ let name_key = match context.origin {
+ FunctionOrigin::Handle(fun_handle) => {
+ NameKey::FunctionLocal(fun_handle, handle)
+ }
+ FunctionOrigin::EntryPoint(ep_index) => {
+ NameKey::EntryPointLocal(ep_index, handle)
+ }
+ };
+ let name = &self.names[&name_key];
+ write!(self.out, "{}", name)?;
+ }
+ crate::Expression::Load { pointer } => {
+ //write!(self.out, "*")?;
+ self.put_expression(pointer, context)?;
+ }
+ crate::Expression::ImageSample {
+ image,
+ sampler,
+ coordinate,
+ level,
+ depth_ref,
+ } => {
+ let op = match depth_ref {
+ Some(_) => "sample_compare",
+ None => "sample",
+ };
+ //TODO: handle arrayed images
+ self.put_expression(image, context)?;
+ write!(self.out, ".{}(", op)?;
+ self.put_expression(sampler, context)?;
+ write!(self.out, ", ")?;
+ self.put_expression(coordinate, context)?;
+ if let Some(dref) = depth_ref {
+ write!(self.out, ", ")?;
+ self.put_expression(dref, context)?;
+ }
+ match level {
+ crate::SampleLevel::Auto => {}
+ crate::SampleLevel::Zero => {
+ write!(self.out, ", level(0)")?;
+ }
+ crate::SampleLevel::Exact(h) => {
+ write!(self.out, ", level(")?;
+ self.put_expression(h, context)?;
+ write!(self.out, ")")?;
+ }
+ crate::SampleLevel::Bias(h) => {
+ write!(self.out, ", bias(")?;
+ self.put_expression(h, context)?;
+ write!(self.out, ")")?;
+ }
+ }
+ write!(self.out, ")")?;
+ }
+ crate::Expression::ImageLoad {
+ image,
+ coordinate,
+ index,
+ } => {
+ //TODO: handle arrayed images
+ self.put_expression(image, context)?;
+ write!(self.out, ".read(")?;
+ self.put_expression(coordinate, context)?;
+ if let Some(index) = index {
+ write!(self.out, ", ")?;
+ self.put_expression(index, context)?;
+ }
+ write!(self.out, ")")?;
+ }
+ crate::Expression::Unary { op, expr } => {
+ let op_str = match op {
+ crate::UnaryOperator::Negate => "-",
+ crate::UnaryOperator::Not => "!",
+ };
+ write!(self.out, "{}", op_str)?;
+ self.put_expression(expr, context)?;
+ }
+ crate::Expression::Binary { op, left, right } => {
+ let op_str = match op {
+ crate::BinaryOperator::Add => "+",
+ crate::BinaryOperator::Subtract => "-",
+ crate::BinaryOperator::Multiply => "*",
+ crate::BinaryOperator::Divide => "/",
+ crate::BinaryOperator::Modulo => "%",
+ crate::BinaryOperator::Equal => "==",
+ crate::BinaryOperator::NotEqual => "!=",
+ crate::BinaryOperator::Less => "<",
+ crate::BinaryOperator::LessEqual => "<=",
+ crate::BinaryOperator::Greater => "==",
+ crate::BinaryOperator::GreaterEqual => ">=",
+ crate::BinaryOperator::And => "&",
+ _ => return Err(Error::UnsupportedBinaryOp(op)),
+ };
+ let kind = self
+ .typifier
+ .get(left, &context.module.types)
+ .scalar_kind()
+ .ok_or(Error::UnsupportedBinaryOp(op))?;
+ if op == crate::BinaryOperator::Modulo && kind == crate::ScalarKind::Float {
+ write!(self.out, "fmod(")?;
+ self.put_expression(left, context)?;
+ write!(self.out, ", ")?;
+ self.put_expression(right, context)?;
+ write!(self.out, ")")?;
+ } else {
+ //write!(self.out, "(")?;
+ self.put_expression(left, context)?;
+ write!(self.out, " {} ", op_str)?;
+ self.put_expression(right, context)?;
+ //write!(self.out, ")")?;
+ }
+ }
+ crate::Expression::Select {
+ condition,
+ accept,
+ reject,
+ } => {
+ write!(self.out, "(")?;
+ self.put_expression(condition, context)?;
+ write!(self.out, " ? ")?;
+ self.put_expression(accept, context)?;
+ write!(self.out, " : ")?;
+ self.put_expression(reject, context)?;
+ write!(self.out, ")")?;
+ }
+ crate::Expression::Intrinsic { fun, argument } => {
+ let op = match fun {
+ crate::IntrinsicFunction::Any => "any",
+ crate::IntrinsicFunction::All => "all",
+ crate::IntrinsicFunction::IsNan => "",
+ crate::IntrinsicFunction::IsInf => "",
+ crate::IntrinsicFunction::IsFinite => "",
+ crate::IntrinsicFunction::IsNormal => "",
+ };
+ self.put_call(op, &[argument], context)?;
+ }
+ crate::Expression::Transpose(expr) => {
+ self.put_call("transpose", &[expr], context)?;
+ }
+ crate::Expression::DotProduct(a, b) => {
+ self.put_call("dot", &[a, b], context)?;
+ }
+ crate::Expression::CrossProduct(a, b) => {
+ self.put_call("cross", &[a, b], context)?;
+ }
+ crate::Expression::As {
+ expr,
+ kind,
+ convert,
+ } => {
+ let scalar = scalar_kind_string(kind);
+ let size = match *self.typifier.get(expr, &context.module.types) {
+ crate::TypeInner::Scalar { .. } => "",
+ crate::TypeInner::Vector { size, .. } => vector_size_string(size),
+ _ => return Err(Error::Validation),
+ };
+ let op = if convert { "static_cast" } else { "as_type" };
+ write!(self.out, "{}<{}{}>(", op, scalar, size)?;
+ self.put_expression(expr, context)?;
+ write!(self.out, ")")?;
+ }
+ crate::Expression::Derivative { axis, expr } => {
+ let op = match axis {
+ crate::DerivativeAxis::X => "dfdx",
+ crate::DerivativeAxis::Y => "dfdy",
+ crate::DerivativeAxis::Width => "fwidth",
+ };
+ self.put_call(op, &[expr], context)?;
+ }
+ crate::Expression::Call {
+ origin: crate::FunctionOrigin::Local(handle),
+ ref arguments,
+ } => {
+ let name = &self.names[&NameKey::Function(handle)];
+ write!(self.out, "{}", name)?;
+ self.put_call("", arguments, context)?;
+ }
+ crate::Expression::Call {
+ origin: crate::FunctionOrigin::External(ref name),
+ ref arguments,
+ } => match name.as_str() {
+ "atan2" | "cos" | "distance" | "length" | "mix" | "normalize" | "sin" => {
+ self.put_call(name, arguments, context)?;
+ }
+ "fclamp" => {
+ self.put_call("clamp", arguments, context)?;
+ }
+ other => return Err(Error::UnsupportedCall(other.to_owned())),
+ },
+ crate::Expression::ArrayLength(expr) => match *self
+ .typifier
+ .get(expr, &context.module.types)
+ {
+ crate::TypeInner::Array {
+ size: crate::ArraySize::Constant(const_handle),
+ ..
+ } => {
+ self.put_constant(const_handle, context.module)?;
+ }
+ crate::TypeInner::Array { .. } => return Err(Error::UnsupportedDynamicArrayLength),
+ _ => return Err(Error::Validation),
+ },
+ }
+ Ok(())
+ }
+
+ fn put_constant(
+ &mut self,
+ handle: Handle<crate::Constant>,
+ module: &crate::Module,
+ ) -> Result<(), Error> {
+ let constant = &module.constants[handle];
+ match constant.inner {
+ crate::ConstantInner::Sint(value) => {
+ write!(self.out, "{}", value)?;
+ }
+ crate::ConstantInner::Uint(value) => {
+ write!(self.out, "{}", value)?;
+ }
+ crate::ConstantInner::Float(value) => {
+ write!(self.out, "{}", value)?;
+ if value.fract() == 0.0 {
+ write!(self.out, ".0")?;
+ }
+ }
+ crate::ConstantInner::Bool(value) => {
+ write!(self.out, "{}", value)?;
+ }
+ crate::ConstantInner::Composite(ref constituents) => {
+ let ty_name = &self.names[&NameKey::Type(constant.ty)];
+ write!(self.out, "{}(", ty_name)?;
+ for (i, &handle) in constituents.iter().enumerate() {
+ if i != 0 {
+ write!(self.out, ", ")?;
+ }
+ self.put_constant(handle, module)?;
+ }
+ write!(self.out, ")")?;
+ }
+ }
+ Ok(())
+ }
+
+ fn put_block(
+ &mut self,
+ level: Level,
+ statements: &[crate::Statement],
+ context: &ExpressionContext,
+ return_value: Option<&str>,
+ ) -> Result<(), Error> {
+ for statement in statements {
+ log::trace!("statement[{}] {:?}", level.0, statement);
+ match *statement {
+ crate::Statement::Block(ref block) => {
+ if !block.is_empty() {
+ writeln!(self.out, "{}{{", level)?;
+ self.put_block(level.next(), block, context, return_value)?;
+ writeln!(self.out, "{}}}", level)?;
+ }
+ }
+ crate::Statement::If {
+ condition,
+ ref accept,
+ ref reject,
+ } => {
+ write!(self.out, "{}if (", level)?;
+ self.put_expression(condition, context)?;
+ writeln!(self.out, ") {{")?;
+ self.put_block(level.next(), accept, context, return_value)?;
+ if !reject.is_empty() {
+ writeln!(self.out, "{}}} else {{", level)?;
+ self.put_block(level.next(), reject, context, return_value)?;
+ }
+ writeln!(self.out, "{}}}", level)?;
+ }
+ crate::Statement::Switch {
+ selector,
+ ref cases,
+ ref default,
+ } => {
+ write!(self.out, "{}switch(", level)?;
+ self.put_expression(selector, context)?;
+ writeln!(self.out, ") {{")?;
+ let lcase = level.next();
+ for (&value, &(ref block, ref fall_through)) in cases.iter() {
+ writeln!(self.out, "{}case {}: {{", lcase, value)?;
+ self.put_block(lcase.next(), block, context, return_value)?;
+ if fall_through.is_none() {
+ writeln!(self.out, "{}break;", lcase.next())?;
+ }
+ writeln!(self.out, "{}}}", lcase)?;
+ }
+ writeln!(self.out, "{}default: {{", lcase)?;
+ self.put_block(lcase.next(), default, context, return_value)?;
+ writeln!(self.out, "{}}}", lcase)?;
+ writeln!(self.out, "{}}}", level)?;
+ }
+ crate::Statement::Loop {
+ ref body,
+ ref continuing,
+ } => {
+ writeln!(self.out, "{}while(true) {{", level)?;
+ self.put_block(level.next(), body, context, return_value)?;
+ if !continuing.is_empty() {
+ //TODO
+ }
+ writeln!(self.out, "{}}}", level)?;
+ }
+ crate::Statement::Break => {
+ writeln!(self.out, "{}break;", level)?;
+ }
+ crate::Statement::Continue => {
+ writeln!(self.out, "{}continue;", level)?;
+ }
+ crate::Statement::Return {
+ value: Some(expr_handle),
+ } => {
+ write!(self.out, "{}return ", level)?;
+ self.put_expression(expr_handle, context)?;
+ writeln!(self.out, ";")?;
+ }
+ crate::Statement::Return { value: None } => {
+ if let Some(string) = return_value {
+ writeln!(self.out, "{}return {};", level, string)?;
+ }
+ }
+ crate::Statement::Kill => {
+ writeln!(self.out, "{}discard_fragment();", level)?;
+ }
+ crate::Statement::Store { pointer, value } => {
+ //write!(self.out, "\t*")?;
+ write!(self.out, "{}", level)?;
+ self.put_expression(pointer, context)?;
+ write!(self.out, " = ")?;
+ self.put_expression(value, context)?;
+ writeln!(self.out, ";")?;
+ }
+ }
+ }
+ Ok(())
+ }
+
+ pub fn write(&mut self, module: &crate::Module, options: &Options) -> Result<(), Error> {
+ self.names.clear();
+ Namer::process(module, RESERVED, &mut self.names);
+
+ writeln!(self.out, "#include <metal_stdlib>")?;
+ writeln!(self.out, "#include <simd/simd.h>")?;
+
+ writeln!(self.out)?;
+ self.write_type_defs(module)?;
+
+ writeln!(self.out)?;
+ self.write_functions(module, options)?;
+
+ Ok(())
+ }
+
+ fn write_type_defs(&mut self, module: &crate::Module) -> Result<(), Error> {
+ for (handle, ty) in module.types.iter() {
+ let name = &self.names[&NameKey::Type(handle)];
+ match ty.inner {
+ crate::TypeInner::Scalar { kind, .. } => {
+ write!(self.out, "typedef {} {}", scalar_kind_string(kind), name)?;
+ }
+ crate::TypeInner::Vector { size, kind, .. } => {
+ write!(
+ self.out,
+ "typedef {}{} {}",
+ scalar_kind_string(kind),
+ vector_size_string(size),
+ name
+ )?;
+ }
+ crate::TypeInner::Matrix { columns, rows, .. } => {
+ write!(
+ self.out,
+ "typedef {}{}x{} {}",
+ scalar_kind_string(crate::ScalarKind::Float),
+ vector_size_string(columns),
+ vector_size_string(rows),
+ name
+ )?;
+ }
+ crate::TypeInner::Pointer { base, class } => {
+ use crate::StorageClass as Sc;
+ let base_name = &self.names[&NameKey::Type(base)];
+ let class_name = match class {
+ Sc::Input | Sc::Output => continue,
+ Sc::Uniform => "constant",
+ Sc::Storage => "device",
+ Sc::Handle
+ | Sc::Private
+ | Sc::Function
+ | Sc::WorkGroup
+ | Sc::PushConstant => "",
+ };
+ write!(self.out, "typedef {} {} *{}", class_name, base_name, name)?;
+ }
+ crate::TypeInner::Array {
+ base,
+ size,
+ stride: _,
+ } => {
+ let base_name = &self.names[&NameKey::Type(base)];
+ write!(self.out, "typedef {} {}[", base_name, name)?;
+ match size {
+ crate::ArraySize::Constant(const_handle) => {
+ self.put_constant(const_handle, module)?;
+ write!(self.out, "]")?;
+ }
+ crate::ArraySize::Dynamic => write!(self.out, "1]")?,
+ }
+ }
+ crate::TypeInner::Struct { ref members } => {
+ writeln!(self.out, "struct {} {{", name)?;
+ for (index, member) in members.iter().enumerate() {
+ let member_name = &self.names[&NameKey::StructMember(handle, index as u32)];
+ let base_name = &self.names[&NameKey::Type(member.ty)];
+ write!(self.out, "\t{} {}", base_name, member_name)?;
+ match member.origin {
+ crate::MemberOrigin::Empty => {}
+ crate::MemberOrigin::BuiltIn(built_in) => {
+ ResolvedBinding::BuiltIn(built_in)
+ .try_fmt_decorated(&mut self.out, "")?;
+ }
+ crate::MemberOrigin::Offset(_) => {
+ //TODO
+ }
+ }
+ writeln!(self.out, ";")?;
+ }
+ write!(self.out, "}}")?;
+ }
+ crate::TypeInner::Image {
+ dim,
+ arrayed,
+ class,
+ } => {
+ let dim_str = match dim {
+ crate::ImageDimension::D1 => "1d",
+ crate::ImageDimension::D2 => "2d",
+ crate::ImageDimension::D3 => "3d",
+ crate::ImageDimension::Cube => "Cube",
+ };
+ let (texture_str, msaa_str, kind, access) = match class {
+ crate::ImageClass::Sampled { kind, multi } => {
+ ("texture", if multi { "_ms" } else { "" }, kind, "sample")
+ }
+ crate::ImageClass::Depth => {
+ ("depth", "", crate::ScalarKind::Float, "sample")
+ }
+ crate::ImageClass::Storage(format) => {
+ let (_, global) = module
+ .global_variables
+ .iter()
+ .find(|(_, var)| var.ty == handle)
+ .expect("Unable to find a global variable using the image type");
+ let access = if global
+ .storage_access
+ .contains(crate::StorageAccess::LOAD | crate::StorageAccess::STORE)
+ {
+ "read_write"
+ } else if global.storage_access.contains(crate::StorageAccess::STORE) {
+ "write"
+ } else if global.storage_access.contains(crate::StorageAccess::LOAD) {
+ "read"
+ } else {
+ return Err(Error::InvalidImageAccess(global.storage_access));
+ };
+ ("texture", "", format.into(), access)
+ }
+ };
+ let base_name = scalar_kind_string(kind);
+ let array_str = if arrayed { "_array" } else { "" };
+ write!(
+ self.out,
+ "typedef {}{}{}{}<{}, access::{}> {}",
+ texture_str, dim_str, msaa_str, array_str, base_name, access, name
+ )?;
+ }
+ crate::TypeInner::Sampler { comparison: _ } => {
+ write!(self.out, "typedef sampler {}", name)?;
+ }
+ }
+ writeln!(self.out, ";")?;
+ }
+ Ok(())
+ }
+
+ fn write_functions(&mut self, module: &crate::Module, options: &Options) -> Result<(), Error> {
+ for (fun_handle, fun) in module.functions.iter() {
+ self.typifier.resolve_all(
+ &fun.expressions,
+ &module.types,
+ &ResolveContext {
+ constants: &module.constants,
+ global_vars: &module.global_variables,
+ local_vars: &fun.local_variables,
+ functions: &module.functions,
+ arguments: &fun.arguments,
+ },
+ )?;
+
+ let fun_name = &self.names[&NameKey::Function(fun_handle)];
+ let result_type_name = match fun.return_type {
+ Some(ret_ty) => &self.names[&NameKey::Type(ret_ty)],
+ None => "void",
+ };
+ writeln!(self.out, "{} {}(", result_type_name, fun_name)?;
+
+ for (index, arg) in fun.arguments.iter().enumerate() {
+ let name = &self.names[&NameKey::FunctionArgument(fun_handle, index as u32)];
+ let param_type_name = &self.names[&NameKey::Type(arg.ty)];
+ let separator = separate(index + 1 == fun.arguments.len());
+ writeln!(self.out, "\t{} {}{}", param_type_name, name, separator)?;
+ }
+ writeln!(self.out, ") {{")?;
+
+ for (local_handle, local) in fun.local_variables.iter() {
+ let ty_name = &self.names[&NameKey::Type(local.ty)];
+ let local_name = &self.names[&NameKey::FunctionLocal(fun_handle, local_handle)];
+ write!(self.out, "\t{} {}", ty_name, local_name)?;
+ if let Some(value) = local.init {
+ write!(self.out, " = ")?;
+ self.put_constant(value, module)?;
+ }
+ writeln!(self.out, ";")?;
+ }
+
+ let context = ExpressionContext {
+ function: fun,
+ origin: FunctionOrigin::Handle(fun_handle),
+ module,
+ };
+ self.put_block(Level(1), &fun.body, &context, None)?;
+ writeln!(self.out, "}}")?;
+ }
+
+ for (ep_index, (&(stage, _), ep)) in module.entry_points.iter().enumerate() {
+ let fun = &ep.function;
+ self.typifier.resolve_all(
+ &fun.expressions,
+ &module.types,
+ &ResolveContext {
+ constants: &module.constants,
+ global_vars: &module.global_variables,
+ local_vars: &fun.local_variables,
+ functions: &module.functions,
+ arguments: &fun.arguments,
+ },
+ )?;
+
+ // find the entry point(s) and inputs/outputs
+ let mut last_used_global = None;
+ for ((handle, var), &usage) in module.global_variables.iter().zip(&fun.global_usage) {
+ match var.class {
+ crate::StorageClass::Input => {
+ if let Some(crate::Binding::Location(_)) = var.binding {
+ continue;
+ }
+ }
+ crate::StorageClass::Output => continue,
+ _ => {}
+ }
+ if !usage.is_empty() {
+ last_used_global = Some(handle);
+ }
+ }
+
+ let fun_name = &self.names[&NameKey::EntryPoint(ep_index as _)];
+ let output_name = format!("{}Output", fun_name);
+ let location_input_name = format!("{}Input", fun_name);
+
+ let (em_str, in_mode, out_mode) = match stage {
+ crate::ShaderStage::Vertex => (
+ "vertex",
+ LocationMode::VertexInput,
+ LocationMode::Intermediate,
+ ),
+ crate::ShaderStage::Fragment { .. } => (
+ "fragment",
+ LocationMode::Intermediate,
+ LocationMode::FragmentOutput,
+ ),
+ crate::ShaderStage::Compute { .. } => {
+ ("kernel", LocationMode::Uniform, LocationMode::Uniform)
+ }
+ };
+
+ let return_value = match stage {
+ crate::ShaderStage::Vertex | crate::ShaderStage::Fragment => {
+ // make dedicated input/output structs
+ writeln!(self.out, "struct {} {{", location_input_name)?;
+
+ for ((handle, var), &usage) in
+ module.global_variables.iter().zip(&fun.global_usage)
+ {
+ if var.class != crate::StorageClass::Input
+ || !usage.contains(crate::GlobalUse::LOAD)
+ {
+ continue;
+ }
+ // if it's a struct, lift all the built-in contents up to the root
+ if let crate::TypeInner::Struct { ref members } = module.types[var.ty].inner
+ {
+ for (index, member) in members.iter().enumerate() {
+ if let crate::MemberOrigin::BuiltIn(built_in) = member.origin {
+ let name =
+ &self.names[&NameKey::StructMember(var.ty, index as u32)];
+ let ty_name = &self.names[&NameKey::Type(member.ty)];
+ write!(self.out, "\t{} {}", ty_name, name)?;
+ ResolvedBinding::BuiltIn(built_in)
+ .try_fmt_decorated(&mut self.out, ";\n")?;
+ }
+ }
+ } else if let Some(ref binding @ crate::Binding::Location(_)) = var.binding
+ {
+ let tyvar = TypedGlobalVariable {
+ module,
+ names: &self.names,
+ handle,
+ usage: crate::GlobalUse::empty(),
+ };
+ let resolved = options.resolve_binding(stage, binding, in_mode)?;
+
+ write!(self.out, "\t")?;
+ tyvar.try_fmt(&mut self.out)?;
+ resolved.try_fmt_decorated(&mut self.out, ";\n")?;
+ }
+ }
+ writeln!(self.out, "}};")?;
+
+ writeln!(self.out, "struct {} {{", output_name)?;
+ for ((handle, var), &usage) in
+ module.global_variables.iter().zip(&fun.global_usage)
+ {
+ if var.class != crate::StorageClass::Output
+ || !usage.contains(crate::GlobalUse::STORE)
+ {
+ continue;
+ }
+ // if it's a struct, lift all the built-in contents up to the root
+ if let crate::TypeInner::Struct { ref members } = module.types[var.ty].inner
+ {
+ for (index, member) in members.iter().enumerate() {
+ let name =
+ &self.names[&NameKey::StructMember(var.ty, index as u32)];
+ let ty_name = &self.names[&NameKey::Type(member.ty)];
+ match member.origin {
+ crate::MemberOrigin::Empty => {}
+ crate::MemberOrigin::BuiltIn(built_in) => {
+ write!(self.out, "\t{} {}", ty_name, name)?;
+ ResolvedBinding::BuiltIn(built_in)
+ .try_fmt_decorated(&mut self.out, ";\n")?;
+ }
+ crate::MemberOrigin::Offset(_) => {
+ //TODO
+ }
+ }
+ }
+ } else {
+ let tyvar = TypedGlobalVariable {
+ module,
+ names: &self.names,
+ handle,
+ usage: crate::GlobalUse::empty(),
+ };
+ write!(self.out, "\t")?;
+ tyvar.try_fmt(&mut self.out)?;
+ if let Some(ref binding) = var.binding {
+ let resolved = options.resolve_binding(stage, binding, out_mode)?;
+ resolved.try_fmt_decorated(&mut self.out, "")?;
+ }
+ writeln!(self.out, ";")?;
+ }
+ }
+ writeln!(self.out, "}};")?;
+
+ writeln!(self.out, "{} {} {}(", em_str, output_name, fun_name)?;
+ let separator = separate(last_used_global.is_none());
+ writeln!(
+ self.out,
+ "\t{} {} [[stage_in]]{}",
+ location_input_name, LOCATION_INPUT_STRUCT_NAME, separator
+ )?;
+
+ Some(OUTPUT_STRUCT_NAME)
+ }
+ crate::ShaderStage::Compute => {
+ writeln!(self.out, "{} void {}(", em_str, fun_name)?;
+ None
+ }
+ };
+
+ for ((handle, var), &usage) in module.global_variables.iter().zip(&fun.global_usage) {
+ if usage.is_empty() || var.class == crate::StorageClass::Output {
+ continue;
+ }
+ if var.class == crate::StorageClass::Input {
+ if let Some(crate::Binding::Location(_)) = var.binding {
+ // location inputs are put into a separate struct
+ continue;
+ }
+ }
+ let loc_mode = match (stage, var.class) {
+ (crate::ShaderStage::Vertex, crate::StorageClass::Input) => {
+ LocationMode::VertexInput
+ }
+ (crate::ShaderStage::Vertex, crate::StorageClass::Output)
+ | (crate::ShaderStage::Fragment { .. }, crate::StorageClass::Input) => {
+ LocationMode::Intermediate
+ }
+ (crate::ShaderStage::Fragment { .. }, crate::StorageClass::Output) => {
+ LocationMode::FragmentOutput
+ }
+ _ => LocationMode::Uniform,
+ };
+ let resolved =
+ options.resolve_binding(stage, var.binding.as_ref().unwrap(), loc_mode)?;
+ let tyvar = TypedGlobalVariable {
+ module,
+ names: &self.names,
+ handle,
+ usage,
+ };
+ let separator = separate(last_used_global == Some(handle));
+ write!(self.out, "\t")?;
+ tyvar.try_fmt(&mut self.out)?;
+ resolved.try_fmt_decorated(&mut self.out, separator)?;
+ if let Some(value) = var.init {
+ write!(self.out, " = ")?;
+ self.put_constant(value, module)?;
+ }
+ writeln!(self.out)?;
+ }
+ writeln!(self.out, ") {{")?;
+
+ match stage {
+ crate::ShaderStage::Vertex | crate::ShaderStage::Fragment => {
+ writeln!(self.out, "\t{} {};", output_name, OUTPUT_STRUCT_NAME)?;
+ }
+ crate::ShaderStage::Compute => {}
+ }
+ for (local_handle, local) in fun.local_variables.iter() {
+ let name = &self.names[&NameKey::EntryPointLocal(ep_index as _, local_handle)];
+ let ty_name = &self.names[&NameKey::Type(local.ty)];
+ write!(self.out, "\t{} {}", ty_name, name)?;
+ if let Some(value) = local.init {
+ write!(self.out, " = ")?;
+ self.put_constant(value, module)?;
+ }
+ writeln!(self.out, ";")?;
+ }
+
+ let context = ExpressionContext {
+ function: fun,
+ origin: FunctionOrigin::EntryPoint(ep_index as _),
+ module,
+ };
+ self.put_block(Level(1), &fun.body, &context, return_value)?;
+ writeln!(self.out, "}}")?;
+ }
+
+ Ok(())
+ }
+}
diff --git a/third_party/rust/naga/src/back/spv/helpers.rs b/third_party/rust/naga/src/back/spv/helpers.rs
new file mode 100644
index 0000000000..5facbe8b69
--- /dev/null
+++ b/third_party/rust/naga/src/back/spv/helpers.rs
@@ -0,0 +1,20 @@
+use spirv::Word;
+
+pub(crate) fn bytes_to_words(bytes: &[u8]) -> Vec<Word> {
+ bytes
+ .chunks(4)
+ .map(|chars| chars.iter().rev().fold(0u32, |u, c| (u << 8) | *c as u32))
+ .collect()
+}
+
+pub(crate) fn string_to_words(input: &str) -> Vec<Word> {
+ let bytes = input.as_bytes();
+ let mut words = bytes_to_words(bytes);
+
+ if bytes.len() % 4 == 0 {
+ // nul-termination
+ words.push(0x0u32);
+ }
+
+ words
+}
diff --git a/third_party/rust/naga/src/back/spv/instructions.rs b/third_party/rust/naga/src/back/spv/instructions.rs
new file mode 100644
index 0000000000..ab8e56844a
--- /dev/null
+++ b/third_party/rust/naga/src/back/spv/instructions.rs
@@ -0,0 +1,708 @@
+use crate::back::spv::{helpers, Instruction};
+use spirv::{Op, Word};
+
+pub(super) enum Signedness {
+ Unsigned = 0,
+ Signed = 1,
+}
+
+//
+// Debug Instructions
+//
+
+pub(super) fn instruction_source(
+ source_language: spirv::SourceLanguage,
+ version: u32,
+) -> Instruction {
+ let mut instruction = Instruction::new(Op::Source);
+ instruction.add_operand(source_language as u32);
+ instruction.add_operands(helpers::bytes_to_words(&version.to_le_bytes()));
+ instruction
+}
+
+pub(super) fn instruction_name(target_id: Word, name: &str) -> Instruction {
+ let mut instruction = Instruction::new(Op::Name);
+ instruction.add_operand(target_id);
+ instruction.add_operands(helpers::string_to_words(name));
+ instruction
+}
+
+//
+// Annotation Instructions
+//
+
+pub(super) fn instruction_decorate(
+ target_id: Word,
+ decoration: spirv::Decoration,
+ operands: &[Word],
+) -> Instruction {
+ let mut instruction = Instruction::new(Op::Decorate);
+ instruction.add_operand(target_id);
+ instruction.add_operand(decoration as u32);
+
+ for operand in operands {
+ instruction.add_operand(*operand)
+ }
+
+ instruction
+}
+
+//
+// Extension Instructions
+//
+
+pub(super) fn instruction_ext_inst_import(id: Word, name: &str) -> Instruction {
+ let mut instruction = Instruction::new(Op::ExtInstImport);
+ instruction.set_result(id);
+ instruction.add_operands(helpers::string_to_words(name));
+ instruction
+}
+
+//
+// Mode-Setting Instructions
+//
+
+pub(super) fn instruction_memory_model(
+ addressing_model: spirv::AddressingModel,
+ memory_model: spirv::MemoryModel,
+) -> Instruction {
+ let mut instruction = Instruction::new(Op::MemoryModel);
+ instruction.add_operand(addressing_model as u32);
+ instruction.add_operand(memory_model as u32);
+ instruction
+}
+
+pub(super) fn instruction_entry_point(
+ execution_model: spirv::ExecutionModel,
+ entry_point_id: Word,
+ name: &str,
+ interface_ids: &[Word],
+) -> Instruction {
+ let mut instruction = Instruction::new(Op::EntryPoint);
+ instruction.add_operand(execution_model as u32);
+ instruction.add_operand(entry_point_id);
+ instruction.add_operands(helpers::string_to_words(name));
+
+ for interface_id in interface_ids {
+ instruction.add_operand(*interface_id);
+ }
+
+ instruction
+}
+
+pub(super) fn instruction_execution_mode(
+ entry_point_id: Word,
+ execution_mode: spirv::ExecutionMode,
+) -> Instruction {
+ let mut instruction = Instruction::new(Op::ExecutionMode);
+ instruction.add_operand(entry_point_id);
+ instruction.add_operand(execution_mode as u32);
+ instruction
+}
+
+pub(super) fn instruction_capability(capability: spirv::Capability) -> Instruction {
+ let mut instruction = Instruction::new(Op::Capability);
+ instruction.add_operand(capability as u32);
+ instruction
+}
+
+//
+// Type-Declaration Instructions
+//
+
+pub(super) fn instruction_type_void(id: Word) -> Instruction {
+ let mut instruction = Instruction::new(Op::TypeVoid);
+ instruction.set_result(id);
+ instruction
+}
+
+pub(super) fn instruction_type_bool(id: Word) -> Instruction {
+ let mut instruction = Instruction::new(Op::TypeBool);
+ instruction.set_result(id);
+ instruction
+}
+
+pub(super) fn instruction_type_int(id: Word, width: Word, signedness: Signedness) -> Instruction {
+ let mut instruction = Instruction::new(Op::TypeInt);
+ instruction.set_result(id);
+ instruction.add_operand(width);
+ instruction.add_operand(signedness as u32);
+ instruction
+}
+
+pub(super) fn instruction_type_float(id: Word, width: Word) -> Instruction {
+ let mut instruction = Instruction::new(Op::TypeFloat);
+ instruction.set_result(id);
+ instruction.add_operand(width);
+ instruction
+}
+
+pub(super) fn instruction_type_vector(
+ id: Word,
+ component_type_id: Word,
+ component_count: crate::VectorSize,
+) -> Instruction {
+ let mut instruction = Instruction::new(Op::TypeVector);
+ instruction.set_result(id);
+ instruction.add_operand(component_type_id);
+ instruction.add_operand(component_count as u32);
+ instruction
+}
+
+pub(super) fn instruction_type_matrix(
+ id: Word,
+ column_type_id: Word,
+ column_count: crate::VectorSize,
+) -> Instruction {
+ let mut instruction = Instruction::new(Op::TypeMatrix);
+ instruction.set_result(id);
+ instruction.add_operand(column_type_id);
+ instruction.add_operand(column_count as u32);
+ instruction
+}
+
+pub(super) fn instruction_type_image(
+ id: Word,
+ sampled_type_id: Word,
+ dim: spirv::Dim,
+ arrayed: bool,
+ image_class: crate::ImageClass,
+) -> Instruction {
+ let mut instruction = Instruction::new(Op::TypeImage);
+ instruction.set_result(id);
+ instruction.add_operand(sampled_type_id);
+ instruction.add_operand(dim as u32);
+
+ instruction.add_operand(match image_class {
+ crate::ImageClass::Depth => 1,
+ _ => 0,
+ });
+ instruction.add_operand(arrayed as u32);
+ instruction.add_operand(match image_class {
+ crate::ImageClass::Sampled { multi: true, .. } => 1,
+ _ => 0,
+ });
+ instruction.add_operand(match image_class {
+ crate::ImageClass::Sampled { .. } => 1,
+ _ => 0,
+ });
+
+ let format = match image_class {
+ crate::ImageClass::Storage(format) => match format {
+ crate::StorageFormat::R8Unorm => spirv::ImageFormat::R8,
+ crate::StorageFormat::R8Snorm => spirv::ImageFormat::R8Snorm,
+ crate::StorageFormat::R8Uint => spirv::ImageFormat::R8ui,
+ crate::StorageFormat::R8Sint => spirv::ImageFormat::R8i,
+ crate::StorageFormat::R16Uint => spirv::ImageFormat::R16ui,
+ crate::StorageFormat::R16Sint => spirv::ImageFormat::R16i,
+ crate::StorageFormat::R16Float => spirv::ImageFormat::R16f,
+ crate::StorageFormat::Rg8Unorm => spirv::ImageFormat::Rg8,
+ crate::StorageFormat::Rg8Snorm => spirv::ImageFormat::Rg8Snorm,
+ crate::StorageFormat::Rg8Uint => spirv::ImageFormat::Rg8ui,
+ crate::StorageFormat::Rg8Sint => spirv::ImageFormat::Rg8i,
+ crate::StorageFormat::R32Uint => spirv::ImageFormat::R32ui,
+ crate::StorageFormat::R32Sint => spirv::ImageFormat::R32i,
+ crate::StorageFormat::R32Float => spirv::ImageFormat::R32f,
+ crate::StorageFormat::Rg16Uint => spirv::ImageFormat::Rg16ui,
+ crate::StorageFormat::Rg16Sint => spirv::ImageFormat::Rg16i,
+ crate::StorageFormat::Rg16Float => spirv::ImageFormat::Rg16f,
+ crate::StorageFormat::Rgba8Unorm => spirv::ImageFormat::Rgba8,
+ crate::StorageFormat::Rgba8Snorm => spirv::ImageFormat::Rgba8Snorm,
+ crate::StorageFormat::Rgba8Uint => spirv::ImageFormat::Rgba8ui,
+ crate::StorageFormat::Rgba8Sint => spirv::ImageFormat::Rgba8i,
+ crate::StorageFormat::Rgb10a2Unorm => spirv::ImageFormat::Rgb10a2ui,
+ crate::StorageFormat::Rg11b10Float => spirv::ImageFormat::R11fG11fB10f,
+ crate::StorageFormat::Rg32Uint => spirv::ImageFormat::Rg32ui,
+ crate::StorageFormat::Rg32Sint => spirv::ImageFormat::Rg32i,
+ crate::StorageFormat::Rg32Float => spirv::ImageFormat::Rg32f,
+ crate::StorageFormat::Rgba16Uint => spirv::ImageFormat::Rgba16ui,
+ crate::StorageFormat::Rgba16Sint => spirv::ImageFormat::Rgba16i,
+ crate::StorageFormat::Rgba16Float => spirv::ImageFormat::Rgba16f,
+ crate::StorageFormat::Rgba32Uint => spirv::ImageFormat::Rgba32ui,
+ crate::StorageFormat::Rgba32Sint => spirv::ImageFormat::Rgba32i,
+ crate::StorageFormat::Rgba32Float => spirv::ImageFormat::Rgba32f,
+ },
+ _ => spirv::ImageFormat::Unknown,
+ };
+
+ instruction.add_operand(format as u32);
+ instruction
+}
+
+pub(super) fn instruction_type_sampler(id: Word) -> Instruction {
+ let mut instruction = Instruction::new(Op::TypeSampler);
+ instruction.set_result(id);
+ instruction
+}
+
+pub(super) fn instruction_type_sampled_image(id: Word, image_type_id: Word) -> Instruction {
+ let mut instruction = Instruction::new(Op::TypeSampledImage);
+ instruction.set_result(id);
+ instruction.add_operand(image_type_id);
+ instruction
+}
+
+pub(super) fn instruction_type_array(
+ id: Word,
+ element_type_id: Word,
+ length_id: Word,
+) -> Instruction {
+ let mut instruction = Instruction::new(Op::TypeArray);
+ instruction.set_result(id);
+ instruction.add_operand(element_type_id);
+ instruction.add_operand(length_id);
+ instruction
+}
+
+pub(super) fn instruction_type_runtime_array(id: Word, element_type_id: Word) -> Instruction {
+ let mut instruction = Instruction::new(Op::TypeRuntimeArray);
+ instruction.set_result(id);
+ instruction.add_operand(element_type_id);
+ instruction
+}
+
+pub(super) fn instruction_type_struct(id: Word, member_ids: &[Word]) -> Instruction {
+ let mut instruction = Instruction::new(Op::TypeStruct);
+ instruction.set_result(id);
+
+ for member_id in member_ids {
+ instruction.add_operand(*member_id)
+ }
+
+ instruction
+}
+
+pub(super) fn instruction_type_pointer(
+ id: Word,
+ storage_class: spirv::StorageClass,
+ type_id: Word,
+) -> Instruction {
+ let mut instruction = Instruction::new(Op::TypePointer);
+ instruction.set_result(id);
+ instruction.add_operand(storage_class as u32);
+ instruction.add_operand(type_id);
+ instruction
+}
+
+pub(super) fn instruction_type_function(
+ id: Word,
+ return_type_id: Word,
+ parameter_ids: &[Word],
+) -> Instruction {
+ let mut instruction = Instruction::new(Op::TypeFunction);
+ instruction.set_result(id);
+ instruction.add_operand(return_type_id);
+
+ for parameter_id in parameter_ids {
+ instruction.add_operand(*parameter_id);
+ }
+
+ instruction
+}
+
+//
+// Constant-Creation Instructions
+//
+
+pub(super) fn instruction_constant_true(result_type_id: Word, id: Word) -> Instruction {
+ let mut instruction = Instruction::new(Op::ConstantTrue);
+ instruction.set_type(result_type_id);
+ instruction.set_result(id);
+ instruction
+}
+
+pub(super) fn instruction_constant_false(result_type_id: Word, id: Word) -> Instruction {
+ let mut instruction = Instruction::new(Op::ConstantFalse);
+ instruction.set_type(result_type_id);
+ instruction.set_result(id);
+ instruction
+}
+
+pub(super) fn instruction_constant(result_type_id: Word, id: Word, values: &[Word]) -> Instruction {
+ let mut instruction = Instruction::new(Op::Constant);
+ instruction.set_type(result_type_id);
+ instruction.set_result(id);
+
+ for value in values {
+ instruction.add_operand(*value);
+ }
+
+ instruction
+}
+
+pub(super) fn instruction_constant_composite(
+ result_type_id: Word,
+ id: Word,
+ constituent_ids: &[Word],
+) -> Instruction {
+ let mut instruction = Instruction::new(Op::ConstantComposite);
+ instruction.set_type(result_type_id);
+ instruction.set_result(id);
+
+ for constituent_id in constituent_ids {
+ instruction.add_operand(*constituent_id);
+ }
+
+ instruction
+}
+
+//
+// Memory Instructions
+//
+
+pub(super) fn instruction_variable(
+ result_type_id: Word,
+ id: Word,
+ storage_class: spirv::StorageClass,
+ initializer_id: Option<Word>,
+) -> Instruction {
+ let mut instruction = Instruction::new(Op::Variable);
+ instruction.set_type(result_type_id);
+ instruction.set_result(id);
+ instruction.add_operand(storage_class as u32);
+
+ if let Some(initializer_id) = initializer_id {
+ instruction.add_operand(initializer_id);
+ }
+
+ instruction
+}
+
+pub(super) fn instruction_load(
+ result_type_id: Word,
+ id: Word,
+ pointer_type_id: Word,
+ memory_access: Option<spirv::MemoryAccess>,
+) -> Instruction {
+ let mut instruction = Instruction::new(Op::Load);
+ instruction.set_type(result_type_id);
+ instruction.set_result(id);
+ instruction.add_operand(pointer_type_id);
+
+ if let Some(memory_access) = memory_access {
+ instruction.add_operand(memory_access.bits());
+ }
+
+ instruction
+}
+
+pub(super) fn instruction_store(
+ pointer_type_id: Word,
+ object_id: Word,
+ memory_access: Option<spirv::MemoryAccess>,
+) -> Instruction {
+ let mut instruction = Instruction::new(Op::Store);
+ instruction.add_operand(pointer_type_id);
+ instruction.add_operand(object_id);
+
+ if let Some(memory_access) = memory_access {
+ instruction.add_operand(memory_access.bits());
+ }
+
+ instruction
+}
+
+pub(super) fn instruction_access_chain(
+ result_type_id: Word,
+ id: Word,
+ base_id: Word,
+ index_ids: &[Word],
+) -> Instruction {
+ let mut instruction = Instruction::new(Op::AccessChain);
+ instruction.set_type(result_type_id);
+ instruction.set_result(id);
+ instruction.add_operand(base_id);
+
+ for index_id in index_ids {
+ instruction.add_operand(*index_id);
+ }
+
+ instruction
+}
+
+//
+// Function Instructions
+//
+
+pub(super) fn instruction_function(
+ return_type_id: Word,
+ id: Word,
+ function_control: spirv::FunctionControl,
+ function_type_id: Word,
+) -> Instruction {
+ let mut instruction = Instruction::new(Op::Function);
+ instruction.set_type(return_type_id);
+ instruction.set_result(id);
+ instruction.add_operand(function_control.bits());
+ instruction.add_operand(function_type_id);
+ instruction
+}
+
+pub(super) fn instruction_function_parameter(result_type_id: Word, id: Word) -> Instruction {
+ let mut instruction = Instruction::new(Op::FunctionParameter);
+ instruction.set_type(result_type_id);
+ instruction.set_result(id);
+ instruction
+}
+
+pub(super) fn instruction_function_end() -> Instruction {
+ Instruction::new(Op::FunctionEnd)
+}
+
+pub(super) fn instruction_function_call(
+ result_type_id: Word,
+ id: Word,
+ function_id: Word,
+ argument_ids: &[Word],
+) -> Instruction {
+ let mut instruction = Instruction::new(Op::FunctionCall);
+ instruction.set_type(result_type_id);
+ instruction.set_result(id);
+ instruction.add_operand(function_id);
+
+ for argument_id in argument_ids {
+ instruction.add_operand(*argument_id);
+ }
+
+ instruction
+}
+
+//
+// Image Instructions
+//
+pub(super) fn instruction_sampled_image(
+ result_type_id: Word,
+ id: Word,
+ image: Word,
+ sampler: Word,
+) -> Instruction {
+ let mut instruction = Instruction::new(Op::SampledImage);
+ instruction.set_type(result_type_id);
+ instruction.set_result(id);
+ instruction.add_operand(image);
+ instruction.add_operand(sampler);
+ instruction
+}
+
+pub(super) fn instruction_image_sample_implicit_lod(
+ result_type_id: Word,
+ id: Word,
+ sampled_image: Word,
+ coordinates: Word,
+) -> Instruction {
+ let mut instruction = Instruction::new(Op::ImageSampleImplicitLod);
+ instruction.set_type(result_type_id);
+ instruction.set_result(id);
+ instruction.add_operand(sampled_image);
+ instruction.add_operand(coordinates);
+ instruction
+}
+
+//
+// Conversion Instructions
+//
+pub(super) fn instruction_unary(
+ op: Op,
+ result_type_id: Word,
+ id: Word,
+ value: Word,
+) -> Instruction {
+ let mut instruction = Instruction::new(op);
+ instruction.set_type(result_type_id);
+ instruction.set_result(id);
+ instruction.add_operand(value);
+ instruction
+}
+
+//
+// Composite Instructions
+//
+
+pub(super) fn instruction_composite_construct(
+ result_type_id: Word,
+ id: Word,
+ constituent_ids: &[Word],
+) -> Instruction {
+ let mut instruction = Instruction::new(Op::CompositeConstruct);
+ instruction.set_type(result_type_id);
+ instruction.set_result(id);
+
+ for constituent_id in constituent_ids {
+ instruction.add_operand(*constituent_id);
+ }
+
+ instruction
+}
+
+//
+// Arithmetic Instructions
+//
+fn instruction_binary(
+ op: Op,
+ result_type_id: Word,
+ id: Word,
+ operand_1: Word,
+ operand_2: Word,
+) -> Instruction {
+ let mut instruction = Instruction::new(op);
+ instruction.set_type(result_type_id);
+ instruction.set_result(id);
+ instruction.add_operand(operand_1);
+ instruction.add_operand(operand_2);
+ instruction
+}
+
+pub(super) fn instruction_i_sub(
+ result_type_id: Word,
+ id: Word,
+ operand_1: Word,
+ operand_2: Word,
+) -> Instruction {
+ instruction_binary(Op::ISub, result_type_id, id, operand_1, operand_2)
+}
+
+pub(super) fn instruction_f_sub(
+ result_type_id: Word,
+ id: Word,
+ operand_1: Word,
+ operand_2: Word,
+) -> Instruction {
+ instruction_binary(Op::FSub, result_type_id, id, operand_1, operand_2)
+}
+
+pub(super) fn instruction_i_mul(
+ result_type_id: Word,
+ id: Word,
+ operand_1: Word,
+ operand_2: Word,
+) -> Instruction {
+ instruction_binary(Op::IMul, result_type_id, id, operand_1, operand_2)
+}
+
+pub(super) fn instruction_f_mul(
+ result_type_id: Word,
+ id: Word,
+ operand_1: Word,
+ operand_2: Word,
+) -> Instruction {
+ instruction_binary(Op::FMul, result_type_id, id, operand_1, operand_2)
+}
+
+pub(super) fn instruction_vector_times_scalar(
+ result_type_id: Word,
+ id: Word,
+ vector_type_id: Word,
+ scalar_type_id: Word,
+) -> Instruction {
+ let mut instruction = Instruction::new(Op::VectorTimesScalar);
+ instruction.set_type(result_type_id);
+ instruction.set_result(id);
+ instruction.add_operand(vector_type_id);
+ instruction.add_operand(scalar_type_id);
+ instruction
+}
+
+pub(super) fn instruction_matrix_times_scalar(
+ result_type_id: Word,
+ id: Word,
+ matrix_id: Word,
+ scalar_id: Word,
+) -> Instruction {
+ let mut instruction = Instruction::new(Op::MatrixTimesScalar);
+ instruction.set_type(result_type_id);
+ instruction.set_result(id);
+ instruction.add_operand(matrix_id);
+ instruction.add_operand(scalar_id);
+ instruction
+}
+
+pub(super) fn instruction_vector_times_matrix(
+ result_type_id: Word,
+ id: Word,
+ vector_id: Word,
+ matrix_id: Word,
+) -> Instruction {
+ let mut instruction = Instruction::new(Op::VectorTimesMatrix);
+ instruction.set_type(result_type_id);
+ instruction.set_result(id);
+ instruction.add_operand(vector_id);
+ instruction.add_operand(matrix_id);
+ instruction
+}
+
+pub(super) fn instruction_matrix_times_vector(
+ result_type_id: Word,
+ id: Word,
+ matrix_id: Word,
+ vector_id: Word,
+) -> Instruction {
+ let mut instruction = Instruction::new(Op::MatrixTimesVector);
+ instruction.set_type(result_type_id);
+ instruction.set_result(id);
+ instruction.add_operand(matrix_id);
+ instruction.add_operand(vector_id);
+ instruction
+}
+
+pub(super) fn instruction_matrix_times_matrix(
+ result_type_id: Word,
+ id: Word,
+ left_matrix: Word,
+ right_matrix: Word,
+) -> Instruction {
+ let mut instruction = Instruction::new(Op::MatrixTimesMatrix);
+ instruction.set_type(result_type_id);
+ instruction.set_result(id);
+ instruction.add_operand(left_matrix);
+ instruction.add_operand(right_matrix);
+ instruction
+}
+
+//
+// Bit Instructions
+//
+
+pub(super) fn instruction_bitwise_and(
+ result_type_id: Word,
+ id: Word,
+ operand_1: Word,
+ operand_2: Word,
+) -> Instruction {
+ instruction_binary(Op::BitwiseAnd, result_type_id, id, operand_1, operand_2)
+}
+
+//
+// Relational and Logical Instructions
+//
+
+//
+// Derivative Instructions
+//
+
+//
+// Control-Flow Instructions
+//
+
+pub(super) fn instruction_label(id: Word) -> Instruction {
+ let mut instruction = Instruction::new(Op::Label);
+ instruction.set_result(id);
+ instruction
+}
+
+pub(super) fn instruction_return() -> Instruction {
+ Instruction::new(Op::Return)
+}
+
+pub(super) fn instruction_return_value(value_id: Word) -> Instruction {
+ let mut instruction = Instruction::new(Op::ReturnValue);
+ instruction.add_operand(value_id);
+ instruction
+}
+
+//
+// Atomic Instructions
+//
+
+//
+// Primitive Instructions
+//
diff --git a/third_party/rust/naga/src/back/spv/layout.rs b/third_party/rust/naga/src/back/spv/layout.rs
new file mode 100644
index 0000000000..006e785317
--- /dev/null
+++ b/third_party/rust/naga/src/back/spv/layout.rs
@@ -0,0 +1,91 @@
+use crate::back::spv::{Instruction, LogicalLayout, PhysicalLayout};
+use spirv::*;
+use std::iter;
+
+impl PhysicalLayout {
+ pub(super) fn new(header: &crate::Header) -> Self {
+ let version: Word = ((header.version.0 as u32) << 16)
+ | ((header.version.1 as u32) << 8)
+ | header.version.2 as u32;
+
+ PhysicalLayout {
+ magic_number: MAGIC_NUMBER,
+ version,
+ generator: header.generator,
+ bound: 0,
+ instruction_schema: 0x0u32,
+ }
+ }
+
+ pub(super) fn in_words(&self, sink: &mut impl Extend<Word>) {
+ sink.extend(iter::once(self.magic_number));
+ sink.extend(iter::once(self.version));
+ sink.extend(iter::once(self.generator));
+ sink.extend(iter::once(self.bound));
+ sink.extend(iter::once(self.instruction_schema));
+ }
+
+ pub(super) fn supports_storage_buffers(&self) -> bool {
+ self.version >= 0x10300
+ }
+}
+
+impl LogicalLayout {
+ pub(super) fn in_words(&self, sink: &mut impl Extend<Word>) {
+ sink.extend(self.capabilities.iter().cloned());
+ sink.extend(self.extensions.iter().cloned());
+ sink.extend(self.ext_inst_imports.iter().cloned());
+ sink.extend(self.memory_model.iter().cloned());
+ sink.extend(self.entry_points.iter().cloned());
+ sink.extend(self.execution_modes.iter().cloned());
+ sink.extend(self.debugs.iter().cloned());
+ sink.extend(self.annotations.iter().cloned());
+ sink.extend(self.declarations.iter().cloned());
+ sink.extend(self.function_declarations.iter().cloned());
+ sink.extend(self.function_definitions.iter().cloned());
+ }
+}
+
+impl Instruction {
+ pub(super) fn new(op: Op) -> Self {
+ Instruction {
+ op,
+ wc: 1, // Always start at 1 for the first word (OP + WC),
+ type_id: None,
+ result_id: None,
+ operands: vec![],
+ }
+ }
+
+ #[allow(clippy::panic)]
+ pub(super) fn set_type(&mut self, id: Word) {
+ assert!(self.type_id.is_none(), "Type can only be set once");
+ self.type_id = Some(id);
+ self.wc += 1;
+ }
+
+ #[allow(clippy::panic)]
+ pub(super) fn set_result(&mut self, id: Word) {
+ assert!(self.result_id.is_none(), "Result can only be set once");
+ self.result_id = Some(id);
+ self.wc += 1;
+ }
+
+ pub(super) fn add_operand(&mut self, operand: Word) {
+ self.operands.push(operand);
+ self.wc += 1;
+ }
+
+ pub(super) fn add_operands(&mut self, operands: Vec<Word>) {
+ for operand in operands.into_iter() {
+ self.add_operand(operand)
+ }
+ }
+
+ pub(super) fn to_words(&self, sink: &mut impl Extend<Word>) {
+ sink.extend(Some((self.wc << 16 | self.op as u32) as u32));
+ sink.extend(self.type_id);
+ sink.extend(self.result_id);
+ sink.extend(self.operands.iter().cloned());
+ }
+}
diff --git a/third_party/rust/naga/src/back/spv/layout_tests.rs b/third_party/rust/naga/src/back/spv/layout_tests.rs
new file mode 100644
index 0000000000..37024b238f
--- /dev/null
+++ b/third_party/rust/naga/src/back/spv/layout_tests.rs
@@ -0,0 +1,166 @@
+use crate::back::spv::test_framework::*;
+use crate::back::spv::{helpers, Instruction, LogicalLayout, PhysicalLayout};
+use crate::Header;
+use spirv::*;
+
+#[test]
+fn test_physical_layout_in_words() {
+ let header = Header {
+ generator: 0,
+ version: (1, 2, 3),
+ };
+ let bound = 5;
+
+ let mut output = vec![];
+ let mut layout = PhysicalLayout::new(&header);
+ layout.bound = bound;
+
+ layout.in_words(&mut output);
+
+ assert_eq!(output[0], spirv::MAGIC_NUMBER);
+ assert_eq!(
+ output[1],
+ to_word(&[header.version.0, header.version.1, header.version.2, 1])
+ );
+ assert_eq!(output[2], 0);
+ assert_eq!(output[3], bound);
+ assert_eq!(output[4], 0);
+}
+
+#[test]
+fn test_logical_layout_in_words() {
+ let mut output = vec![];
+ let mut layout = LogicalLayout::default();
+ let layout_vectors = 11;
+ let mut instructions = Vec::with_capacity(layout_vectors);
+
+ let vector_names = &[
+ "Capabilities",
+ "Extensions",
+ "External Instruction Imports",
+ "Memory Model",
+ "Entry Points",
+ "Execution Modes",
+ "Debugs",
+ "Annotations",
+ "Declarations",
+ "Function Declarations",
+ "Function Definitions",
+ ];
+
+ for i in 0..layout_vectors {
+ let mut dummy_instruction = Instruction::new(Op::Constant);
+ dummy_instruction.set_type((i + 1) as u32);
+ dummy_instruction.set_result((i + 2) as u32);
+ dummy_instruction.add_operand((i + 3) as u32);
+ dummy_instruction.add_operands(helpers::string_to_words(
+ format!("This is the vector: {}", vector_names[i]).as_str(),
+ ));
+ instructions.push(dummy_instruction);
+ }
+
+ instructions[0].to_words(&mut layout.capabilities);
+ instructions[1].to_words(&mut layout.extensions);
+ instructions[2].to_words(&mut layout.ext_inst_imports);
+ instructions[3].to_words(&mut layout.memory_model);
+ instructions[4].to_words(&mut layout.entry_points);
+ instructions[5].to_words(&mut layout.execution_modes);
+ instructions[6].to_words(&mut layout.debugs);
+ instructions[7].to_words(&mut layout.annotations);
+ instructions[8].to_words(&mut layout.declarations);
+ instructions[9].to_words(&mut layout.function_declarations);
+ instructions[10].to_words(&mut layout.function_definitions);
+
+ layout.in_words(&mut output);
+
+ let mut index: usize = 0;
+ for instruction in instructions {
+ let wc = instruction.wc as usize;
+ let instruction_output = &output[index..index + wc];
+ validate_instruction(instruction_output, &instruction);
+ index += wc;
+ }
+}
+
+#[test]
+fn test_instruction_set_type() {
+ let ty = 1;
+ let mut instruction = Instruction::new(Op::Constant);
+ assert_eq!(instruction.wc, 1);
+
+ instruction.set_type(ty);
+ assert_eq!(instruction.type_id.unwrap(), ty);
+ assert_eq!(instruction.wc, 2);
+}
+
+#[test]
+#[should_panic]
+fn test_instruction_set_type_twice() {
+ let ty = 1;
+ let mut instruction = Instruction::new(Op::Constant);
+ instruction.set_type(ty);
+ instruction.set_type(ty);
+}
+
+#[test]
+fn test_instruction_set_result() {
+ let result = 1;
+ let mut instruction = Instruction::new(Op::Constant);
+ assert_eq!(instruction.wc, 1);
+
+ instruction.set_result(result);
+ assert_eq!(instruction.result_id.unwrap(), result);
+ assert_eq!(instruction.wc, 2);
+}
+
+#[test]
+#[should_panic]
+fn test_instruction_set_result_twice() {
+ let result = 1;
+ let mut instruction = Instruction::new(Op::Constant);
+ instruction.set_result(result);
+ instruction.set_result(result);
+}
+
+#[test]
+fn test_instruction_add_operand() {
+ let operand = 1;
+ let mut instruction = Instruction::new(Op::Constant);
+ assert_eq!(instruction.operands.len(), 0);
+ assert_eq!(instruction.wc, 1);
+
+ instruction.add_operand(operand);
+ assert_eq!(instruction.operands.len(), 1);
+ assert_eq!(instruction.wc, 2);
+}
+
+#[test]
+fn test_instruction_add_operands() {
+ let operands = vec![1, 2, 3];
+ let mut instruction = Instruction::new(Op::Constant);
+ assert_eq!(instruction.operands.len(), 0);
+ assert_eq!(instruction.wc, 1);
+
+ instruction.add_operands(operands);
+ assert_eq!(instruction.operands.len(), 3);
+ assert_eq!(instruction.wc, 4);
+}
+
+#[test]
+fn test_instruction_to_words() {
+ let ty = 1;
+ let result = 2;
+ let operand = 3;
+ let mut instruction = Instruction::new(Op::Constant);
+ instruction.set_type(ty);
+ instruction.set_result(result);
+ instruction.add_operand(operand);
+
+ let mut output = vec![];
+ instruction.to_words(&mut output);
+ validate_instruction(output.as_slice(), &instruction);
+}
+
+fn to_word(bytes: &[u8]) -> Word {
+ ((bytes[0] as u32) << 16) | ((bytes[1] as u32) << 8) | bytes[2] as u32
+}
diff --git a/third_party/rust/naga/src/back/spv/mod.rs b/third_party/rust/naga/src/back/spv/mod.rs
new file mode 100644
index 0000000000..15f1598357
--- /dev/null
+++ b/third_party/rust/naga/src/back/spv/mod.rs
@@ -0,0 +1,52 @@
+mod helpers;
+mod instructions;
+mod layout;
+mod writer;
+
+#[cfg(test)]
+mod test_framework;
+
+#[cfg(test)]
+mod layout_tests;
+
+pub use writer::Writer;
+
+use spirv::*;
+
+bitflags::bitflags! {
+ pub struct WriterFlags: u32 {
+ const NONE = 0x0;
+ const DEBUG = 0x1;
+ }
+}
+
+struct PhysicalLayout {
+ magic_number: Word,
+ version: Word,
+ generator: Word,
+ bound: Word,
+ instruction_schema: Word,
+}
+
+#[derive(Default)]
+struct LogicalLayout {
+ capabilities: Vec<Word>,
+ extensions: Vec<Word>,
+ ext_inst_imports: Vec<Word>,
+ memory_model: Vec<Word>,
+ entry_points: Vec<Word>,
+ execution_modes: Vec<Word>,
+ debugs: Vec<Word>,
+ annotations: Vec<Word>,
+ declarations: Vec<Word>,
+ function_declarations: Vec<Word>,
+ function_definitions: Vec<Word>,
+}
+
+pub(self) struct Instruction {
+ op: Op,
+ wc: u32,
+ type_id: Option<Word>,
+ result_id: Option<Word>,
+ operands: Vec<Word>,
+}
diff --git a/third_party/rust/naga/src/back/spv/test_framework.rs b/third_party/rust/naga/src/back/spv/test_framework.rs
new file mode 100644
index 0000000000..be2fa74fe1
--- /dev/null
+++ b/third_party/rust/naga/src/back/spv/test_framework.rs
@@ -0,0 +1,27 @@
+pub(super) fn validate_instruction(
+ words: &[spirv::Word],
+ instruction: &crate::back::spv::Instruction,
+) {
+ let mut inst_index = 0;
+ let (wc, op) = ((words[inst_index] >> 16) as u16, words[inst_index] as u16);
+ inst_index += 1;
+
+ assert_eq!(wc, words.len() as u16);
+ assert_eq!(op, instruction.op as u16);
+
+ if instruction.type_id.is_some() {
+ assert_eq!(words[inst_index], instruction.type_id.unwrap());
+ inst_index += 1;
+ }
+
+ if instruction.result_id.is_some() {
+ assert_eq!(words[inst_index], instruction.result_id.unwrap());
+ inst_index += 1;
+ }
+
+ let mut op_index = 0;
+ for i in inst_index..wc as usize {
+ assert_eq!(words[i as usize], instruction.operands[op_index]);
+ op_index += 1;
+ }
+}
diff --git a/third_party/rust/naga/src/back/spv/writer.rs b/third_party/rust/naga/src/back/spv/writer.rs
new file mode 100644
index 0000000000..f1b43f5289
--- /dev/null
+++ b/third_party/rust/naga/src/back/spv/writer.rs
@@ -0,0 +1,1776 @@
+/*! Standard Portable Intermediate Representation (SPIR-V) backend !*/
+use super::{Instruction, LogicalLayout, PhysicalLayout, WriterFlags};
+use spirv::Word;
+use std::{collections::hash_map::Entry, ops};
+use thiserror::Error;
+
+const BITS_PER_BYTE: crate::Bytes = 8;
+
+#[derive(Clone, Debug, Error)]
+pub enum Error {
+ #[error("can't find local variable: {0:?}")]
+ UnknownLocalVariable(crate::LocalVariable),
+ #[error("bad image class for op: {0:?}")]
+ BadImageClass(crate::ImageClass),
+ #[error("not an image")]
+ NotImage,
+ #[error("empty value")]
+ FeatureNotImplemented(),
+}
+
+struct Block {
+ label: Option<Instruction>,
+ body: Vec<Instruction>,
+ termination: Option<Instruction>,
+}
+
+impl Block {
+ pub fn new() -> Self {
+ Block {
+ label: None,
+ body: vec![],
+ termination: None,
+ }
+ }
+}
+
+struct LocalVariable {
+ id: Word,
+ name: Option<String>,
+ instruction: Instruction,
+}
+
+struct Function {
+ signature: Option<Instruction>,
+ parameters: Vec<Instruction>,
+ variables: Vec<LocalVariable>,
+ blocks: Vec<Block>,
+}
+
+impl Function {
+ pub fn new() -> Self {
+ Function {
+ signature: None,
+ parameters: vec![],
+ variables: vec![],
+ blocks: vec![],
+ }
+ }
+
+ fn to_words(&self, sink: &mut impl Extend<Word>) {
+ self.signature.as_ref().unwrap().to_words(sink);
+ for instruction in self.parameters.iter() {
+ instruction.to_words(sink);
+ }
+ for (index, block) in self.blocks.iter().enumerate() {
+ block.label.as_ref().unwrap().to_words(sink);
+ if index == 0 {
+ for local_var in self.variables.iter() {
+ local_var.instruction.to_words(sink);
+ }
+ }
+ for instruction in block.body.iter() {
+ instruction.to_words(sink);
+ }
+ block.termination.as_ref().unwrap().to_words(sink);
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Hash, Eq, Copy, Clone)]
+enum LocalType {
+ Void,
+ Scalar {
+ kind: crate::ScalarKind,
+ width: crate::Bytes,
+ },
+ Vector {
+ size: crate::VectorSize,
+ kind: crate::ScalarKind,
+ width: crate::Bytes,
+ },
+ Pointer {
+ base: crate::Handle<crate::Type>,
+ class: crate::StorageClass,
+ },
+ SampledImage {
+ image_type: crate::Handle<crate::Type>,
+ },
+}
+
+#[derive(Debug, PartialEq, Hash, Eq, Copy, Clone)]
+enum LookupType {
+ Handle(crate::Handle<crate::Type>),
+ Local(LocalType),
+}
+
+fn map_dim(dim: crate::ImageDimension) -> spirv::Dim {
+ match dim {
+ crate::ImageDimension::D1 => spirv::Dim::Dim1D,
+ crate::ImageDimension::D2 => spirv::Dim::Dim2D,
+ crate::ImageDimension::D3 => spirv::Dim::Dim2D,
+ crate::ImageDimension::Cube => spirv::Dim::DimCube,
+ }
+}
+
+#[derive(Debug, PartialEq, Clone, Hash, Eq)]
+struct LookupFunctionType {
+ parameter_type_ids: Vec<Word>,
+ return_type_id: Word,
+}
+
+enum MaybeOwned<'a, T> {
+ Owned(T),
+ Borrowed(&'a T),
+}
+
+impl<'a, T> ops::Deref for MaybeOwned<'a, T> {
+ type Target = T;
+ fn deref(&self) -> &T {
+ match *self {
+ MaybeOwned::Owned(ref value) => value,
+ MaybeOwned::Borrowed(reference) => reference,
+ }
+ }
+}
+
+enum Dimension {
+ Scalar,
+ Vector,
+ Matrix,
+}
+
+fn get_dimension(ty_inner: &crate::TypeInner) -> Dimension {
+ match *ty_inner {
+ crate::TypeInner::Scalar { .. } => Dimension::Scalar,
+ crate::TypeInner::Vector { .. } => Dimension::Vector,
+ crate::TypeInner::Matrix { .. } => Dimension::Matrix,
+ _ => unreachable!(),
+ }
+}
+
+pub struct Writer {
+ physical_layout: PhysicalLayout,
+ logical_layout: LogicalLayout,
+ id_count: u32,
+ capabilities: crate::FastHashSet<spirv::Capability>,
+ debugs: Vec<Instruction>,
+ annotations: Vec<Instruction>,
+ writer_flags: WriterFlags,
+ void_type: Option<u32>,
+ lookup_type: crate::FastHashMap<LookupType, Word>,
+ lookup_function: crate::FastHashMap<crate::Handle<crate::Function>, Word>,
+ lookup_function_type: crate::FastHashMap<LookupFunctionType, Word>,
+ lookup_constant: crate::FastHashMap<crate::Handle<crate::Constant>, Word>,
+ lookup_global_variable: crate::FastHashMap<crate::Handle<crate::GlobalVariable>, Word>,
+}
+
+// type alias, for success return of write_expression
+type WriteExpressionOutput = (Word, LookupType);
+
+impl Writer {
+ pub fn new(header: &crate::Header, writer_flags: WriterFlags) -> Self {
+ Writer {
+ physical_layout: PhysicalLayout::new(header),
+ logical_layout: LogicalLayout::default(),
+ id_count: 0,
+ capabilities: crate::FastHashSet::default(),
+ debugs: vec![],
+ annotations: vec![],
+ writer_flags,
+ void_type: None,
+ lookup_type: crate::FastHashMap::default(),
+ lookup_function: crate::FastHashMap::default(),
+ lookup_function_type: crate::FastHashMap::default(),
+ lookup_constant: crate::FastHashMap::default(),
+ lookup_global_variable: crate::FastHashMap::default(),
+ }
+ }
+
+ fn generate_id(&mut self) -> Word {
+ self.id_count += 1;
+ self.id_count
+ }
+
+ fn try_add_capabilities(&mut self, capabilities: &[spirv::Capability]) {
+ for capability in capabilities.iter() {
+ self.capabilities.insert(*capability);
+ }
+ }
+
+ fn get_type_id(&mut self, arena: &crate::Arena<crate::Type>, lookup_ty: LookupType) -> Word {
+ if let Entry::Occupied(e) = self.lookup_type.entry(lookup_ty) {
+ *e.get()
+ } else {
+ match lookup_ty {
+ LookupType::Handle(handle) => match arena[handle].inner {
+ crate::TypeInner::Scalar { kind, width } => self
+ .get_type_id(arena, LookupType::Local(LocalType::Scalar { kind, width })),
+ _ => self.write_type_declaration_arena(arena, handle),
+ },
+ LookupType::Local(local_ty) => self.write_type_declaration_local(arena, local_ty),
+ }
+ }
+ }
+
+ fn get_constant_id(
+ &mut self,
+ handle: crate::Handle<crate::Constant>,
+ ir_module: &crate::Module,
+ ) -> Word {
+ match self.lookup_constant.entry(handle) {
+ Entry::Occupied(e) => *e.get(),
+ _ => {
+ let (instruction, id) = self.write_constant_type(handle, ir_module);
+ instruction.to_words(&mut self.logical_layout.declarations);
+ id
+ }
+ }
+ }
+
+ fn get_global_variable_id(
+ &mut self,
+ ir_module: &crate::Module,
+ handle: crate::Handle<crate::GlobalVariable>,
+ ) -> Word {
+ match self.lookup_global_variable.entry(handle) {
+ Entry::Occupied(e) => *e.get(),
+ _ => {
+ let (instruction, id) = self.write_global_variable(ir_module, handle);
+ instruction.to_words(&mut self.logical_layout.declarations);
+ id
+ }
+ }
+ }
+
+ fn get_function_return_type(
+ &mut self,
+ ty: Option<crate::Handle<crate::Type>>,
+ arena: &crate::Arena<crate::Type>,
+ ) -> Word {
+ match ty {
+ Some(handle) => self.get_type_id(arena, LookupType::Handle(handle)),
+ None => match self.void_type {
+ Some(id) => id,
+ None => {
+ let id = self.generate_id();
+ self.void_type = Some(id);
+ super::instructions::instruction_type_void(id)
+ .to_words(&mut self.logical_layout.declarations);
+ id
+ }
+ },
+ }
+ }
+
+ fn get_pointer_id(
+ &mut self,
+ arena: &crate::Arena<crate::Type>,
+ handle: crate::Handle<crate::Type>,
+ class: crate::StorageClass,
+ ) -> Word {
+ let ty = &arena[handle];
+ let ty_id = self.get_type_id(arena, LookupType::Handle(handle));
+ match ty.inner {
+ crate::TypeInner::Pointer { .. } => ty_id,
+ _ => {
+ match self
+ .lookup_type
+ .entry(LookupType::Local(LocalType::Pointer {
+ base: handle,
+ class,
+ })) {
+ Entry::Occupied(e) => *e.get(),
+ _ => {
+ let id =
+ self.create_pointer(ty_id, self.parse_to_spirv_storage_class(class));
+ self.lookup_type.insert(
+ LookupType::Local(LocalType::Pointer {
+ base: handle,
+ class,
+ }),
+ id,
+ );
+ id
+ }
+ }
+ }
+ }
+ }
+
+ fn create_pointer(&mut self, ty_id: Word, class: spirv::StorageClass) -> Word {
+ let id = self.generate_id();
+ let instruction = super::instructions::instruction_type_pointer(id, class, ty_id);
+ instruction.to_words(&mut self.logical_layout.declarations);
+ id
+ }
+
+ fn create_constant(&mut self, type_id: Word, value: &[Word]) -> Word {
+ let id = self.generate_id();
+ let instruction = super::instructions::instruction_constant(type_id, id, value);
+ instruction.to_words(&mut self.logical_layout.declarations);
+ id
+ }
+
+ fn write_function(
+ &mut self,
+ ir_function: &crate::Function,
+ ir_module: &crate::Module,
+ ) -> spirv::Word {
+ let mut function = Function::new();
+
+ for (_, variable) in ir_function.local_variables.iter() {
+ let id = self.generate_id();
+
+ let init_word = variable
+ .init
+ .map(|constant| self.get_constant_id(constant, ir_module));
+
+ let pointer_id =
+ self.get_pointer_id(&ir_module.types, variable.ty, crate::StorageClass::Function);
+ function.variables.push(LocalVariable {
+ id,
+ name: variable.name.clone(),
+ instruction: super::instructions::instruction_variable(
+ pointer_id,
+ id,
+ spirv::StorageClass::Function,
+ init_word,
+ ),
+ });
+ }
+
+ let return_type_id =
+ self.get_function_return_type(ir_function.return_type, &ir_module.types);
+ let mut parameter_type_ids = Vec::with_capacity(ir_function.arguments.len());
+
+ let mut function_parameter_pointer_ids = vec![];
+
+ for argument in ir_function.arguments.iter() {
+ let id = self.generate_id();
+ let pointer_id =
+ self.get_pointer_id(&ir_module.types, argument.ty, crate::StorageClass::Function);
+
+ function_parameter_pointer_ids.push(pointer_id);
+ parameter_type_ids
+ .push(self.get_type_id(&ir_module.types, LookupType::Handle(argument.ty)));
+ function
+ .parameters
+ .push(super::instructions::instruction_function_parameter(
+ pointer_id, id,
+ ));
+ }
+
+ let lookup_function_type = LookupFunctionType {
+ return_type_id,
+ parameter_type_ids,
+ };
+
+ let function_id = self.generate_id();
+ let function_type =
+ self.get_function_type(lookup_function_type, function_parameter_pointer_ids);
+ function.signature = Some(super::instructions::instruction_function(
+ return_type_id,
+ function_id,
+ spirv::FunctionControl::empty(),
+ function_type,
+ ));
+
+ self.write_block(&ir_function.body, ir_module, ir_function, &mut function);
+
+ function.to_words(&mut self.logical_layout.function_definitions);
+ super::instructions::instruction_function_end()
+ .to_words(&mut self.logical_layout.function_definitions);
+
+ function_id
+ }
+
+ // TODO Move to instructions module
+ fn write_entry_point(
+ &mut self,
+ entry_point: &crate::EntryPoint,
+ stage: crate::ShaderStage,
+ name: &str,
+ ir_module: &crate::Module,
+ ) -> Instruction {
+ let function_id = self.write_function(&entry_point.function, ir_module);
+
+ let exec_model = match stage {
+ crate::ShaderStage::Vertex => spirv::ExecutionModel::Vertex,
+ crate::ShaderStage::Fragment { .. } => spirv::ExecutionModel::Fragment,
+ crate::ShaderStage::Compute { .. } => spirv::ExecutionModel::GLCompute,
+ };
+
+ let mut interface_ids = vec![];
+ for ((handle, _), &usage) in ir_module
+ .global_variables
+ .iter()
+ .filter(|&(_, var)| {
+ var.class == crate::StorageClass::Input || var.class == crate::StorageClass::Output
+ })
+ .zip(&entry_point.function.global_usage)
+ {
+ if usage.contains(crate::GlobalUse::STORE) || usage.contains(crate::GlobalUse::LOAD) {
+ let id = self.get_global_variable_id(ir_module, handle);
+ interface_ids.push(id);
+ }
+ }
+
+ self.try_add_capabilities(exec_model.required_capabilities());
+ match stage {
+ crate::ShaderStage::Vertex => {}
+ crate::ShaderStage::Fragment => {
+ let execution_mode = spirv::ExecutionMode::OriginUpperLeft;
+ self.try_add_capabilities(execution_mode.required_capabilities());
+ super::instructions::instruction_execution_mode(function_id, execution_mode)
+ .to_words(&mut self.logical_layout.execution_modes);
+ }
+ crate::ShaderStage::Compute => {}
+ }
+
+ if self.writer_flags.contains(WriterFlags::DEBUG) {
+ self.debugs
+ .push(super::instructions::instruction_name(function_id, name));
+ }
+
+ super::instructions::instruction_entry_point(
+ exec_model,
+ function_id,
+ name,
+ interface_ids.as_slice(),
+ )
+ }
+
+ fn write_scalar(&self, id: Word, kind: crate::ScalarKind, width: crate::Bytes) -> Instruction {
+ let bits = (width * BITS_PER_BYTE) as u32;
+ match kind {
+ crate::ScalarKind::Sint => super::instructions::instruction_type_int(
+ id,
+ bits,
+ super::instructions::Signedness::Signed,
+ ),
+ crate::ScalarKind::Uint => super::instructions::instruction_type_int(
+ id,
+ bits,
+ super::instructions::Signedness::Unsigned,
+ ),
+ crate::ScalarKind::Float => super::instructions::instruction_type_float(id, bits),
+ crate::ScalarKind::Bool => super::instructions::instruction_type_bool(id),
+ }
+ }
+
+ fn parse_to_spirv_storage_class(&self, class: crate::StorageClass) -> spirv::StorageClass {
+ match class {
+ crate::StorageClass::Handle => spirv::StorageClass::UniformConstant,
+ crate::StorageClass::Function => spirv::StorageClass::Function,
+ crate::StorageClass::Input => spirv::StorageClass::Input,
+ crate::StorageClass::Output => spirv::StorageClass::Output,
+ crate::StorageClass::Private => spirv::StorageClass::Private,
+ crate::StorageClass::Storage if self.physical_layout.supports_storage_buffers() => {
+ spirv::StorageClass::StorageBuffer
+ }
+ crate::StorageClass::Storage | crate::StorageClass::Uniform => {
+ spirv::StorageClass::Uniform
+ }
+ crate::StorageClass::WorkGroup => spirv::StorageClass::Workgroup,
+ crate::StorageClass::PushConstant => spirv::StorageClass::PushConstant,
+ }
+ }
+
+ fn write_type_declaration_local(
+ &mut self,
+ arena: &crate::Arena<crate::Type>,
+ local_ty: LocalType,
+ ) -> Word {
+ let id = self.generate_id();
+ let instruction = match local_ty {
+ LocalType::Void => unreachable!(),
+ LocalType::Scalar { kind, width } => self.write_scalar(id, kind, width),
+ LocalType::Vector { size, kind, width } => {
+ let scalar_id =
+ self.get_type_id(arena, LookupType::Local(LocalType::Scalar { kind, width }));
+ super::instructions::instruction_type_vector(id, scalar_id, size)
+ }
+ LocalType::Pointer { .. } => unimplemented!(),
+ LocalType::SampledImage { image_type } => {
+ let image_type_id = self.get_type_id(arena, LookupType::Handle(image_type));
+ super::instructions::instruction_type_sampled_image(id, image_type_id)
+ }
+ };
+
+ self.lookup_type.insert(LookupType::Local(local_ty), id);
+ instruction.to_words(&mut self.logical_layout.declarations);
+ id
+ }
+
+ fn write_type_declaration_arena(
+ &mut self,
+ arena: &crate::Arena<crate::Type>,
+ handle: crate::Handle<crate::Type>,
+ ) -> Word {
+ let ty = &arena[handle];
+ let id = self.generate_id();
+
+ let instruction = match ty.inner {
+ crate::TypeInner::Scalar { kind, width } => {
+ self.lookup_type
+ .insert(LookupType::Local(LocalType::Scalar { kind, width }), id);
+ self.write_scalar(id, kind, width)
+ }
+ crate::TypeInner::Vector { size, kind, width } => {
+ let scalar_id =
+ self.get_type_id(arena, LookupType::Local(LocalType::Scalar { kind, width }));
+ self.lookup_type.insert(
+ LookupType::Local(LocalType::Vector { size, kind, width }),
+ id,
+ );
+ super::instructions::instruction_type_vector(id, scalar_id, size)
+ }
+ crate::TypeInner::Matrix {
+ columns,
+ rows: _,
+ width,
+ } => {
+ let vector_id = self.get_type_id(
+ arena,
+ LookupType::Local(LocalType::Vector {
+ size: columns,
+ kind: crate::ScalarKind::Float,
+ width,
+ }),
+ );
+ super::instructions::instruction_type_matrix(id, vector_id, columns)
+ }
+ crate::TypeInner::Image {
+ dim,
+ arrayed,
+ class,
+ } => {
+ let width = 4;
+ let local_type = match class {
+ crate::ImageClass::Sampled { kind, multi: _ } => {
+ LocalType::Scalar { kind, width }
+ }
+ crate::ImageClass::Depth => LocalType::Scalar {
+ kind: crate::ScalarKind::Float,
+ width,
+ },
+ crate::ImageClass::Storage(format) => LocalType::Scalar {
+ kind: format.into(),
+ width,
+ },
+ };
+ let type_id = self.get_type_id(arena, LookupType::Local(local_type));
+ let dim = map_dim(dim);
+ self.try_add_capabilities(dim.required_capabilities());
+ super::instructions::instruction_type_image(id, type_id, dim, arrayed, class)
+ }
+ crate::TypeInner::Sampler { comparison: _ } => {
+ super::instructions::instruction_type_sampler(id)
+ }
+ crate::TypeInner::Array { base, size, stride } => {
+ if let Some(array_stride) = stride {
+ self.annotations
+ .push(super::instructions::instruction_decorate(
+ id,
+ spirv::Decoration::ArrayStride,
+ &[array_stride.get()],
+ ));
+ }
+
+ let type_id = self.get_type_id(arena, LookupType::Handle(base));
+ match size {
+ crate::ArraySize::Constant(const_handle) => {
+ let length_id = self.lookup_constant[&const_handle];
+ super::instructions::instruction_type_array(id, type_id, length_id)
+ }
+ crate::ArraySize::Dynamic => {
+ super::instructions::instruction_type_runtime_array(id, type_id)
+ }
+ }
+ }
+ crate::TypeInner::Struct { ref members } => {
+ let mut member_ids = Vec::with_capacity(members.len());
+ for member in members {
+ let member_id = self.get_type_id(arena, LookupType::Handle(member.ty));
+ member_ids.push(member_id);
+ }
+ super::instructions::instruction_type_struct(id, member_ids.as_slice())
+ }
+ crate::TypeInner::Pointer { base, class } => {
+ let type_id = self.get_type_id(arena, LookupType::Handle(base));
+ self.lookup_type
+ .insert(LookupType::Local(LocalType::Pointer { base, class }), id);
+ super::instructions::instruction_type_pointer(
+ id,
+ self.parse_to_spirv_storage_class(class),
+ type_id,
+ )
+ }
+ };
+
+ self.lookup_type.insert(LookupType::Handle(handle), id);
+ instruction.to_words(&mut self.logical_layout.declarations);
+ id
+ }
+
+ fn write_constant_type(
+ &mut self,
+ handle: crate::Handle<crate::Constant>,
+ ir_module: &crate::Module,
+ ) -> (Instruction, Word) {
+ let id = self.generate_id();
+ self.lookup_constant.insert(handle, id);
+ let constant = &ir_module.constants[handle];
+ let arena = &ir_module.types;
+
+ match constant.inner {
+ crate::ConstantInner::Sint(val) => {
+ let ty = &ir_module.types[constant.ty];
+ let type_id = self.get_type_id(arena, LookupType::Handle(constant.ty));
+
+ let instruction = match ty.inner {
+ crate::TypeInner::Scalar { kind: _, width } => match width {
+ 4 => super::instructions::instruction_constant(type_id, id, &[val as u32]),
+ 8 => {
+ let (low, high) = ((val >> 32) as u32, val as u32);
+ super::instructions::instruction_constant(type_id, id, &[low, high])
+ }
+ _ => unreachable!(),
+ },
+ _ => unreachable!(),
+ };
+ (instruction, id)
+ }
+ crate::ConstantInner::Uint(val) => {
+ let ty = &ir_module.types[constant.ty];
+ let type_id = self.get_type_id(arena, LookupType::Handle(constant.ty));
+
+ let instruction = match ty.inner {
+ crate::TypeInner::Scalar { kind: _, width } => match width {
+ 4 => super::instructions::instruction_constant(type_id, id, &[val as u32]),
+ 8 => {
+ let (low, high) = ((val >> 32) as u32, val as u32);
+ super::instructions::instruction_constant(type_id, id, &[low, high])
+ }
+ _ => unreachable!(),
+ },
+ _ => unreachable!(),
+ };
+
+ (instruction, id)
+ }
+ crate::ConstantInner::Float(val) => {
+ let ty = &ir_module.types[constant.ty];
+ let type_id = self.get_type_id(arena, LookupType::Handle(constant.ty));
+
+ let instruction = match ty.inner {
+ crate::TypeInner::Scalar { kind: _, width } => match width {
+ 4 => super::instructions::instruction_constant(
+ type_id,
+ id,
+ &[(val as f32).to_bits()],
+ ),
+ 8 => {
+ let bits = f64::to_bits(val);
+ let (low, high) = ((bits >> 32) as u32, bits as u32);
+ super::instructions::instruction_constant(type_id, id, &[low, high])
+ }
+ _ => unreachable!(),
+ },
+ _ => unreachable!(),
+ };
+ (instruction, id)
+ }
+ crate::ConstantInner::Bool(val) => {
+ let type_id = self.get_type_id(arena, LookupType::Handle(constant.ty));
+
+ let instruction = if val {
+ super::instructions::instruction_constant_true(type_id, id)
+ } else {
+ super::instructions::instruction_constant_false(type_id, id)
+ };
+
+ (instruction, id)
+ }
+ crate::ConstantInner::Composite(ref constituents) => {
+ let mut constituent_ids = Vec::with_capacity(constituents.len());
+ for constituent in constituents.iter() {
+ let constituent_id = self.get_constant_id(*constituent, &ir_module);
+ constituent_ids.push(constituent_id);
+ }
+
+ let type_id = self.get_type_id(arena, LookupType::Handle(constant.ty));
+ let instruction = super::instructions::instruction_constant_composite(
+ type_id,
+ id,
+ constituent_ids.as_slice(),
+ );
+ (instruction, id)
+ }
+ }
+ }
+
+ fn write_global_variable(
+ &mut self,
+ ir_module: &crate::Module,
+ handle: crate::Handle<crate::GlobalVariable>,
+ ) -> (Instruction, Word) {
+ let global_variable = &ir_module.global_variables[handle];
+ let id = self.generate_id();
+
+ let class = self.parse_to_spirv_storage_class(global_variable.class);
+ self.try_add_capabilities(class.required_capabilities());
+
+ let init_word = global_variable
+ .init
+ .map(|constant| self.get_constant_id(constant, ir_module));
+ let pointer_id =
+ self.get_pointer_id(&ir_module.types, global_variable.ty, global_variable.class);
+ let instruction =
+ super::instructions::instruction_variable(pointer_id, id, class, init_word);
+
+ if self.writer_flags.contains(WriterFlags::DEBUG) {
+ if let Some(ref name) = global_variable.name {
+ self.debugs
+ .push(super::instructions::instruction_name(id, name.as_str()));
+ }
+ }
+
+ if let Some(interpolation) = global_variable.interpolation {
+ let decoration = match interpolation {
+ crate::Interpolation::Linear => Some(spirv::Decoration::NoPerspective),
+ crate::Interpolation::Flat => Some(spirv::Decoration::Flat),
+ crate::Interpolation::Patch => Some(spirv::Decoration::Patch),
+ crate::Interpolation::Centroid => Some(spirv::Decoration::Centroid),
+ crate::Interpolation::Sample => Some(spirv::Decoration::Sample),
+ crate::Interpolation::Perspective => None,
+ };
+ if let Some(decoration) = decoration {
+ self.annotations
+ .push(super::instructions::instruction_decorate(
+ id,
+ decoration,
+ &[],
+ ));
+ }
+ }
+
+ match *global_variable.binding.as_ref().unwrap() {
+ crate::Binding::Location(location) => {
+ self.annotations
+ .push(super::instructions::instruction_decorate(
+ id,
+ spirv::Decoration::Location,
+ &[location],
+ ));
+ }
+ crate::Binding::Resource { group, binding } => {
+ self.annotations
+ .push(super::instructions::instruction_decorate(
+ id,
+ spirv::Decoration::DescriptorSet,
+ &[group],
+ ));
+ self.annotations
+ .push(super::instructions::instruction_decorate(
+ id,
+ spirv::Decoration::Binding,
+ &[binding],
+ ));
+ }
+ crate::Binding::BuiltIn(built_in) => {
+ let built_in = match built_in {
+ crate::BuiltIn::BaseInstance => spirv::BuiltIn::BaseInstance,
+ crate::BuiltIn::BaseVertex => spirv::BuiltIn::BaseVertex,
+ crate::BuiltIn::ClipDistance => spirv::BuiltIn::ClipDistance,
+ crate::BuiltIn::InstanceIndex => spirv::BuiltIn::InstanceIndex,
+ crate::BuiltIn::Position => spirv::BuiltIn::Position,
+ crate::BuiltIn::VertexIndex => spirv::BuiltIn::VertexIndex,
+ crate::BuiltIn::PointSize => spirv::BuiltIn::PointSize,
+ crate::BuiltIn::FragCoord => spirv::BuiltIn::FragCoord,
+ crate::BuiltIn::FrontFacing => spirv::BuiltIn::FrontFacing,
+ crate::BuiltIn::SampleIndex => spirv::BuiltIn::SampleId,
+ crate::BuiltIn::FragDepth => spirv::BuiltIn::FragDepth,
+ crate::BuiltIn::GlobalInvocationId => spirv::BuiltIn::GlobalInvocationId,
+ crate::BuiltIn::LocalInvocationId => spirv::BuiltIn::LocalInvocationId,
+ crate::BuiltIn::LocalInvocationIndex => spirv::BuiltIn::LocalInvocationIndex,
+ crate::BuiltIn::WorkGroupId => spirv::BuiltIn::WorkgroupId,
+ };
+
+ self.annotations
+ .push(super::instructions::instruction_decorate(
+ id,
+ spirv::Decoration::BuiltIn,
+ &[built_in as u32],
+ ));
+ }
+ }
+
+ // TODO Initializer is optional and not (yet) included in the IR
+
+ self.lookup_global_variable.insert(handle, id);
+ (instruction, id)
+ }
+
+ fn get_function_type(
+ &mut self,
+ lookup_function_type: LookupFunctionType,
+ parameter_pointer_ids: Vec<Word>,
+ ) -> Word {
+ match self
+ .lookup_function_type
+ .entry(lookup_function_type.clone())
+ {
+ Entry::Occupied(e) => *e.get(),
+ _ => {
+ let id = self.generate_id();
+ let instruction = super::instructions::instruction_type_function(
+ id,
+ lookup_function_type.return_type_id,
+ parameter_pointer_ids.as_slice(),
+ );
+ instruction.to_words(&mut self.logical_layout.declarations);
+ self.lookup_function_type.insert(lookup_function_type, id);
+ id
+ }
+ }
+ }
+
+ fn write_composite_construct(
+ &mut self,
+ base_type_id: Word,
+ constituent_ids: &[Word],
+ block: &mut Block,
+ ) -> Word {
+ let id = self.generate_id();
+ block
+ .body
+ .push(super::instructions::instruction_composite_construct(
+ base_type_id,
+ id,
+ constituent_ids,
+ ));
+ id
+ }
+
+ fn get_type_inner<'a>(
+ &self,
+ ty_arena: &'a crate::Arena<crate::Type>,
+ lookup_ty: LookupType,
+ ) -> MaybeOwned<'a, crate::TypeInner> {
+ match lookup_ty {
+ LookupType::Handle(handle) => MaybeOwned::Borrowed(&ty_arena[handle].inner),
+ LookupType::Local(local_ty) => match local_ty {
+ LocalType::Scalar { kind, width } => {
+ MaybeOwned::Owned(crate::TypeInner::Scalar { kind, width })
+ }
+ LocalType::Vector { size, kind, width } => {
+ MaybeOwned::Owned(crate::TypeInner::Vector { size, kind, width })
+ }
+ LocalType::Pointer { base, class } => {
+ MaybeOwned::Owned(crate::TypeInner::Pointer { base, class })
+ }
+ _ => unreachable!(),
+ },
+ }
+ }
+
+ fn write_expression<'a>(
+ &mut self,
+ ir_module: &'a crate::Module,
+ ir_function: &crate::Function,
+ expression: &crate::Expression,
+ block: &mut Block,
+ function: &mut Function,
+ ) -> Result<WriteExpressionOutput, Error> {
+ match *expression {
+ crate::Expression::Access { base, index } => {
+ let id = self.generate_id();
+
+ let (base_id, base_lookup_ty) = self.write_expression(
+ ir_module,
+ ir_function,
+ &ir_function.expressions[base],
+ block,
+ function,
+ )?;
+ let (index_id, _) = self.write_expression(
+ ir_module,
+ ir_function,
+ &ir_function.expressions[index],
+ block,
+ function,
+ )?;
+
+ let base_ty_inner = self.get_type_inner(&ir_module.types, base_lookup_ty);
+
+ let (pointer_id, type_id, lookup_ty) = match *base_ty_inner {
+ crate::TypeInner::Vector { kind, width, .. } => {
+ let scalar_id = self.get_type_id(
+ &ir_module.types,
+ LookupType::Local(LocalType::Scalar { kind, width }),
+ );
+ (
+ self.create_pointer(scalar_id, spirv::StorageClass::Function),
+ scalar_id,
+ LookupType::Local(LocalType::Scalar { kind, width }),
+ )
+ }
+ _ => unimplemented!(),
+ };
+
+ block
+ .body
+ .push(super::instructions::instruction_access_chain(
+ pointer_id,
+ id,
+ base_id,
+ &[index_id],
+ ));
+
+ let load_id = self.generate_id();
+ block.body.push(super::instructions::instruction_load(
+ type_id, load_id, id, None,
+ ));
+
+ Ok((load_id, lookup_ty))
+ }
+ crate::Expression::AccessIndex { base, index } => {
+ let id = self.generate_id();
+ let (base_id, base_lookup_ty) = self
+ .write_expression(
+ ir_module,
+ ir_function,
+ &ir_function.expressions[base],
+ block,
+ function,
+ )
+ .unwrap();
+
+ let base_ty_inner = self.get_type_inner(&ir_module.types, base_lookup_ty);
+
+ let (pointer_id, type_id, lookup_ty) = match *base_ty_inner {
+ crate::TypeInner::Vector { kind, width, .. } => {
+ let scalar_id = self.get_type_id(
+ &ir_module.types,
+ LookupType::Local(LocalType::Scalar { kind, width }),
+ );
+ (
+ self.create_pointer(scalar_id, spirv::StorageClass::Function),
+ scalar_id,
+ LookupType::Local(LocalType::Scalar { kind, width }),
+ )
+ }
+ crate::TypeInner::Struct { ref members } => {
+ let member = &members[index as usize];
+ let type_id =
+ self.get_type_id(&ir_module.types, LookupType::Handle(member.ty));
+ (
+ self.create_pointer(type_id, spirv::StorageClass::Uniform),
+ type_id,
+ LookupType::Handle(member.ty),
+ )
+ }
+ _ => unimplemented!(),
+ };
+
+ let const_ty_id = self.get_type_id(
+ &ir_module.types,
+ LookupType::Local(LocalType::Scalar {
+ kind: crate::ScalarKind::Sint,
+ width: 4,
+ }),
+ );
+ let const_id = self.create_constant(const_ty_id, &[index]);
+
+ block
+ .body
+ .push(super::instructions::instruction_access_chain(
+ pointer_id,
+ id,
+ base_id,
+ &[const_id],
+ ));
+
+ let load_id = self.generate_id();
+ block.body.push(super::instructions::instruction_load(
+ type_id, load_id, id, None,
+ ));
+
+ Ok((load_id, lookup_ty))
+ }
+ crate::Expression::GlobalVariable(handle) => {
+ let var = &ir_module.global_variables[handle];
+ let id = self.get_global_variable_id(&ir_module, handle);
+
+ Ok((id, LookupType::Handle(var.ty)))
+ }
+ crate::Expression::Constant(handle) => {
+ let var = &ir_module.constants[handle];
+ let id = self.get_constant_id(handle, ir_module);
+ Ok((id, LookupType::Handle(var.ty)))
+ }
+ crate::Expression::Compose { ty, ref components } => {
+ let base_type_id = self.get_type_id(&ir_module.types, LookupType::Handle(ty));
+
+ let mut constituent_ids = Vec::with_capacity(components.len());
+ for component in components {
+ let expression = &ir_function.expressions[*component];
+ let (component_id, component_local_ty) = self.write_expression(
+ ir_module,
+ &ir_function,
+ expression,
+ block,
+ function,
+ )?;
+
+ let component_id = match expression {
+ crate::Expression::LocalVariable(_)
+ | crate::Expression::GlobalVariable(_) => {
+ let load_id = self.generate_id();
+ block.body.push(super::instructions::instruction_load(
+ self.get_type_id(&ir_module.types, component_local_ty),
+ load_id,
+ component_id,
+ None,
+ ));
+ load_id
+ }
+ _ => component_id,
+ };
+
+ constituent_ids.push(component_id);
+ }
+ let constituent_ids_slice = constituent_ids.as_slice();
+
+ let id = match ir_module.types[ty].inner {
+ crate::TypeInner::Vector { .. } => {
+ self.write_composite_construct(base_type_id, constituent_ids_slice, block)
+ }
+ crate::TypeInner::Matrix {
+ rows,
+ columns,
+ width,
+ } => {
+ let vector_type_id = self.get_type_id(
+ &ir_module.types,
+ LookupType::Local(LocalType::Vector {
+ width,
+ kind: crate::ScalarKind::Float,
+ size: columns,
+ }),
+ );
+
+ let capacity = match rows {
+ crate::VectorSize::Bi => 2,
+ crate::VectorSize::Tri => 3,
+ crate::VectorSize::Quad => 4,
+ };
+
+ let mut vector_ids = Vec::with_capacity(capacity);
+
+ for _ in 0..capacity {
+ let vector_id = self.write_composite_construct(
+ vector_type_id,
+ constituent_ids_slice,
+ block,
+ );
+ vector_ids.push(vector_id);
+ }
+
+ self.write_composite_construct(base_type_id, vector_ids.as_slice(), block)
+ }
+ _ => unreachable!(),
+ };
+
+ Ok((id, LookupType::Handle(ty)))
+ }
+ crate::Expression::Binary { op, left, right } => {
+ let id = self.generate_id();
+ let left_expression = &ir_function.expressions[left];
+ let right_expression = &ir_function.expressions[right];
+ let (left_id, left_lookup_ty) = self.write_expression(
+ ir_module,
+ ir_function,
+ left_expression,
+ block,
+ function,
+ )?;
+ let (right_id, right_lookup_ty) = self.write_expression(
+ ir_module,
+ ir_function,
+ right_expression,
+ block,
+ function,
+ )?;
+
+ let left_lookup_ty = left_lookup_ty;
+ let right_lookup_ty = right_lookup_ty;
+
+ let left_ty_inner = self.get_type_inner(&ir_module.types, left_lookup_ty);
+ let right_ty_inner = self.get_type_inner(&ir_module.types, right_lookup_ty);
+
+ let left_result_type_id = self.get_type_id(&ir_module.types, left_lookup_ty);
+
+ let right_result_type_id = self.get_type_id(&ir_module.types, right_lookup_ty);
+
+ let left_id = match *left_expression {
+ crate::Expression::LocalVariable(_) | crate::Expression::GlobalVariable(_) => {
+ let load_id = self.generate_id();
+ block.body.push(super::instructions::instruction_load(
+ left_result_type_id,
+ load_id,
+ left_id,
+ None,
+ ));
+ load_id
+ }
+ _ => left_id,
+ };
+
+ let right_id = match *right_expression {
+ crate::Expression::LocalVariable(..)
+ | crate::Expression::GlobalVariable(..) => {
+ let load_id = self.generate_id();
+ block.body.push(super::instructions::instruction_load(
+ right_result_type_id,
+ load_id,
+ right_id,
+ None,
+ ));
+ load_id
+ }
+ _ => right_id,
+ };
+
+ let left_dimension = get_dimension(&left_ty_inner);
+ let right_dimension = get_dimension(&right_ty_inner);
+
+ let (instruction, lookup_ty) = match op {
+ crate::BinaryOperator::Multiply => match (left_dimension, right_dimension) {
+ (Dimension::Vector, Dimension::Scalar { .. }) => (
+ super::instructions::instruction_vector_times_scalar(
+ left_result_type_id,
+ id,
+ left_id,
+ right_id,
+ ),
+ left_lookup_ty,
+ ),
+ (Dimension::Vector, Dimension::Matrix) => (
+ super::instructions::instruction_vector_times_matrix(
+ left_result_type_id,
+ id,
+ left_id,
+ right_id,
+ ),
+ left_lookup_ty,
+ ),
+ (Dimension::Matrix, Dimension::Scalar { .. }) => (
+ super::instructions::instruction_matrix_times_scalar(
+ left_result_type_id,
+ id,
+ left_id,
+ right_id,
+ ),
+ left_lookup_ty,
+ ),
+ (Dimension::Matrix, Dimension::Vector) => (
+ super::instructions::instruction_matrix_times_vector(
+ right_result_type_id,
+ id,
+ left_id,
+ right_id,
+ ),
+ right_lookup_ty,
+ ),
+ (Dimension::Matrix, Dimension::Matrix) => (
+ super::instructions::instruction_matrix_times_matrix(
+ left_result_type_id,
+ id,
+ left_id,
+ right_id,
+ ),
+ left_lookup_ty,
+ ),
+ (Dimension::Vector, Dimension::Vector)
+ | (Dimension::Scalar, Dimension::Scalar)
+ if left_ty_inner.scalar_kind() == Some(crate::ScalarKind::Float) =>
+ {
+ (
+ super::instructions::instruction_f_mul(
+ left_result_type_id,
+ id,
+ left_id,
+ right_id,
+ ),
+ left_lookup_ty,
+ )
+ }
+ (Dimension::Vector, Dimension::Vector)
+ | (Dimension::Scalar, Dimension::Scalar) => (
+ super::instructions::instruction_i_mul(
+ left_result_type_id,
+ id,
+ left_id,
+ right_id,
+ ),
+ left_lookup_ty,
+ ),
+ _ => unreachable!(),
+ },
+ crate::BinaryOperator::Subtract => match *left_ty_inner {
+ crate::TypeInner::Scalar { kind, .. } => match kind {
+ crate::ScalarKind::Sint | crate::ScalarKind::Uint => (
+ super::instructions::instruction_i_sub(
+ left_result_type_id,
+ id,
+ left_id,
+ right_id,
+ ),
+ left_lookup_ty,
+ ),
+ crate::ScalarKind::Float => (
+ super::instructions::instruction_f_sub(
+ left_result_type_id,
+ id,
+ left_id,
+ right_id,
+ ),
+ left_lookup_ty,
+ ),
+ _ => unreachable!(),
+ },
+ _ => unreachable!(),
+ },
+ crate::BinaryOperator::And => (
+ super::instructions::instruction_bitwise_and(
+ left_result_type_id,
+ id,
+ left_id,
+ right_id,
+ ),
+ left_lookup_ty,
+ ),
+ _ => unimplemented!("{:?}", op),
+ };
+
+ block.body.push(instruction);
+ Ok((id, lookup_ty))
+ }
+ crate::Expression::LocalVariable(variable) => {
+ let var = &ir_function.local_variables[variable];
+ function
+ .variables
+ .iter()
+ .find(|&v| v.name.as_ref().unwrap() == var.name.as_ref().unwrap())
+ .map(|local_var| (local_var.id, LookupType::Handle(var.ty)))
+ .ok_or_else(|| Error::UnknownLocalVariable(var.clone()))
+ }
+ crate::Expression::FunctionArgument(index) => {
+ let handle = ir_function.arguments[index as usize].ty;
+ let type_id = self.get_type_id(&ir_module.types, LookupType::Handle(handle));
+ let load_id = self.generate_id();
+
+ block.body.push(super::instructions::instruction_load(
+ type_id,
+ load_id,
+ function.parameters[index as usize].result_id.unwrap(),
+ None,
+ ));
+ Ok((load_id, LookupType::Handle(handle)))
+ }
+ crate::Expression::Call {
+ ref origin,
+ ref arguments,
+ } => match *origin {
+ crate::FunctionOrigin::Local(local_function) => {
+ let origin_function = &ir_module.functions[local_function];
+ let id = self.generate_id();
+ let mut argument_ids = vec![];
+
+ for argument in arguments {
+ let expression = &ir_function.expressions[*argument];
+ let (id, lookup_ty) = self.write_expression(
+ ir_module,
+ ir_function,
+ expression,
+ block,
+ function,
+ )?;
+
+ // Create variable - OpVariable
+ // Store value to variable - OpStore
+ // Use id of variable
+
+ let handle = match lookup_ty {
+ LookupType::Handle(handle) => handle,
+ LookupType::Local(_) => unreachable!(),
+ };
+
+ let pointer_id = self.get_pointer_id(
+ &ir_module.types,
+ handle,
+ crate::StorageClass::Function,
+ );
+
+ let variable_id = self.generate_id();
+ function.variables.push(LocalVariable {
+ id: variable_id,
+ name: None,
+ instruction: super::instructions::instruction_variable(
+ pointer_id,
+ variable_id,
+ spirv::StorageClass::Function,
+ None,
+ ),
+ });
+ block.body.push(super::instructions::instruction_store(
+ variable_id,
+ id,
+ None,
+ ));
+ argument_ids.push(variable_id);
+ }
+
+ let return_type_id = self
+ .get_function_return_type(origin_function.return_type, &ir_module.types);
+
+ block
+ .body
+ .push(super::instructions::instruction_function_call(
+ return_type_id,
+ id,
+ *self.lookup_function.get(&local_function).unwrap(),
+ argument_ids.as_slice(),
+ ));
+
+ let result_type = match origin_function.return_type {
+ Some(ty_handle) => LookupType::Handle(ty_handle),
+ None => LookupType::Local(LocalType::Void),
+ };
+ Ok((id, result_type))
+ }
+ _ => unimplemented!("{:?}", origin),
+ },
+ crate::Expression::As {
+ expr,
+ kind,
+ convert,
+ } => {
+ if !convert {
+ return Err(Error::FeatureNotImplemented());
+ }
+
+ let (expr_id, expr_type) = self.write_expression(
+ ir_module,
+ ir_function,
+ &ir_function.expressions[expr],
+ block,
+ function,
+ )?;
+
+ let expr_type_inner = self.get_type_inner(&ir_module.types, expr_type);
+
+ let (expr_kind, local_type) = match *expr_type_inner {
+ crate::TypeInner::Scalar {
+ kind: expr_kind,
+ width,
+ } => (expr_kind, LocalType::Scalar { kind, width }),
+ crate::TypeInner::Vector {
+ size,
+ kind: expr_kind,
+ width,
+ } => (expr_kind, LocalType::Vector { size, kind, width }),
+ _ => unreachable!(),
+ };
+
+ let lookup_type = LookupType::Local(local_type);
+ let op = match (expr_kind, kind) {
+ _ if !convert => spirv::Op::Bitcast,
+ (crate::ScalarKind::Float, crate::ScalarKind::Uint) => spirv::Op::ConvertFToU,
+ (crate::ScalarKind::Float, crate::ScalarKind::Sint) => spirv::Op::ConvertFToS,
+ (crate::ScalarKind::Sint, crate::ScalarKind::Float) => spirv::Op::ConvertSToF,
+ (crate::ScalarKind::Uint, crate::ScalarKind::Float) => spirv::Op::ConvertUToF,
+ // We assume it's either an identity cast, or int-uint.
+ // In both cases no SPIR-V instructions need to be generated.
+ _ => {
+ let id = match ir_function.expressions[expr] {
+ crate::Expression::LocalVariable(_)
+ | crate::Expression::GlobalVariable(_) => {
+ let load_id = self.generate_id();
+ let kind_type_id = self.get_type_id(&ir_module.types, expr_type);
+ block.body.push(super::instructions::instruction_load(
+ kind_type_id,
+ load_id,
+ expr_id,
+ None,
+ ));
+ load_id
+ }
+ _ => expr_id,
+ };
+ return Ok((id, lookup_type));
+ }
+ };
+
+ let id = self.generate_id();
+ let kind_type_id = self.get_type_id(&ir_module.types, lookup_type);
+ let instruction =
+ super::instructions::instruction_unary(op, kind_type_id, id, expr_id);
+ block.body.push(instruction);
+
+ Ok((id, lookup_type))
+ }
+ crate::Expression::ImageSample {
+ image,
+ sampler,
+ coordinate,
+ level: _,
+ depth_ref: _,
+ } => {
+ // image
+ let image_expression = &ir_function.expressions[image];
+ let (image_id, image_lookup_ty) = self.write_expression(
+ ir_module,
+ ir_function,
+ image_expression,
+ block,
+ function,
+ )?;
+
+ let image_result_type_id = self.get_type_id(&ir_module.types, image_lookup_ty);
+ let image_id = match *image_expression {
+ crate::Expression::LocalVariable(_) | crate::Expression::GlobalVariable(_) => {
+ let load_id = self.generate_id();
+ block.body.push(super::instructions::instruction_load(
+ image_result_type_id,
+ load_id,
+ image_id,
+ None,
+ ));
+ load_id
+ }
+ _ => image_id,
+ };
+
+ let image_ty = match image_lookup_ty {
+ LookupType::Handle(handle) => handle,
+ LookupType::Local(_) => unreachable!(),
+ };
+
+ // OpTypeSampledImage
+ let sampled_image_type_id = self.get_type_id(
+ &ir_module.types,
+ LookupType::Local(LocalType::SampledImage {
+ image_type: image_ty,
+ }),
+ );
+
+ // sampler
+ let sampler_expression = &ir_function.expressions[sampler];
+ let (sampler_id, sampler_lookup_ty) = self.write_expression(
+ ir_module,
+ ir_function,
+ sampler_expression,
+ block,
+ function,
+ )?;
+
+ let sampler_result_type_id = self.get_type_id(&ir_module.types, sampler_lookup_ty);
+ let sampler_id = match *sampler_expression {
+ crate::Expression::LocalVariable(_) | crate::Expression::GlobalVariable(_) => {
+ let load_id = self.generate_id();
+ block.body.push(super::instructions::instruction_load(
+ sampler_result_type_id,
+ load_id,
+ sampler_id,
+ None,
+ ));
+ load_id
+ }
+ _ => sampler_id,
+ };
+
+ // coordinate
+ let coordinate_expression = &ir_function.expressions[coordinate];
+ let (coordinate_id, coordinate_lookup_ty) = self.write_expression(
+ ir_module,
+ ir_function,
+ coordinate_expression,
+ block,
+ function,
+ )?;
+
+ let coordinate_result_type_id =
+ self.get_type_id(&ir_module.types, coordinate_lookup_ty);
+ let coordinate_id = match *coordinate_expression {
+ crate::Expression::LocalVariable(_) | crate::Expression::GlobalVariable(_) => {
+ let load_id = self.generate_id();
+ block.body.push(super::instructions::instruction_load(
+ coordinate_result_type_id,
+ load_id,
+ coordinate_id,
+ None,
+ ));
+ load_id
+ }
+ _ => coordinate_id,
+ };
+
+ // component kind
+ let image_type = &ir_module.types[image_ty];
+ let image_sample_result_type =
+ if let crate::TypeInner::Image { class, .. } = image_type.inner {
+ let width = 4;
+ LookupType::Local(match class {
+ crate::ImageClass::Sampled { kind, multi: _ } => LocalType::Vector {
+ kind,
+ width,
+ size: crate::VectorSize::Quad,
+ },
+ crate::ImageClass::Depth => LocalType::Scalar {
+ kind: crate::ScalarKind::Float,
+ width,
+ },
+ _ => return Err(Error::BadImageClass(class)),
+ })
+ } else {
+ return Err(Error::NotImage);
+ };
+
+ let sampled_image_id = self.generate_id();
+ block
+ .body
+ .push(super::instructions::instruction_sampled_image(
+ sampled_image_type_id,
+ sampled_image_id,
+ image_id,
+ sampler_id,
+ ));
+ let id = self.generate_id();
+ let image_sample_result_type_id =
+ self.get_type_id(&ir_module.types, image_sample_result_type);
+ block
+ .body
+ .push(super::instructions::instruction_image_sample_implicit_lod(
+ image_sample_result_type_id,
+ id,
+ sampled_image_id,
+ coordinate_id,
+ ));
+ Ok((id, image_sample_result_type))
+ }
+ _ => unimplemented!("{:?}", expression),
+ }
+ }
+
+ fn write_block(
+ &mut self,
+ statements: &[crate::Statement],
+ ir_module: &crate::Module,
+ ir_function: &crate::Function,
+ function: &mut Function,
+ ) -> spirv::Word {
+ let mut block = Block::new();
+ let id = self.generate_id();
+ block.label = Some(super::instructions::instruction_label(id));
+
+ for statement in statements {
+ match *statement {
+ crate::Statement::Block(ref ir_block) => {
+ if !ir_block.is_empty() {
+ //TODO: link the block with `OpBranch`
+ self.write_block(ir_block, ir_module, ir_function, function);
+ }
+ }
+ crate::Statement::Return { value } => {
+ block.termination = Some(match ir_function.return_type {
+ Some(_) => {
+ let expression = &ir_function.expressions[value.unwrap()];
+ let (id, lookup_ty) = self
+ .write_expression(
+ ir_module,
+ ir_function,
+ expression,
+ &mut block,
+ function,
+ )
+ .unwrap();
+
+ let id = match *expression {
+ crate::Expression::LocalVariable(_)
+ | crate::Expression::GlobalVariable(_) => {
+ let load_id = self.generate_id();
+ let value_ty_id = self.get_type_id(&ir_module.types, lookup_ty);
+ block.body.push(super::instructions::instruction_load(
+ value_ty_id,
+ load_id,
+ id,
+ None,
+ ));
+ load_id
+ }
+
+ _ => id,
+ };
+ super::instructions::instruction_return_value(id)
+ }
+ None => super::instructions::instruction_return(),
+ });
+ }
+ crate::Statement::Store { pointer, value } => {
+ let pointer_expression = &ir_function.expressions[pointer];
+ let value_expression = &ir_function.expressions[value];
+ let (pointer_id, _) = self
+ .write_expression(
+ ir_module,
+ ir_function,
+ pointer_expression,
+ &mut block,
+ function,
+ )
+ .unwrap();
+ let (value_id, value_lookup_ty) = self
+ .write_expression(
+ ir_module,
+ ir_function,
+ value_expression,
+ &mut block,
+ function,
+ )
+ .unwrap();
+
+ let value_id = match value_expression {
+ crate::Expression::LocalVariable(_)
+ | crate::Expression::GlobalVariable(_) => {
+ let load_id = self.generate_id();
+ let value_ty_id = self.get_type_id(&ir_module.types, value_lookup_ty);
+ block.body.push(super::instructions::instruction_load(
+ value_ty_id,
+ load_id,
+ value_id,
+ None,
+ ));
+ load_id
+ }
+ _ => value_id,
+ };
+
+ block.body.push(super::instructions::instruction_store(
+ pointer_id, value_id, None,
+ ));
+ }
+ _ => unimplemented!("{:?}", statement),
+ }
+ }
+
+ function.blocks.push(block);
+ id
+ }
+
+ fn write_physical_layout(&mut self) {
+ self.physical_layout.bound = self.id_count + 1;
+ }
+
+ fn write_logical_layout(&mut self, ir_module: &crate::Module) {
+ let id = self.generate_id();
+ super::instructions::instruction_ext_inst_import(id, "GLSL.std.450")
+ .to_words(&mut self.logical_layout.ext_inst_imports);
+
+ if self.writer_flags.contains(WriterFlags::DEBUG) {
+ self.debugs.push(super::instructions::instruction_source(
+ spirv::SourceLanguage::GLSL,
+ 450,
+ ));
+ }
+
+ for (handle, ir_function) in ir_module.functions.iter() {
+ let id = self.write_function(ir_function, ir_module);
+ self.lookup_function.insert(handle, id);
+ }
+
+ for (&(stage, ref name), ir_ep) in ir_module.entry_points.iter() {
+ let entry_point_instruction = self.write_entry_point(ir_ep, stage, name, ir_module);
+ entry_point_instruction.to_words(&mut self.logical_layout.entry_points);
+ }
+
+ for capability in self.capabilities.iter() {
+ super::instructions::instruction_capability(*capability)
+ .to_words(&mut self.logical_layout.capabilities);
+ }
+
+ let addressing_model = spirv::AddressingModel::Logical;
+ let memory_model = spirv::MemoryModel::GLSL450;
+ self.try_add_capabilities(addressing_model.required_capabilities());
+ self.try_add_capabilities(memory_model.required_capabilities());
+
+ super::instructions::instruction_memory_model(addressing_model, memory_model)
+ .to_words(&mut self.logical_layout.memory_model);
+
+ if self.writer_flags.contains(WriterFlags::DEBUG) {
+ for debug in self.debugs.iter() {
+ debug.to_words(&mut self.logical_layout.debugs);
+ }
+ }
+
+ for annotation in self.annotations.iter() {
+ annotation.to_words(&mut self.logical_layout.annotations);
+ }
+ }
+
+ pub fn write(&mut self, ir_module: &crate::Module) -> Vec<Word> {
+ let mut words: Vec<Word> = vec![];
+
+ self.write_logical_layout(ir_module);
+ self.write_physical_layout();
+
+ self.physical_layout.in_words(&mut words);
+ self.logical_layout.in_words(&mut words);
+ words
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::back::spv::{Writer, WriterFlags};
+ use crate::Header;
+
+ #[test]
+ fn test_writer_generate_id() {
+ let mut writer = create_writer();
+
+ assert_eq!(writer.id_count, 0);
+ writer.generate_id();
+ assert_eq!(writer.id_count, 1);
+ }
+
+ #[test]
+ fn test_try_add_capabilities() {
+ let mut writer = create_writer();
+
+ assert_eq!(writer.capabilities.len(), 0);
+ writer.try_add_capabilities(&[spirv::Capability::Shader]);
+ assert_eq!(writer.capabilities.len(), 1);
+
+ writer.try_add_capabilities(&[spirv::Capability::Shader]);
+ assert_eq!(writer.capabilities.len(), 1);
+ }
+
+ #[test]
+ fn test_write_physical_layout() {
+ let mut writer = create_writer();
+ assert_eq!(writer.physical_layout.bound, 0);
+ writer.write_physical_layout();
+ assert_eq!(writer.physical_layout.bound, 1);
+ }
+
+ fn create_writer() -> Writer {
+ let header = Header {
+ generator: 0,
+ version: (1, 0, 0),
+ };
+ Writer::new(&header, WriterFlags::NONE)
+ }
+}