Support dual source blending (#2427)

Co-authored-by: teoxoy <28601907+teoxoy@users.noreply.github.com>
This commit is contained in:
Frederik Magnus Johansen Vestre
2023-08-30 21:00:35 +02:00
committed by GitHub
parent 3bd2834b4f
commit 0491d39232
29 changed files with 376 additions and 9 deletions

View File

@@ -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);
}
}
}
}

View File

@@ -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,

View File

@@ -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 {

View File

@@ -82,7 +82,10 @@ pub type EntryPointResourceMap = std::collections::BTreeMap<String, EntryPointRe
enum ResolvedBinding {
BuiltIn(crate::BuiltIn),
Attribute(u32),
Color(u32),
Color {
location: u32,
second_blend_source: bool,
},
User {
prefix: &'static str,
index: u32,
@@ -245,9 +248,20 @@ impl Options {
location,
interpolation,
sampling,
second_blend_source,
} => 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,

View File

@@ -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;

View File

@@ -17,6 +17,7 @@ enum Attribute {
Invariant,
Interpolate(Option<crate::Interpolation>, Option<crate::Sampling>),
Location(u32),
SecondBlendSource,
Stage(ShaderStage),
WorkGroupSize([u32; 3]),
}
@@ -319,6 +320,7 @@ impl<W: Write> Writer<W> {
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<Attribute> {
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),
],
}
}

View File

@@ -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

View File

@@ -475,6 +475,7 @@ impl Frontend {
location,
interpolation,
sampling,
second_blend_source: false,
},
handle,
storage,

View File

@@ -43,6 +43,7 @@ impl crate::Binding {
location: _,
interpolation: ref mut interpolation @ None,
ref mut sampling,
second_blend_source: _,
} = *self
{
match ty.scalar_kind() {

View File

@@ -255,6 +255,7 @@ impl Decoration {
location,
interpolation,
sampling,
second_blend_source: false,
}),
_ => Err(Error::MissingDecoration(spirv::Decoration::Location)),
}

View File

@@ -143,6 +143,7 @@ impl<T> ParsedAttribute<T> {
#[derive(Default)]
struct BindingParser {
location: ParsedAttribute<u32>,
second_blend_source: ParsedAttribute<bool>,
built_in: ParsedAttribute<crate::BuiltIn>,
interpolation: ParsedAttribute<crate::Interpolation>,
sampling: ParsedAttribute<crate::Sampling>,
@@ -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) => {

View File

@@ -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<Interpolation>,
sampling: Option<Sampling>,
},

View File

@@ -256,6 +256,9 @@ pub struct FunctionInfo {
///
/// [`GlobalVariable`]: crate::GlobalVariable
sampling: crate::FastHashSet<Sampling>,
/// 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(),

View File

@@ -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<crate::Type>,
type_info: &'a Vec<super::r#type::TypeInfo>,
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

View File

@@ -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;
}
}

View File

@@ -0,0 +1,13 @@
(
god_mode: true,
vertex:[
],
fragment:[
(
entry_point:"main",
target_profile:"ps_5_1",
),
],
compute:[
],
)

11
tests/in/dualsource.wgsl Normal file
View File

@@ -0,0 +1,11 @@
/* Simple test for multiple output sources from fragment shaders */
struct FragmentOutput{
@location(0) color: vec4<f32>,
@location(0) @second_blend_source mask: vec4<f32>,
}
@fragment
fn main(@builtin(position) position: vec4<f32>) -> 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);
}

View File

@@ -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: [

View File

@@ -273,6 +273,7 @@
),
],
sampling: [],
dual_source_blending: false,
),
],
entry_points: [
@@ -426,6 +427,7 @@
),
],
sampling: [],
dual_source_blending: false,
),
],
const_expression_types: [],

View File

@@ -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: [

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -0,0 +1,12 @@
(
vertex:[
],
fragment:[
(
entry_point:"main",
target_profile:"ps_5_1",
),
],
compute:[
],
)

View File

@@ -2052,6 +2052,7 @@
ty: 26,
binding: Some(Location(
location: 0,
second_blend_source: false,
interpolation: Some(Perspective),
sampling: Some(Center),
)),

View File

@@ -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,
)),

View File

@@ -0,0 +1,29 @@
// language: metal2.0
#include <metal_stdlib>
#include <simd/simd.h>
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 };
}

View File

@@ -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

View File

@@ -0,0 +1,16 @@
struct FragmentOutput {
@location(0) color: vec4<f32>,
@location(0) @second_blend_source mask: vec4<f32>,
}
@fragment
fn main(@builtin(position) position: vec4<f32>) -> FragmentOutput {
var color: vec4<f32>;
var mask: vec4<f32>;
color = vec4<f32>(0.4, 0.3, 0.2, 0.1);
mask = vec4<f32>(0.9, 0.8, 0.7, 0.6);
let _e13 = color;
let _e14 = mask;
return FragmentOutput(_e13, _e14);
}

View File

@@ -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",