Skip to content

[wgsl-in, wgsl-out, glsl-in] WebGPU compliant dual source blending feature #7146

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,33 @@ allow you to be explicit about whether features you need are available on the we

By @Vecvec in [#6905](https://github.com/gfx-rs/wgpu/pull/6905), [#7086](https://github.com/gfx-rs/wgpu/pull/7086)

#### WebGPU compliant dual source blending feature

Previously, dual source blending was implemented with a `wgpu` native only feature flag and used a custom syntax in wgpu.
By now, dual source blending was added to the [WebGPU spec as an extension](https://www.w3.org/TR/webgpu/#dom-gpufeaturename-dual-source-blending).
We're now following suite and implement the official syntax.

Existing shaders using dual source blending need to be updated:

```diff
struct FragmentOutput{
- @location(0) source0: vec4<f32>,
- @location(0) @second_blend_source source1: vec4<f32>,
+ @location(0) @blend_src(0) source0: vec4<f32>,
+ @location(0) @blend_src(1) source1: vec4<f32>,
}

```
With that `wgpu::Features::DUAL_SOURCE_BLENDING` is now available on WebGPU.

Furthermore, GLSL shaders now support dual source blending as well via the `index` layout qualifier:
```c
layout(location = 0, index = 0) out vec4 output0;
layout(location = 0, index = 1) out vec4 output1;
```

By @wumpf in [#7144](https://github.com/gfx-rs/wgpu/pull/7144)

### New Features

- Added mesh shader support to `wgpu_hal`. By @SupaMaggie70Incorporated in [#7089](https://github.com/gfx-rs/wgpu/pull/7089)
Expand Down
4 changes: 2 additions & 2 deletions naga/src/back/glsl/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -605,15 +605,15 @@ impl<W> Writer<'_, W> {
location: _,
interpolation,
sampling,
second_blend_source,
blend_src,
} => {
if interpolation == Some(Interpolation::Linear) {
self.features.request(Features::NOPERSPECTIVE_QUALIFIER);
}
if sampling == Some(Sampling::Sample) {
self.features.request(Features::SAMPLE_QUALIFIER);
}
if second_blend_source {
if blend_src.is_some() {
self.features.request(Features::DUAL_SOURCE_BLENDING);
}
}
Expand Down
20 changes: 11 additions & 9 deletions naga/src/back/glsl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -465,8 +465,7 @@ impl fmt::Display for VaryingName<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self.binding {
crate::Binding::Location {
second_blend_source: true,
..
blend_src: Some(1), ..
} => {
write!(f, "_fs2p_location1",)
}
Expand Down Expand Up @@ -1498,13 +1497,13 @@ impl<'a, W: Write> Writer<'a, W> {
Some(binding) => binding,
};

let (location, interpolation, sampling, second_blend_source) = match *binding {
let (location, interpolation, sampling, blend_src) = match *binding {
crate::Binding::Location {
location,
interpolation,
sampling,
second_blend_source,
} => (location, interpolation, sampling, second_blend_source),
blend_src,
} => (location, interpolation, sampling, blend_src),
crate::Binding::BuiltIn(built_in) => {
if let crate::BuiltIn::Position { invariant: true } = built_in {
match (self.options.version, self.entry_point.stage) {
Expand Down Expand Up @@ -1551,16 +1550,19 @@ impl<'a, W: Write> Writer<'a, W> {
|| !emit_interpolation_and_auxiliary
{
if self.options.version.supports_io_locations() {
if second_blend_source {
write!(self.out, "layout(location = {location}, index = 1) ")?;
if let Some(blend_src) = blend_src {
write!(
self.out,
"layout(location = {location}, index = {blend_src}) "
)?;
} else {
write!(self.out, "layout(location = {location}) ")?;
}
None
} else {
Some(VaryingLocation {
location,
index: second_blend_source as u32,
index: blend_src.unwrap_or(0),
})
}
} else {
Expand Down Expand Up @@ -1601,7 +1603,7 @@ impl<'a, W: Write> Writer<'a, W> {
location,
interpolation: None,
sampling: None,
second_blend_source,
blend_src,
},
stage: self.entry_point.stage,
options: VaryingOptions::from_writer_options(self.options, output),
Expand Down
9 changes: 2 additions & 7 deletions naga/src/back/hlsl/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -531,16 +531,11 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> {
write!(self.out, " : {builtin_str}")?;
}
Some(crate::Binding::Location {
second_blend_source: true,
..
blend_src: Some(1), ..
}) => {
write!(self.out, " : SV_Target1")?;
}
Some(crate::Binding::Location {
location,
second_blend_source: false,
..
}) => {
Some(crate::Binding::Location { location, .. }) => {
if stage == Some((ShaderStage::Fragment, Io::Output)) {
write!(self.out, " : SV_Target{location}")?;
} else {
Expand Down
18 changes: 8 additions & 10 deletions naga/src/back/msl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ enum ResolvedBinding {
Attribute(u32),
Color {
location: u32,
second_blend_source: bool,
blend_src: Option<u32>,
},
User {
prefix: &'static str,
Expand Down Expand Up @@ -467,18 +467,16 @@ impl Options {
location,
interpolation,
sampling,
second_blend_source,
blend_src,
} => match mode {
LocationMode::VertexInput => Ok(ResolvedBinding::Attribute(location)),
LocationMode::FragmentOutput => {
if second_blend_source && self.lang_version < (1, 2) {
return Err(Error::UnsupportedAttribute(
"second_blend_source".to_string(),
));
if blend_src.is_some() && self.lang_version < (1, 2) {
return Err(Error::UnsupportedAttribute("blend_src".to_string()));
}
Ok(ResolvedBinding::Color {
location,
second_blend_source,
blend_src,
})
}
LocationMode::VertexOutput | LocationMode::FragmentInput => {
Expand Down Expand Up @@ -640,10 +638,10 @@ impl ResolvedBinding {
Self::Attribute(index) => write!(out, "attribute({index})")?,
Self::Color {
location,
second_blend_source,
blend_src,
} => {
if second_blend_source {
write!(out, "color({location}) index(1)")?
if let Some(blend_src) = blend_src {
write!(out, "color({location}) index({blend_src})")?
} else {
write!(out, "color({location})")?
}
Expand Down
6 changes: 3 additions & 3 deletions naga/src/back/spv/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1801,7 +1801,7 @@ impl Writer {
location,
interpolation,
sampling,
second_blend_source,
blend_src,
} => {
self.decorate(id, Decoration::Location, &[location]);

Expand Down Expand Up @@ -1846,8 +1846,8 @@ impl Writer {
}
}
}
if second_blend_source {
self.decorate(id, Decoration::Index, &[1]);
if let Some(blend_src) = blend_src {
self.decorate(id, Decoration::Index, &[blend_src]);
}
}
crate::Binding::BuiltIn(built_in) => {
Expand Down
39 changes: 34 additions & 5 deletions naga/src/back/wgsl/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ enum Attribute {
Invariant,
Interpolate(Option<crate::Interpolation>, Option<crate::Sampling>),
Location(u32),
SecondBlendSource,
BlendSrc(u32),
Stage(ShaderStage),
WorkGroupSize([u32; 3]),
}
Expand Down Expand Up @@ -127,6 +127,9 @@ impl<W: Write> Writer<W> {

self.reset(module);

// Write all needed directives.
self.write_enable_dual_source_blending_if_needed(module)?;

// Write all structs
for (handle, ty) in module.types.iter() {
if let TypeInner::Struct { ref members, .. } = ty.inner {
Expand Down Expand Up @@ -319,7 +322,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::BlendSrc(blend_src) => write!(self.out, "@blend_src({blend_src}) ")?,
Attribute::BuiltIn(builtin_attrib) => {
let builtin = builtin_attrib.to_wgsl_if_implemented()?;
write!(self.out, "@builtin({builtin}) ")?;
Expand Down Expand Up @@ -363,6 +366,32 @@ impl<W: Write> Writer<W> {
Ok(())
}

/// Writes all the necessary directives out
fn write_enable_dual_source_blending_if_needed(&mut self, module: &Module) -> BackendResult {
// Check for dual source blending.
if module.types.iter().any(|(_handle, ty)| {
if let TypeInner::Struct { ref members, .. } = ty.inner {
members.iter().any(|member| {
member.binding.as_ref().is_some_and(|binding| {
matches!(
binding,
&crate::Binding::Location {
blend_src: Some(_),
..
}
)
})
})
} else {
false
}
}) {
writeln!(self.out, "enable dual_source_blending;")?;
}

Ok(())
}

/// Helper method used to write structs
///
/// # Notes
Expand Down Expand Up @@ -1888,7 +1917,7 @@ fn map_binding_to_attribute(binding: &crate::Binding) -> Vec<Attribute> {
location,
interpolation,
sampling,
second_blend_source: false,
blend_src: None,
} => vec![
Attribute::Location(location),
Attribute::Interpolate(interpolation, sampling),
Expand All @@ -1897,10 +1926,10 @@ fn map_binding_to_attribute(binding: &crate::Binding) -> Vec<Attribute> {
location,
interpolation,
sampling,
second_blend_source: true,
blend_src: Some(blend_src),
} => vec![
Attribute::Location(location),
Attribute::SecondBlendSource,
Attribute::BlendSrc(blend_src),
Attribute::Interpolate(interpolation, sampling),
],
}
Expand Down
2 changes: 2 additions & 0 deletions naga/src/front/glsl/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ pub enum QualifierKey<'a> {
Layout,
/// Used for image formats
Format,
/// Used for `index` layout qualifiers
Index,
}

#[derive(Debug)]
Expand Down
4 changes: 2 additions & 2 deletions naga/src/front/glsl/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1449,7 +1449,7 @@ impl Context<'_> {
location,
interpolation,
sampling: None,
second_blend_source: false,
blend_src: None,
};
location += 1;

Expand Down Expand Up @@ -1485,7 +1485,7 @@ impl Context<'_> {
location,
interpolation,
sampling: None,
second_blend_source: false,
blend_src: None,
};
location += 1;
binding
Expand Down
7 changes: 7 additions & 0 deletions naga/src/front/glsl/parser/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,13 @@ impl ParsingContext<'_> {
QualifierKey::Layout,
QualifierValue::Layout(StructLayout::Std430),
),
"index" => {
self.expect(frontend, TokenValue::Assign)?;
let (value, end_meta) = self.parse_uint_constant(frontend, ctx)?;
token.meta.subsume(end_meta);

(QualifierKey::Index, QualifierValue::Uint(value))
}
word => {
if let Some(format) = map_image_format(word) {
(QualifierKey::Format, QualifierValue::Format(format))
Expand Down
10 changes: 9 additions & 1 deletion naga/src/front/glsl/variables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -449,14 +449,22 @@ impl Frontend {
meta,
);

let blend_src = qualifiers
.layout_qualifiers
.remove(&QualifierKey::Index)
.and_then(|(value, _span)| match value {
QualifierValue::Uint(index) => Some(index),
_ => None,
});

let idx = self.entry_args.len();
self.entry_args.push(EntryArg {
name: name.clone(),
binding: Binding::Location {
location,
interpolation,
sampling,
second_blend_source: false,
blend_src,
},
handle,
storage,
Expand Down
2 changes: 1 addition & 1 deletion naga/src/front/interpolator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ impl crate::Binding {
location: _,
interpolation: ref mut interpolation @ None,
ref mut sampling,
second_blend_source: _,
blend_src: _,
} = *self
{
match ty.scalar_kind() {
Expand Down
2 changes: 1 addition & 1 deletion naga/src/front/spv/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ impl Decoration {
location,
interpolation,
sampling,
second_blend_source: false,
blend_src: None,
}),
_ => Err(Error::MissingDecoration(spirv::Decoration::Location)),
}
Expand Down
10 changes: 8 additions & 2 deletions naga/src/front/wgsl/lower/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3489,15 +3489,21 @@ impl<'source, 'temp> Lowerer<'source, 'temp> {
Some(ast::Binding::BuiltIn(b)) => Some(crate::Binding::BuiltIn(b)),
Some(ast::Binding::Location {
location,
second_blend_source,
interpolation,
sampling,
blend_src,
}) => {
let blend_src = if let Some(blend_src) = blend_src {
Some(self.const_u32(blend_src, &mut ctx.as_const())?.0)
} else {
None
};

let mut binding = crate::Binding::Location {
location: self.const_u32(location, &mut ctx.as_const())?.0,
second_blend_source,
interpolation,
sampling,
blend_src,
};
binding.apply_default_interpolation(&ctx.module.types[ty].inner);
Some(binding)
Expand Down
2 changes: 1 addition & 1 deletion naga/src/front/wgsl/parse/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,9 @@ pub enum Binding<'a> {
BuiltIn(crate::BuiltIn),
Location {
location: Handle<Expression<'a>>,
second_blend_source: bool,
interpolation: Option<crate::Interpolation>,
sampling: Option<crate::Sampling>,
blend_src: Option<Handle<Expression<'a>>>,
},
}

Expand Down
Loading
Loading