From 0491d39232bafc30962f6ea240ab6ed457282a62 Mon Sep 17 00:00:00 2001 From: Frederik Magnus Johansen Vestre Date: Wed, 30 Aug 2023 21:00:35 +0200 Subject: [PATCH] Support dual source blending (#2427) Co-authored-by: teoxoy <28601907+teoxoy@users.noreply.github.com> --- src/back/glsl/features.rs | 11 ++++ src/back/glsl/mod.rs | 18 +++++- src/back/hlsl/writer.rs | 12 +++- src/back/msl/mod.rs | 29 ++++++++- src/back/spv/writer.rs | 4 ++ src/back/wgsl/writer.rs | 13 ++++ src/front/glsl/functions.rs | 2 + src/front/glsl/variables.rs | 1 + src/front/interpolator.rs | 1 + src/front/spv/mod.rs | 1 + src/front/wgsl/parse/mod.rs | 5 ++ src/lib.rs | 2 + src/valid/analyzer.rs | 5 ++ src/valid/interface.rs | 65 +++++++++++++++++++- src/valid/mod.rs | 2 + tests/in/dualsource.param.ron | 13 ++++ tests/in/dualsource.wgsl | 11 ++++ tests/out/analysis/access.info.ron | 9 +++ tests/out/analysis/collatz.info.ron | 2 + tests/out/analysis/shadow.info.ron | 3 + tests/out/glsl/dualsource.main.Fragment.glsl | 27 ++++++++ tests/out/hlsl/dualsource.hlsl | 29 +++++++++ tests/out/hlsl/dualsource.ron | 12 ++++ tests/out/ir/access.ron | 1 + tests/out/ir/shadow.ron | 3 + tests/out/msl/dualsource.msl | 29 +++++++++ tests/out/spv/dualsource.spvasm | 55 +++++++++++++++++ tests/out/wgsl/dualsource.wgsl | 16 +++++ tests/snapshots.rs | 4 ++ 29 files changed, 376 insertions(+), 9 deletions(-) create mode 100644 tests/in/dualsource.param.ron create mode 100644 tests/in/dualsource.wgsl create mode 100644 tests/out/glsl/dualsource.main.Fragment.glsl create mode 100644 tests/out/hlsl/dualsource.hlsl create mode 100644 tests/out/hlsl/dualsource.ron create mode 100644 tests/out/msl/dualsource.msl create mode 100644 tests/out/spv/dualsource.spvasm create mode 100644 tests/out/wgsl/dualsource.wgsl diff --git a/src/back/glsl/features.rs b/src/back/glsl/features.rs index 8a5da1b6e1..6bb5b18888 100644 --- a/src/back/glsl/features.rs +++ b/src/back/glsl/features.rs @@ -41,6 +41,8 @@ bitflags::bitflags! { const TEXTURE_LEVELS = 1 << 19; /// Image size query const IMAGE_SIZE = 1 << 20; + /// Dual source blending + const DUAL_SOURCE_BLENDING = 1 << 21; } } @@ -104,6 +106,7 @@ impl FeaturesManager { check_feature!(CULL_DISTANCE, 450, 300 /* with extension */); check_feature!(SAMPLE_VARIABLES, 400, 300); check_feature!(DYNAMIC_ARRAY_SIZE, 430, 310); + check_feature!(DUAL_SOURCE_BLENDING, 330, 300 /* with extension */); match version { Version::Embedded { is_webgl: true, .. } => check_feature!(MULTI_VIEW, 140, 300), _ => check_feature!(MULTI_VIEW, 140, 310), @@ -233,6 +236,10 @@ impl FeaturesManager { // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_texture_query_levels.txt writeln!(out, "#extension GL_ARB_texture_query_levels : require")?; } + if self.0.contains(Features::DUAL_SOURCE_BLENDING) && version.is_es() { + // https://registry.khronos.org/OpenGL/extensions/EXT/EXT_blend_func_extended.txt + writeln!(out, "#extension GL_EXT_blend_func_extended : require")?; + } Ok(()) } @@ -497,6 +504,7 @@ impl<'a, W> Writer<'a, W> { location: _, interpolation, sampling, + second_blend_source, } => { if interpolation == Some(Interpolation::Linear) { self.features.request(Features::NOPERSPECTIVE_QUALIFIER); @@ -504,6 +512,9 @@ impl<'a, W> Writer<'a, W> { if sampling == Some(Sampling::Sample) { self.features.request(Features::SAMPLE_QUALIFIER); } + if second_blend_source { + self.features.request(Features::DUAL_SOURCE_BLENDING); + } } } } diff --git a/src/back/glsl/mod.rs b/src/back/glsl/mod.rs index ef5dd0143f..4b732a6038 100644 --- a/src/back/glsl/mod.rs +++ b/src/back/glsl/mod.rs @@ -333,6 +333,12 @@ struct VaryingName<'a> { impl fmt::Display for VaryingName<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self.binding { + crate::Binding::Location { + second_blend_source: true, + .. + } => { + write!(f, "_fs2p_location1",) + } crate::Binding::Location { location, .. } => { let prefix = match (self.stage, self.output) { (ShaderStage::Compute, _) => unreachable!(), @@ -1235,12 +1241,13 @@ impl<'a, W: Write> Writer<'a, W> { Some(binding) => binding, }; - let (location, interpolation, sampling) = match *binding { + let (location, interpolation, sampling, second_blend_source) = match *binding { crate::Binding::Location { location, interpolation, sampling, - } => (location, interpolation, sampling), + second_blend_source, + } => (location, interpolation, sampling, second_blend_source), crate::Binding::BuiltIn(built_in) => { if let crate::BuiltIn::Position { invariant: true } = built_in { match (self.options.version, self.entry_point.stage) { @@ -1281,7 +1288,11 @@ impl<'a, W: Write> Writer<'a, W> { // Write the I/O locations, if allowed if self.options.version.supports_explicit_locations() || !emit_interpolation_and_auxiliary { - write!(self.out, "layout(location = {location}) ")?; + if second_blend_source { + write!(self.out, "layout(location = {location}, index = 1) ")?; + } else { + write!(self.out, "layout(location = {location}) ")?; + } } // Write the interpolation qualifier. @@ -1318,6 +1329,7 @@ impl<'a, W: Write> Writer<'a, W> { location, interpolation: None, sampling: None, + second_blend_source, }, stage: self.entry_point.stage, output, diff --git a/src/back/hlsl/writer.rs b/src/back/hlsl/writer.rs index 4f19126388..c37334b60d 100644 --- a/src/back/hlsl/writer.rs +++ b/src/back/hlsl/writer.rs @@ -416,7 +416,17 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { let builtin_str = builtin.to_hlsl_str()?; write!(self.out, " : {builtin_str}")?; } - crate::Binding::Location { location, .. } => { + crate::Binding::Location { + second_blend_source: true, + .. + } => { + write!(self.out, " : SV_Target1")?; + } + crate::Binding::Location { + location, + second_blend_source: false, + .. + } => { if stage == Some((crate::ShaderStage::Fragment, Io::Output)) { write!(self.out, " : SV_Target{location}")?; } else { diff --git a/src/back/msl/mod.rs b/src/back/msl/mod.rs index 78bcc2123b..819d48a8fd 100644 --- a/src/back/msl/mod.rs +++ b/src/back/msl/mod.rs @@ -82,7 +82,10 @@ pub type EntryPointResourceMap = std::collections::BTreeMap match mode { LocationMode::VertexInput => Ok(ResolvedBinding::Attribute(location)), - LocationMode::FragmentOutput => Ok(ResolvedBinding::Color(location)), + LocationMode::FragmentOutput => { + if second_blend_source && self.lang_version < (1, 2) { + return Err(Error::UnsupportedAttribute( + "second_blend_source".to_string(), + )); + } + Ok(ResolvedBinding::Color { + location, + second_blend_source, + }) + } LocationMode::VertexOutput | LocationMode::FragmentInput => { Ok(ResolvedBinding::User { prefix: if self.spirv_cross_compatibility { @@ -404,7 +418,16 @@ impl ResolvedBinding { write!(out, "{name}")?; } Self::Attribute(index) => write!(out, "attribute({index})")?, - Self::Color(index) => write!(out, "color({index})")?, + Self::Color { + location, + second_blend_source, + } => { + if second_blend_source { + write!(out, "color({location}) index(1)")? + } else { + write!(out, "color({location})")? + } + } Self::User { prefix, index, diff --git a/src/back/spv/writer.rs b/src/back/spv/writer.rs index e6db93602a..41a13cbc19 100644 --- a/src/back/spv/writer.rs +++ b/src/back/spv/writer.rs @@ -1434,6 +1434,7 @@ impl Writer { location, interpolation, sampling, + second_blend_source, } => { self.decorate(id, Decoration::Location, &[location]); @@ -1473,6 +1474,9 @@ impl Writer { } } } + if second_blend_source { + self.decorate(id, Decoration::Index, &[1]); + } } crate::Binding::BuiltIn(built_in) => { use crate::BuiltIn as Bi; diff --git a/src/back/wgsl/writer.rs b/src/back/wgsl/writer.rs index 44dd2a97c0..43d094ec2e 100644 --- a/src/back/wgsl/writer.rs +++ b/src/back/wgsl/writer.rs @@ -17,6 +17,7 @@ enum Attribute { Invariant, Interpolate(Option, Option), Location(u32), + SecondBlendSource, Stage(ShaderStage), WorkGroupSize([u32; 3]), } @@ -319,6 +320,7 @@ impl Writer { for attribute in attributes { match *attribute { Attribute::Location(id) => write!(self.out, "@location({id}) ")?, + Attribute::SecondBlendSource => write!(self.out, "@second_blend_source ")?, Attribute::BuiltIn(builtin_attrib) => { let builtin = builtin_str(builtin_attrib)?; write!(self.out, "@builtin({builtin}) ")?; @@ -1917,9 +1919,20 @@ fn map_binding_to_attribute(binding: &crate::Binding) -> Vec { location, interpolation, sampling, + second_blend_source: false, } => vec![ Attribute::Location(location), Attribute::Interpolate(interpolation, sampling), ], + crate::Binding::Location { + location, + interpolation, + sampling, + second_blend_source: true, + } => vec![ + Attribute::Location(location), + Attribute::SecondBlendSource, + Attribute::Interpolate(interpolation, sampling), + ], } } diff --git a/src/front/glsl/functions.rs b/src/front/glsl/functions.rs index f448272583..7dee023743 100644 --- a/src/front/glsl/functions.rs +++ b/src/front/glsl/functions.rs @@ -1294,6 +1294,7 @@ impl Frontend { location, interpolation, sampling: None, + second_blend_source: false, }; location += 1; @@ -1336,6 +1337,7 @@ impl Frontend { location, interpolation, sampling: None, + second_blend_source: false, }; location += 1; binding diff --git a/src/front/glsl/variables.rs b/src/front/glsl/variables.rs index a9a403e402..ad0bd7cbd2 100644 --- a/src/front/glsl/variables.rs +++ b/src/front/glsl/variables.rs @@ -475,6 +475,7 @@ impl Frontend { location, interpolation, sampling, + second_blend_source: false, }, handle, storage, diff --git a/src/front/interpolator.rs b/src/front/interpolator.rs index 96f5db6ff7..0196a2254d 100644 --- a/src/front/interpolator.rs +++ b/src/front/interpolator.rs @@ -43,6 +43,7 @@ impl crate::Binding { location: _, interpolation: ref mut interpolation @ None, ref mut sampling, + second_blend_source: _, } = *self { match ty.scalar_kind() { diff --git a/src/front/spv/mod.rs b/src/front/spv/mod.rs index a210e8c421..36108fdd9b 100644 --- a/src/front/spv/mod.rs +++ b/src/front/spv/mod.rs @@ -255,6 +255,7 @@ impl Decoration { location, interpolation, sampling, + second_blend_source: false, }), _ => Err(Error::MissingDecoration(spirv::Decoration::Location)), } diff --git a/src/front/wgsl/parse/mod.rs b/src/front/wgsl/parse/mod.rs index 4a9aafc5f7..86dd12799d 100644 --- a/src/front/wgsl/parse/mod.rs +++ b/src/front/wgsl/parse/mod.rs @@ -143,6 +143,7 @@ impl ParsedAttribute { #[derive(Default)] struct BindingParser { location: ParsedAttribute, + second_blend_source: ParsedAttribute, built_in: ParsedAttribute, interpolation: ParsedAttribute, sampling: ParsedAttribute, @@ -182,6 +183,9 @@ impl BindingParser { } lexer.expect(Token::Paren(')'))?; } + "second_blend_source" => { + self.second_blend_source.set(true, name_span)?; + } "invariant" => { self.invariant.set(true, name_span)?; } @@ -208,6 +212,7 @@ impl BindingParser { location, interpolation, sampling, + second_blend_source: self.second_blend_source.value.unwrap_or(false), })) } (None, Some(crate::BuiltIn::Position { .. }), None, None, invariant) => { diff --git a/src/lib.rs b/src/lib.rs index a1f7ef1654..fdea215e33 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -922,6 +922,8 @@ pub enum Binding { /// [`Fragment`]: crate::ShaderStage::Fragment Location { location: u32, + /// Indicates the 2nd input to the blender when dual-source blending. + second_blend_source: bool, interpolation: Option, sampling: Option, }, diff --git a/src/valid/analyzer.rs b/src/valid/analyzer.rs index 1e75c5af6c..e4162e15f5 100644 --- a/src/valid/analyzer.rs +++ b/src/valid/analyzer.rs @@ -256,6 +256,9 @@ pub struct FunctionInfo { /// /// [`GlobalVariable`]: crate::GlobalVariable sampling: crate::FastHashSet, + + /// Indicates that the function is using dual source blending. + pub dual_source_blending: bool, } impl FunctionInfo { @@ -999,6 +1002,7 @@ impl ModuleInfo { global_uses: vec![GlobalUse::empty(); module.global_variables.len()].into_boxed_slice(), expressions: vec![ExpressionInfo::new(); fun.expressions.len()].into_boxed_slice(), sampling: crate::FastHashSet::default(), + dual_source_blending: false, }; let resolve_context = ResolveContext::with_locals(module, &fun.local_variables, &fun.arguments); @@ -1108,6 +1112,7 @@ fn uniform_control_flow() { global_uses: vec![GlobalUse::empty(); global_var_arena.len()].into_boxed_slice(), expressions: vec![ExpressionInfo::new(); expressions.len()].into_boxed_slice(), sampling: crate::FastHashSet::default(), + dual_source_blending: false, }; let resolve_context = ResolveContext { constants: &Arena::new(), diff --git a/src/valid/interface.rs b/src/valid/interface.rs index 2e54df2478..d788a54a34 100644 --- a/src/valid/interface.rs +++ b/src/valid/interface.rs @@ -61,6 +61,17 @@ pub enum VaryingError { DuplicateBuiltIn(crate::BuiltIn), #[error("Capability {0:?} is not supported")] UnsupportedCapability(Capabilities), + #[error("The attribute {0:?} is only valid as an output for stage {1:?}")] + InvalidInputAttributeInStage(&'static str, crate::ShaderStage), + #[error("The attribute {0:?} is not valid for stage {1:?}")] + InvalidAttributeInStage(&'static str, crate::ShaderStage), + #[error( + "The location index {location} cannot be used together with the attribute {attribute:?}" + )] + InvalidLocationAttributeCombination { + location: u32, + attribute: &'static str, + }, } #[derive(Clone, Debug, thiserror::Error)] @@ -89,6 +100,10 @@ pub enum EntryPointError { InvalidIntegerInterpolation { location: u32 }, #[error(transparent)] Function(#[from] FunctionError), + #[error( + "Invalid locations {location_mask:?} are set while dual source blending. Only location 0 may be set." + )] + InvalidLocationsWhileDualSourceBlending { location_mask: BitSet }, } #[cfg(feature = "validate")] @@ -106,6 +121,7 @@ fn storage_usage(access: crate::StorageAccess) -> GlobalUse { struct VaryingContext<'a> { stage: crate::ShaderStage, output: bool, + second_blend_source: bool, types: &'a UniqueArena, type_info: &'a Vec, location_mask: &'a mut BitSet, @@ -293,6 +309,7 @@ impl VaryingContext<'_> { location, interpolation, sampling, + second_blend_source, } => { // Only IO-shareable types may be stored in locations. if !self.type_info[ty.index()] @@ -301,7 +318,37 @@ impl VaryingContext<'_> { { return Err(VaryingError::NotIOShareableType(ty)); } - if !self.location_mask.insert(location as usize) { + + if second_blend_source { + if !self + .capabilities + .contains(Capabilities::DUAL_SOURCE_BLENDING) + { + return Err(VaryingError::UnsupportedCapability( + Capabilities::DUAL_SOURCE_BLENDING, + )); + } + if self.stage != crate::ShaderStage::Fragment { + return Err(VaryingError::InvalidAttributeInStage( + "second_blend_source", + self.stage, + )); + } + if !self.output { + return Err(VaryingError::InvalidInputAttributeInStage( + "second_blend_source", + self.stage, + )); + } + if location != 0 { + return Err(VaryingError::InvalidLocationAttributeCombination { + location, + attribute: "second_blend_source", + }); + } + + self.second_blend_source = true; + } else if !self.location_mask.insert(location as usize) { #[cfg(feature = "validate")] if self.flags.contains(super::ValidationFlags::BINDINGS) { return Err(VaryingError::BindingCollision { location }); @@ -567,7 +614,7 @@ impl super::Validator { return Err(EntryPointError::UnexpectedWorkgroupSize.with_span()); } - let info = self + let mut info = self .validate_function(&ep.function, module, mod_info, true) .map_err(WithSpan::into_other)?; @@ -593,6 +640,7 @@ impl super::Validator { let mut ctx = VaryingContext { stage: ep.stage, output: false, + second_blend_source: false, types: &module.types, type_info: &self.types, location_mask: &mut self.location_mask, @@ -612,6 +660,7 @@ impl super::Validator { let mut ctx = VaryingContext { stage: ep.stage, output: true, + second_blend_source: false, types: &module.types, type_info: &self.types, location_mask: &mut self.location_mask, @@ -623,6 +672,18 @@ impl super::Validator { }; ctx.validate(fr.ty, fr.binding.as_ref()) .map_err_inner(|e| EntryPointError::Result(e).with_span())?; + #[cfg(feature = "validate")] + if ctx.second_blend_source { + // Only the first location may be used whhen dual source blending + if ctx.location_mask.len() == 1 && ctx.location_mask.contains(0) { + info.dual_source_blending = true; + } else { + return Err(EntryPointError::InvalidLocationsWhileDualSourceBlending { + location_mask: self.location_mask.clone(), + } + .with_span()); + } + } #[cfg(feature = "validate")] if ep.stage == crate::ShaderStage::Vertex diff --git a/src/valid/mod.rs b/src/valid/mod.rs index b04d69b5b4..5351287725 100644 --- a/src/valid/mod.rs +++ b/src/valid/mod.rs @@ -112,6 +112,8 @@ bitflags::bitflags! { const MULTISAMPLED_SHADING = 0x800; /// Support for ray queries and acceleration structures. const RAY_QUERY = 0x1000; + /// Support for generating two sources for blending from fragement shaders + const DUAL_SOURCE_BLENDING = 0x2000; } } diff --git a/tests/in/dualsource.param.ron b/tests/in/dualsource.param.ron new file mode 100644 index 0000000000..1cf512c6c4 --- /dev/null +++ b/tests/in/dualsource.param.ron @@ -0,0 +1,13 @@ +( + god_mode: true, + vertex:[ + ], + fragment:[ + ( + entry_point:"main", + target_profile:"ps_5_1", + ), + ], + compute:[ + ], +) diff --git a/tests/in/dualsource.wgsl b/tests/in/dualsource.wgsl new file mode 100644 index 0000000000..e7b658ef7d --- /dev/null +++ b/tests/in/dualsource.wgsl @@ -0,0 +1,11 @@ +/* Simple test for multiple output sources from fragment shaders */ +struct FragmentOutput{ + @location(0) color: vec4, + @location(0) @second_blend_source mask: vec4, +} +@fragment +fn main(@builtin(position) position: vec4) -> FragmentOutput { + var color = vec4(0.4,0.3,0.2,0.1); + var mask = vec4(0.9,0.8,0.7,0.6); + return FragmentOutput(color, mask); +} diff --git a/tests/out/analysis/access.info.ron b/tests/out/analysis/access.info.ron index 62e898d3c0..03186d9f18 100644 --- a/tests/out/analysis/access.info.ron +++ b/tests/out/analysis/access.info.ron @@ -1251,6 +1251,7 @@ ), ], sampling: [], + dual_source_blending: false, ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -2808,6 +2809,7 @@ ), ], sampling: [], + dual_source_blending: false, ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -2847,6 +2849,7 @@ ), ], sampling: [], + dual_source_blending: false, ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -2919,6 +2922,7 @@ ), ], sampling: [], + dual_source_blending: false, ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -2961,6 +2965,7 @@ ), ], sampling: [], + dual_source_blending: false, ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -3050,6 +3055,7 @@ ), ], sampling: [], + dual_source_blending: false, ), ], entry_points: [ @@ -3727,6 +3733,7 @@ ), ], sampling: [], + dual_source_blending: false, ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -4184,6 +4191,7 @@ ), ], sampling: [], + dual_source_blending: false, ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -4288,6 +4296,7 @@ ), ], sampling: [], + dual_source_blending: false, ), ], const_expression_types: [ diff --git a/tests/out/analysis/collatz.info.ron b/tests/out/analysis/collatz.info.ron index c0c222ba48..8241bf9a15 100644 --- a/tests/out/analysis/collatz.info.ron +++ b/tests/out/analysis/collatz.info.ron @@ -273,6 +273,7 @@ ), ], sampling: [], + dual_source_blending: false, ), ], entry_points: [ @@ -426,6 +427,7 @@ ), ], sampling: [], + dual_source_blending: false, ), ], const_expression_types: [], diff --git a/tests/out/analysis/shadow.info.ron b/tests/out/analysis/shadow.info.ron index 8af61bff53..7459466e2e 100644 --- a/tests/out/analysis/shadow.info.ron +++ b/tests/out/analysis/shadow.info.ron @@ -766,6 +766,7 @@ ), ], sampling: [], + dual_source_blending: false, ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -2091,6 +2092,7 @@ ), ], sampling: [], + dual_source_blending: false, ), ], entry_points: [ @@ -2183,6 +2185,7 @@ ), ], sampling: [], + dual_source_blending: false, ), ], const_expression_types: [ diff --git a/tests/out/glsl/dualsource.main.Fragment.glsl b/tests/out/glsl/dualsource.main.Fragment.glsl new file mode 100644 index 0000000000..9ac463c2b3 --- /dev/null +++ b/tests/out/glsl/dualsource.main.Fragment.glsl @@ -0,0 +1,27 @@ +#version 310 es +#extension GL_EXT_blend_func_extended : require + +precision highp float; +precision highp int; + +struct FragmentOutput { + vec4 color; + vec4 mask; +}; +layout(location = 0) out vec4 _fs2p_location0; +layout(location = 0, index = 1) out vec4 _fs2p_location1; + +void main() { + vec4 position = gl_FragCoord; + vec4 color = vec4(0.0); + vec4 mask = vec4(0.0); + color = vec4(0.4, 0.3, 0.2, 0.1); + mask = vec4(0.9, 0.8, 0.7, 0.6); + vec4 _e13 = color; + vec4 _e14 = mask; + FragmentOutput _tmp_return = FragmentOutput(_e13, _e14); + _fs2p_location0 = _tmp_return.color; + _fs2p_location1 = _tmp_return.mask; + return; +} + diff --git a/tests/out/hlsl/dualsource.hlsl b/tests/out/hlsl/dualsource.hlsl new file mode 100644 index 0000000000..1d5b53d6f8 --- /dev/null +++ b/tests/out/hlsl/dualsource.hlsl @@ -0,0 +1,29 @@ +struct FragmentOutput { + float4 color : SV_Target0; + float4 mask : SV_Target1; +}; + +struct FragmentInput_main { + float4 position_1 : SV_Position; +}; + +FragmentOutput ConstructFragmentOutput(float4 arg0, float4 arg1) { + FragmentOutput ret = (FragmentOutput)0; + ret.color = arg0; + ret.mask = arg1; + return ret; +} + +FragmentOutput main(FragmentInput_main fragmentinput_main) +{ + float4 position = fragmentinput_main.position_1; + float4 color = (float4)0; + float4 mask = (float4)0; + + color = float4(0.4, 0.3, 0.2, 0.1); + mask = float4(0.9, 0.8, 0.7, 0.6); + float4 _expr13 = color; + float4 _expr14 = mask; + const FragmentOutput fragmentoutput = ConstructFragmentOutput(_expr13, _expr14); + return fragmentoutput; +} diff --git a/tests/out/hlsl/dualsource.ron b/tests/out/hlsl/dualsource.ron new file mode 100644 index 0000000000..341a4c528e --- /dev/null +++ b/tests/out/hlsl/dualsource.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ], + fragment:[ + ( + entry_point:"main", + target_profile:"ps_5_1", + ), + ], + compute:[ + ], +) diff --git a/tests/out/ir/access.ron b/tests/out/ir/access.ron index 7447249127..5cda4104cd 100644 --- a/tests/out/ir/access.ron +++ b/tests/out/ir/access.ron @@ -2052,6 +2052,7 @@ ty: 26, binding: Some(Location( location: 0, + second_blend_source: false, interpolation: Some(Perspective), sampling: Some(Center), )), diff --git a/tests/out/ir/shadow.ron b/tests/out/ir/shadow.ron index 246657255b..9ae5af0003 100644 --- a/tests/out/ir/shadow.ron +++ b/tests/out/ir/shadow.ron @@ -1264,6 +1264,7 @@ ty: 2, binding: Some(Location( location: 0, + second_blend_source: false, interpolation: Some(Perspective), sampling: Some(Center), )), @@ -1273,6 +1274,7 @@ ty: 4, binding: Some(Location( location: 1, + second_blend_source: false, interpolation: Some(Perspective), sampling: Some(Center), )), @@ -1282,6 +1284,7 @@ ty: 4, binding: Some(Location( location: 0, + second_blend_source: false, interpolation: None, sampling: None, )), diff --git a/tests/out/msl/dualsource.msl b/tests/out/msl/dualsource.msl new file mode 100644 index 0000000000..92b1909f8b --- /dev/null +++ b/tests/out/msl/dualsource.msl @@ -0,0 +1,29 @@ +// language: metal2.0 +#include +#include + +using metal::uint; + +struct FragmentOutput { + metal::float4 color; + metal::float4 mask; +}; + +struct main_Input { +}; +struct main_Output { + metal::float4 color [[color(0)]]; + metal::float4 mask [[color(0) index(1)]]; +}; +fragment main_Output main_( + metal::float4 position [[position]] +) { + metal::float4 color = {}; + metal::float4 mask = {}; + color = metal::float4(0.4, 0.3, 0.2, 0.1); + mask = metal::float4(0.9, 0.8, 0.7, 0.6); + metal::float4 _e13 = color; + metal::float4 _e14 = mask; + const auto _tmp = FragmentOutput {_e13, _e14}; + return main_Output { _tmp.color, _tmp.mask }; +} diff --git a/tests/out/spv/dualsource.spvasm b/tests/out/spv/dualsource.spvasm new file mode 100644 index 0000000000..0fba6bb269 --- /dev/null +++ b/tests/out/spv/dualsource.spvasm @@ -0,0 +1,55 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 35 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %17 "main" %11 %14 %16 +OpExecutionMode %17 OriginUpperLeft +OpMemberDecorate %5 0 Offset 0 +OpMemberDecorate %5 1 Offset 16 +OpDecorate %11 BuiltIn FragCoord +OpDecorate %14 Location 0 +OpDecorate %16 Location 0 +OpDecorate %16 Index 1 +%2 = OpTypeVoid +%4 = OpTypeFloat 32 +%3 = OpTypeVector %4 4 +%5 = OpTypeStruct %3 %3 +%7 = OpTypePointer Function %3 +%8 = OpConstantNull %3 +%12 = OpTypePointer Input %3 +%11 = OpVariable %12 Input +%15 = OpTypePointer Output %3 +%14 = OpVariable %15 Output +%16 = OpVariable %15 Output +%18 = OpTypeFunction %2 +%19 = OpConstant %4 0.4 +%20 = OpConstant %4 0.3 +%21 = OpConstant %4 0.2 +%22 = OpConstant %4 0.1 +%23 = OpConstant %4 0.9 +%24 = OpConstant %4 0.8 +%25 = OpConstant %4 0.7 +%26 = OpConstant %4 0.6 +%17 = OpFunction %2 None %18 +%10 = OpLabel +%6 = OpVariable %7 Function %8 +%9 = OpVariable %7 Function %8 +%13 = OpLoad %3 %11 +OpBranch %27 +%27 = OpLabel +%28 = OpCompositeConstruct %3 %19 %20 %21 %22 +OpStore %6 %28 +%29 = OpCompositeConstruct %3 %23 %24 %25 %26 +OpStore %9 %29 +%30 = OpLoad %3 %6 +%31 = OpLoad %3 %9 +%32 = OpCompositeConstruct %5 %30 %31 +%33 = OpCompositeExtract %3 %32 0 +OpStore %14 %33 +%34 = OpCompositeExtract %3 %32 1 +OpStore %16 %34 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/tests/out/wgsl/dualsource.wgsl b/tests/out/wgsl/dualsource.wgsl new file mode 100644 index 0000000000..d7cc4cbfdc --- /dev/null +++ b/tests/out/wgsl/dualsource.wgsl @@ -0,0 +1,16 @@ +struct FragmentOutput { + @location(0) color: vec4, + @location(0) @second_blend_source mask: vec4, +} + +@fragment +fn main(@builtin(position) position: vec4) -> FragmentOutput { + var color: vec4; + var mask: vec4; + + color = vec4(0.4, 0.3, 0.2, 0.1); + mask = vec4(0.9, 0.8, 0.7, 0.6); + let _e13 = color; + let _e14 = mask; + return FragmentOutput(_e13, _e14); +} diff --git a/tests/snapshots.rs b/tests/snapshots.rs index e1e1144139..a2e03679b2 100644 --- a/tests/snapshots.rs +++ b/tests/snapshots.rs @@ -512,6 +512,10 @@ fn convert_wgsl() { "fragment-output", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ), + ( + "dualsource", + Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, + ), ("functions-webgl", Targets::GLSL), ( "interpolate",