diff --git a/cli/src/main.rs b/cli/src/main.rs index 0a2c22b890..0415ddc4ff 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -7,6 +7,7 @@ use std::{env, error::Error, path::Path}; struct Parameters { validation_flags: naga::valid::ValidationFlags, index_bounds_check_policy: naga::back::IndexBoundsCheckPolicy, + entry_point: Option, spv_adjust_coordinate_space: bool, spv_flow_dump_prefix: Option, spv: naga::back::spv::Options, @@ -89,7 +90,7 @@ fn main() { }; } "flow-dir" => params.spv_flow_dump_prefix = args.next(), - "entry-point" => params.glsl.entry_point = args.next().unwrap(), + "entry-point" => params.entry_point = Some(args.next().unwrap()), "profile" => { use naga::back::glsl::Version; let string = args.next().unwrap(); @@ -282,17 +283,28 @@ fn main() { stage @ "vert" | stage @ "frag" | stage @ "comp" => { use naga::back::glsl; - params.glsl.shader_stage = match stage { - "vert" => naga::ShaderStage::Vertex, - "frag" => naga::ShaderStage::Fragment, - "comp" => naga::ShaderStage::Compute, - _ => unreachable!(), + let pipeline_options = glsl::PipelineOptions { + entry_point: match params.entry_point { + Some(ref name) => name.clone(), + None => "main".to_string(), + }, + shader_stage: match stage { + "vert" => naga::ShaderStage::Vertex, + "frag" => naga::ShaderStage::Fragment, + "comp" => naga::ShaderStage::Compute, + _ => unreachable!(), + }, }; let mut buffer = String::new(); - let mut writer = - glsl::Writer::new(&mut buffer, &module, info.as_ref().unwrap(), ¶ms.glsl) - .unwrap_pretty(); + let mut writer = glsl::Writer::new( + &mut buffer, + &module, + info.as_ref().unwrap(), + ¶ms.glsl, + &pipeline_options, + ) + .unwrap_pretty(); writer.write().unwrap(); fs::write(output_path, buffer).unwrap(); } diff --git a/src/back/glsl/features.rs b/src/back/glsl/features.rs index 957b023476..118840dc2f 100644 --- a/src/back/glsl/features.rs +++ b/src/back/glsl/features.rs @@ -217,7 +217,7 @@ impl<'a, W> Writer<'a, W> { self.varying_required_features(result.binding.as_ref(), result.ty); } - if let ShaderStage::Compute = self.options.shader_stage { + if let ShaderStage::Compute = self.entry_point.stage { self.features.request(Features::COMPUTE_SHADER) } diff --git a/src/back/glsl/mod.rs b/src/back/glsl/mod.rs index f45c93a49b..9391c7ffe9 100644 --- a/src/back/glsl/mod.rs +++ b/src/back/glsl/mod.rs @@ -66,8 +66,12 @@ pub const SUPPORTED_CORE_VERSIONS: &[u16] = &[330, 400, 410, 420, 430, 440, 450] /// List of supported es glsl versions pub const SUPPORTED_ES_VERSIONS: &[u16] = &[300, 310, 320]; +pub type BindingMap = std::collections::BTreeMap; + /// glsl version #[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] pub enum Version { /// `core` glsl Desktop(u16), @@ -124,9 +128,29 @@ impl fmt::Display for Version { /// Structure that contains the configuration used in the [`Writer`](Writer) #[derive(Debug, Clone)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] pub struct Options { /// The glsl version to be used pub version: Version, + /// Map of resources association to binding locations. + pub binding_map: BindingMap, +} + +impl Default for Options { + fn default() -> Self { + Options { + version: Version::Embedded(310), + binding_map: BindingMap::default(), + } + } +} + +// A subset of options that are meant to be changed per pipeline. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +pub struct PipelineOptions { /// The stage of the entry point pub shader_stage: ShaderStage, /// The name of the entry point @@ -136,16 +160,6 @@ pub struct Options { pub entry_point: String, } -impl Default for Options { - fn default() -> Self { - Options { - version: Version::Embedded(320), - shader_stage: ShaderStage::Compute, - entry_point: "main".to_string(), - } - } -} - /// Structure that contains a reflection info pub struct ReflectionInfo { pub texture_mapping: crate::FastHashMap, @@ -297,6 +311,7 @@ impl<'a, W: Write> Writer<'a, W> { module: &'a crate::Module, info: &'a valid::ModuleInfo, options: &'a Options, + pipeline_options: &'a PipelineOptions, ) -> Result { // Check if the requested version is supported if !options.version.is_supported() { @@ -308,7 +323,9 @@ impl<'a, W: Write> Writer<'a, W> { let ep_idx = module .entry_points .iter() - .position(|ep| options.shader_stage == ep.stage && options.entry_point == ep.name) + .position(|ep| { + pipeline_options.shader_stage == ep.stage && pipeline_options.entry_point == ep.name + }) .ok_or(Error::EntryPointNotFound)?; // Generate a map with names required to write the module @@ -370,7 +387,7 @@ impl<'a, W: Write> Writer<'a, W> { writeln!(self.out)?; } - if self.options.shader_stage == ShaderStage::Compute { + if self.entry_point.stage == ShaderStage::Compute { let workgroup_size = self.entry_point.workgroup_size; writeln!( self.out, @@ -439,13 +456,36 @@ impl<'a, W: Write> Writer<'a, W> { arrayed, class, } => { - // Write the storage format if needed - if let TypeInner::Image { - class: crate::ImageClass::Storage(format), - .. - } = self.module.types[global.ty].inner - { - write!(self.out, "layout({}) ", glsl_storage_format(format))?; + // Gather the storage format if needed + let layout_storage_format = match self.module.types[global.ty].inner { + TypeInner::Image { + class: crate::ImageClass::Storage(format), + .. + } => Some(glsl_storage_format(format)), + _ => None, + }; + // Gether the location if needed + let layout_binding = if self.options.version.supports_explicit_locations() { + let br = global.binding.as_ref().unwrap(); + self.options.binding_map.get(br).cloned() + } else { + None + }; + + // Write all the layout qualifiers + if layout_binding.is_some() || layout_storage_format.is_some() { + write!(self.out, "layout(")?; + if let Some(binding) = layout_binding { + write!(self.out, "binding = {}", binding)?; + } + if let Some(format) = layout_storage_format { + let separator = match layout_binding { + Some(_) => ",", + None => "", + }; + write!(self.out, "{}{}", separator, format)?; + } + write!(self.out, ") ")?; } if let Some(storage_access) = glsl_storage_access(global.storage_access) { @@ -702,6 +742,15 @@ impl<'a, W: Write> Writer<'a, W> { handle: Handle, global: &crate::GlobalVariable, ) -> BackendResult { + if self.options.version.supports_explicit_locations() { + if let Some(ref br) = global.binding { + match self.options.binding_map.get(br) { + Some(binding) => write!(self.out, "layout(binding = {}) ", binding)?, + None => log::debug!("unassigned binding for {:?}", global.name), + } + } + } + if let Some(storage_access) = glsl_storage_access(global.storage_access) { write!(self.out, "{} ", storage_access)?; } @@ -782,7 +831,7 @@ impl<'a, W: Write> Writer<'a, W> { // // We ignore all interpolation and auxiliary modifiers that aren't used in fragment // shaders' input globals or vertex shaders' output globals. - let emit_interpolation_and_auxiliary = match self.options.shader_stage { + let emit_interpolation_and_auxiliary = match self.entry_point.stage { ShaderStage::Vertex => output, ShaderStage::Fragment => !output, _ => false, diff --git a/src/back/msl/mod.rs b/src/back/msl/mod.rs index 738eb9c93b..6a46bedaab 100644 --- a/src/back/msl/mod.rs +++ b/src/back/msl/mod.rs @@ -24,7 +24,10 @@ holding the result. !*/ use crate::{arena::Handle, valid::ModuleInfo}; -use std::fmt::{Error as FmtError, Write}; +use std::{ + fmt::{Error as FmtError, Write}, + ops, +}; mod keywords; pub mod sampler; @@ -57,21 +60,16 @@ pub struct BindTarget { pub mutable: bool, } -#[derive(Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize))] -#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] -pub struct BindSource { - pub stage: crate::ShaderStage, - pub group: u32, - pub binding: u32, -} +// Using `BTreeMap` instead of `HashMap` so that we can hash itself. +pub type BindingMap = std::collections::BTreeMap; -pub type BindingMap = std::collections::BTreeMap; - -#[derive(Clone, Debug, Default, Hash, Eq, Ord, PartialEq, PartialOrd)] +#[derive(Clone, Debug, Default, Hash, Eq, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] pub struct PerStageResources { + #[cfg_attr(feature = "deserialize", serde(default))] + pub resources: BindingMap, + #[cfg_attr(feature = "deserialize", serde(default))] pub push_constant_buffer: Option, @@ -82,7 +80,7 @@ pub struct PerStageResources { pub sizes_buffer: Option, } -#[derive(Clone, Debug, Default, Hash, Eq, Ord, PartialEq, PartialOrd)] +#[derive(Clone, Debug, Default, Hash, Eq, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] pub struct PerStageMap { @@ -94,6 +92,17 @@ pub struct PerStageMap { pub cs: PerStageResources, } +impl ops::Index for PerStageMap { + type Output = PerStageResources; + fn index(&self, stage: crate::ShaderStage) -> &PerStageResources { + match stage { + crate::ShaderStage::Vertex => &self.vs, + crate::ShaderStage::Fragment => &self.fs, + crate::ShaderStage::Compute => &self.cs, + } + } +} + enum ResolvedBinding { BuiltIn(crate::BuiltIn), Attribute(u32), @@ -146,11 +155,11 @@ pub enum Error { #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] pub enum EntryPointError { #[error("mapping of {0:?} is missing")] - MissingBinding(BindSource), - #[error("mapping for push constants at stage {0:?} is missing")] - MissingPushConstants(crate::ShaderStage), - #[error("mapping for sizes buffer for stage {0:?} is missing")] - MissingSizesBuffer(crate::ShaderStage), + MissingBinding(crate::ResourceBinding), + #[error("mapping for push constants is missing")] + MissingPushConstants, + #[error("mapping for sizes buffer is missing")] + MissingSizesBuffer, } #[derive(Clone, Copy, Debug)] @@ -167,9 +176,7 @@ enum LocationMode { pub struct Options { /// (Major, Minor) target version of the Metal Shading Language. pub lang_version: (u8, u8), - /// Binding model mapping to Metal. - pub binding_map: BindingMap, - /// Map of per-stage resources (e.g. push constants) to slots + /// Map of per-stage resources to slots. pub per_stage_map: PerStageMap, /// Samplers to be inlined into the code. pub inline_samplers: Vec, @@ -183,7 +190,6 @@ impl Default for Options { fn default() -> Self { Options { lang_version: (1, 0), - binding_map: BindingMap::default(), per_stage_map: PerStageMap::default(), inline_samplers: Vec::new(), spirv_cross_compatibility: false, @@ -257,19 +263,14 @@ impl Options { stage: crate::ShaderStage, res_binding: &crate::ResourceBinding, ) -> Result { - let source = BindSource { - stage, - group: res_binding.group, - binding: res_binding.binding, - }; - match self.binding_map.get(&source) { + match self.per_stage_map[stage].resources.get(&res_binding) { Some(target) => Ok(ResolvedBinding::Resource(target.clone())), None if self.fake_missing_bindings => Ok(ResolvedBinding::User { prefix: "fake", index: 0, interpolation: None, }), - None => Err(EntryPointError::MissingBinding(source)), + None => Err(EntryPointError::MissingBinding(res_binding.clone())), } } @@ -294,7 +295,7 @@ impl Options { index: 0, interpolation: None, }), - None => Err(EntryPointError::MissingPushConstants(stage)), + None => Err(EntryPointError::MissingPushConstants), } } @@ -302,12 +303,7 @@ impl Options { &self, stage: crate::ShaderStage, ) -> Result { - let slot = match stage { - crate::ShaderStage::Vertex => self.per_stage_map.vs.sizes_buffer, - crate::ShaderStage::Fragment => self.per_stage_map.fs.sizes_buffer, - crate::ShaderStage::Compute => self.per_stage_map.cs.sizes_buffer, - }; - + let slot = self.per_stage_map[stage].sizes_buffer; match slot { Some(slot) => Ok(ResolvedBinding::Resource(BindTarget { buffer: Some(slot), @@ -320,7 +316,7 @@ impl Options { index: 0, interpolation: None, }), - None => Err(EntryPointError::MissingSizesBuffer(stage)), + None => Err(EntryPointError::MissingSizesBuffer), } } } diff --git a/src/lib.rs b/src/lib.rs index 361c080693..6b86d64e5d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -625,7 +625,7 @@ pub enum Binding { } /// Pipeline binding information for global resources. -#[derive(Clone, Debug, Hash, PartialEq)] +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] pub struct ResourceBinding { diff --git a/tests/in/access.param.ron b/tests/in/access.param.ron index f7084220b9..675006b010 100644 --- a/tests/in/access.param.ron +++ b/tests/in/access.param.ron @@ -5,11 +5,11 @@ msl_custom: true, msl: ( lang_version: (2, 0), - binding_map: { - (stage: Vertex, group: 0, binding: 0): (buffer: Some(0), mutable: true), - }, per_stage_map: ( vs: ( + resources: { + (group: 0, binding: 0): (buffer: Some(0), mutable: true), + }, sizes_buffer: Some(24), ), ), diff --git a/tests/in/boids.param.ron b/tests/in/boids.param.ron index 25413d0c47..8f304e5c2a 100644 --- a/tests/in/boids.param.ron +++ b/tests/in/boids.param.ron @@ -5,13 +5,13 @@ msl_custom: true, msl: ( lang_version: (2, 0), - binding_map: { - (stage: Compute, group: 0, binding: 0): (buffer: Some(0), mutable: false), - (stage: Compute, group: 0, binding: 1): (buffer: Some(1), mutable: true), - (stage: Compute, group: 0, binding: 2): (buffer: Some(2), mutable: true), - }, per_stage_map: ( cs: ( + resources: { + (group: 0, binding: 0): (buffer: Some(0), mutable: false), + (group: 0, binding: 1): (buffer: Some(1), mutable: true), + (group: 0, binding: 2): (buffer: Some(2), mutable: true), + }, sizes_buffer: Some(3), ) ), diff --git a/tests/in/interpolate.param.ron b/tests/in/interpolate.param.ron index f0c6a13d56..39fdcf20d3 100644 --- a/tests/in/interpolate.param.ron +++ b/tests/in/interpolate.param.ron @@ -3,5 +3,9 @@ spv_capabilities: [ Shader, SampleRateShading ], spv_debug: true, spv_adjust_coordinate_space: true, - glsl_desktop_version: Some(400) + glsl: ( + version: Desktop(400), + binding_map: {}, + ), + glsl_custom: true, ) diff --git a/tests/in/skybox.param.ron b/tests/in/skybox.param.ron index 9a5bf7d2b1..9d0ad493dc 100644 --- a/tests/in/skybox.param.ron +++ b/tests/in/skybox.param.ron @@ -6,12 +6,18 @@ msl_custom: true, msl: ( lang_version: (2, 1), - binding_map: { - (stage: Vertex, group: 0, binding: 0): (buffer: Some(0)), - (stage: Fragment, group: 0, binding: 1): (texture: Some(0)), - (stage: Fragment, group: 0, binding: 2): (sampler: Some(Inline(0))), - }, per_stage_map: ( + vs: ( + resources: { + (group: 0, binding: 0): (buffer: Some(0)), + }, + ), + fs: ( + resources: { + (group: 0, binding: 1): (texture: Some(0)), + (group: 0, binding: 2): (sampler: Some(Inline(0))), + }, + ), ), inline_samplers: [ ( @@ -28,5 +34,13 @@ ], spirv_cross_compatibility: false, fake_missing_bindings: false, - ) + ), + glsl_custom: true, + glsl: ( + version: Embedded(320), + binding_map: { + (group: 0, binding: 0): 0, + (group: 0, binding: 1): 0, + }, + ), ) diff --git a/tests/out/glsl/skybox.fs_main.Fragment.glsl b/tests/out/glsl/skybox.fs_main.Fragment.glsl index 1a8488c2ab..c75b0abb0f 100644 --- a/tests/out/glsl/skybox.fs_main.Fragment.glsl +++ b/tests/out/glsl/skybox.fs_main.Fragment.glsl @@ -1,4 +1,4 @@ -#version 310 es +#version 320 es precision highp float; @@ -7,7 +7,7 @@ struct VertexOutput { vec3 uv; }; -uniform highp samplerCube _group_0_binding_1; +layout(binding = 0) uniform highp samplerCube _group_0_binding_1; smooth in vec3 _vs2fs_location0; layout(location = 0) out vec4 _fs2p_location0; diff --git a/tests/out/glsl/skybox.vs_main.Vertex.glsl b/tests/out/glsl/skybox.vs_main.Vertex.glsl index 1c81eb42ae..c1b423fc62 100644 --- a/tests/out/glsl/skybox.vs_main.Vertex.glsl +++ b/tests/out/glsl/skybox.vs_main.Vertex.glsl @@ -1,4 +1,4 @@ -#version 310 es +#version 320 es precision highp float; @@ -7,7 +7,7 @@ struct VertexOutput { vec3 uv; }; -uniform Data_block_0 { +layout(binding = 0) uniform Data_block_0 { mat4x4 proj_inv; mat4x4 view; } _group_0_binding_0; diff --git a/tests/snapshots.rs b/tests/snapshots.rs index 7ea614461d..bfcb897d04 100644 --- a/tests/snapshots.rs +++ b/tests/snapshots.rs @@ -49,9 +49,12 @@ struct Parameters { #[cfg(all(not(feature = "deserialize"), feature = "msl-out"))] #[serde(default)] msl_custom: bool, - #[cfg_attr(not(feature = "glsl-out"), allow(dead_code))] + #[cfg(all(feature = "deserialize", feature = "glsl-out"))] #[serde(default)] - glsl_desktop_version: Option, + glsl: naga::back::glsl::Options, + #[cfg(all(not(feature = "deserialize"), feature = "glsl-out"))] + #[serde(default)] + glsl_custom: bool, #[cfg_attr(not(feature = "glsl-out"), allow(dead_code))] #[serde(default)] glsl_vert_ep_name: Option, @@ -233,17 +236,26 @@ fn write_output_glsl( ) { use naga::back::glsl; - let options = glsl::Options { - version: match params.glsl_desktop_version { - Some(v) => glsl::Version::Desktop(v), - None => glsl::Version::Embedded(310), - }, + #[cfg_attr(feature = "deserialize", allow(unused_variables))] + let default_options = glsl::Options::default(); + #[cfg(feature = "deserialize")] + let options = ¶ms.glsl; + #[cfg(not(feature = "deserialize"))] + let options = if params.glsl_custom { + println!("Skipping {}", destination.display()); + return; + } else { + &default_options + }; + + let pipeline_options = glsl::PipelineOptions { shader_stage: stage, entry_point: ep_name.to_string(), }; let mut buffer = String::new(); - let mut writer = glsl::Writer::new(&mut buffer, module, info, &options).unwrap(); + let mut writer = + glsl::Writer::new(&mut buffer, module, info, &options, &pipeline_options).unwrap(); writer.write().unwrap(); fs::write(