Skip to content
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

Dual Source Blending Investigation + Feature Proposal #4283

Open
bjjones opened this issue Sep 7, 2023 · 12 comments · May be fixed by #4621
Open

Dual Source Blending Investigation + Feature Proposal #4283

bjjones opened this issue Sep 7, 2023 · 12 comments · May be fixed by #4621

Comments

@bjjones
Copy link
Contributor

bjjones commented Sep 7, 2023

Overview

Dual-source blending is a feature available on most platforms that allows a developer to specify blending from two fragment shader outputs to a single framebuffer. This feature has some desirable applications and should be considered as an optional post-V1 WebGPU feature.

Usages

This feature was requested previously in issue #391 with some brief discussion of usages.

Additionally, I've pulled these two scenarios from the spec of the similar OpenGL extension:

Implementation of sub-pixel accurate font rendering algorithms. Given a known layout of
pixel elements (red, green and blue components) coverage may be calculated independently
for each element and passed to the blender in the second source color as a per-channel opacity.

Consider rendering a partially reflective colored glass window.
It will attenuate light passing through it, and reflect some of the light
that strikes it. Using an appropriate combination of functions, this effect
may be simulated in a single pass using only fixed-function blending
hardware.


D3D12

Dual source blending is supported on all D3D12 platforms as a core feature. [MSDN]

When using dual-source blending, writing to other render targets is undefined. [MSDN]

typedef enum D3D12_BLEND {
    ...,
    D3D12_BLEND_SRC1_COLOR,
    D3D12_BLEND_INV_SRC1_COLOR,
    D3D12_BLEND_SRC1_ALPHA,
    D3D12_BLEND_INV_SRC1_ALPHA,
};

[D3D12_BLEND Enum]

HLSL

To specify the two blend sources in an HLSL shader, you must use SV_Target0 and SV_Target1 as fragment shader outputs, which correspond to the indices of the dual-source blend. [MSDN]


Vulkan

Dual source blending is available on Vulkan as an extension. You must query the dualSrcBlend feature to determine if the required enumerations can be used. [Khronos]

The dualSrcBlend feature is available on 72.6% of devices. This feature is overwhelmingly supported on Windows, Linux, MacOS and iOS devices, however, only 49.5% of Android devices have support. Android devices that do not support dualSrcBlend primarily use ARM, ImgTec, and Qualcomm GPUs. [GPUInfo]

Vulkan's maxFragmentDualSrcAttachments limit is 1 for most platforms.

Vulkan specifies that the dual-source blend operation is undefined if the secondary source is not set. [Khronos]

typedef enum VkBlendFactor {
    ...,
    VK_BLEND_FACTOR_SRC1_COLOR,
    VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR,
    VK_BLEND_FACTOR_SRC1_ALPHA,
    VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA,
}

[VkBlendFactor Enum]

SPIR-V

SPIR-V uses the index n decoration following a location n decoration to specify the blend index of the output. [Khronos]


Metal

Dual-source blending appears to be available on all currently supported platforms [Metal Feature Set Tables]

Metal doesn't support multiple render targets when using dual-source blending [Apple Developer]

typedef enum MTLBlendFactor {
    ...,
    MTLBlendFactorSource1Color,
    MTLBlendFactorOneMinusSource1Color,
    MTLBlendFactorSource1Alpha,
    MTLBlendFactorOneMinusSource1Alpha,
};

[MTLBlendFactor Enum]

MSL

Metal Shading Language uses the index(n) attribute following a color(n) attribute to specify the blend index of the output. [Apple Developer]


Proposal

WebGPU:

Because dual source blending is a desirable feature, but is not supported on all platforms, we should add dual-source-blending as an optional post-V1 WebGPU feature.

dual-source-blending should add the following enums to GPUBlendFactor:

enum GPUBlendFactor {
    ...,
    "src1",
    "one-minus-src1",
    "src1-alpha",
    "one-minus-src1-alpha",
};

WGSL:

When dual-source-blending is enabled, allow the @index attribute to specify the dual-source blend index when added to a fragment shader output alongside the @location attribute.

An example shader:

struct ShaderIO {
   @location(0) @index(0) fragColor: vec4f,
   @location(0) @index(1) fragBlend: vec4f,
}

@fragment fn main() -> ShaderIO {
   var io: ShaderIO;
   io.fragColor = vec4f(1.0, 1.0, 1.0, 1.0);
   io.fragBlend = vec4f(0.5, 0.5, 0.5, 0.5);
   return io;
}

Restrictions:

  • src1 GPUBlendFactors must be restricted to color attachment 0. (Due to HLSL's restricted dual source blend output targets)
  • @index attribute must be restricted to @location(0). (Due to HLSL's restricted dual source blend output targets)
  • The developer must not write to any other render targets. (Due to this being unsupported on almost all platforms)
  • The developer must define the secondary blend source. (Due to this being undefined on at least Vulkan)
@teoxoy
Copy link
Member

teoxoy commented Sep 11, 2023

I find the @index(n) attribute to be too generic even though MSL, SPIR-V and GLSL use it.
I imagine most people will have to look up what it means if they are not already familiar with dual-source blending in GLSL/MSL.

There is also the fact that currently the index can only be 0 or 1.
Is there any indication that we'll get multiple blend sources (more than 2) in the future?

If we do, I'd propose to use something like @blend_source(n), or just @second_blend_source if we don't.

@Kangz
Copy link
Contributor

Kangz commented Oct 10, 2023

What about @blend_src1 to match the num? :) We should bikeshed the names a bit and come up with a PR, the group agreed to add this.

@bjjones
Copy link
Contributor Author

bjjones commented Oct 10, 2023

From the implementation perspective I think it's a bit neater to explicitly include an attribute on the src0 blend source. Underneath we do need to know for validation and backend shading language generation. If we just had @blend_src1 as suggested we'd do this:

  1. Visit first output and have no knowledge dual source blending is being used.
  2. Visit second output. Recognize it as blend src1.
  3. Revisit first output and recognize it as blend src0.

Which I think is less desirable than:

  1. Visit first output. Recognize it as blend src0
  2. Visit second output. Recognize it as blend src1

It's not a huge deal, but I don't see a significant benefit to leaving the src0 attribute implicit and the implementation could be cleaner with the explicit attribute.

I have no significant opinion on the naming. @blend_source(n) gets my vote.

@kainino0x
Copy link
Contributor

To collate suggestions on the WGSL syntax (from here and from a google/mozilla discussion), described by what attributes they'd allow:

    • @location(0) @index(0), @location(0) @index(1)
    • possibly allow: @location(0), @location(0) @index(1)
  1. ^ but rename @index to @blend_source
  2. ^ but rename @index to @blend_src
    • @location(0, src0), @location(0, src1)
    • possibly allow: @location(0), @location(0, src1)
  3. ^ but rename src1 to 1
  4. ^ but rename src1 to src=1
  5. ^ but rename src1 to index=1
    • @location(0), @location(0) @second_blend_source
  6. ^ but rename @second_blend_source to @blend_src1

@jimblandy
Copy link
Contributor

jimblandy commented Oct 11, 2023

I wanted to see what these really looked like:

// Sketch 1
struct FragOut {
    @location(0) @index(0) color: vec4<f32>, // @index(0) possibly optional
    @location(0) @index(1) blend: vec4<f32>,
}

// Sketch 2
struct FragOut {
    @location(0) @blend_source(0) color: vec4<f32>, // @blend_source(0) possibly optional
    @location(0) @blend_source(1) blend: vec4<f32>,
}

// Sketch 3
struct FragOut {
    @location(0) @blend_src(0) color: vec4<f32>, // @blend_src(0) possibly optional
    @location(0) @blend_src(1) blend: vec4<f32>,
}

// Sketch 4
struct FragOut {
    @location(0, src0) color: vec4<f32>, // src0 possibly optional
    @location(0, src1) blend: vec4<f32>,
}

// Sketch 5
struct FragOut {
    @location(0, 0) color: vec4<f32>, // ,0 possibly optional
    @location(0, 1) blend: vec4<f32>,
}

// Sketch 6
struct FragOut {
    @location(0, src=0) color: vec4<f32>, // src=0 possibly optional
    @location(0, src=1) blend: vec4<f32>,
}

// Sketch 7
struct FragOut {
    @location(0, index=0) color: vec4<f32>, // index=0 possibly optional
    @location(0, index=1) blend: vec4<f32>,
}

// Sketch 8
struct FragOut {
    @location(0) color: vec4<f32>,
    @location(0) @second_blend_source blend: vec4<f32>,
}

// Sketch 9 
struct FragOut {
    @location(0) color: vec4<f32>,
    @location(0) @blend_src1 blend: vec4<f32>,
}

@kainino0x

This comment was marked as outdated.

@jimblandy
Copy link
Contributor

jimblandy commented Oct 11, 2023

One angle from which to compare these: when the 0 and 1 are broken out into their own expressions, as opposed to being part of an identifier like @blend_src1, then it's easy to spec them as constant expressions, and then people can use const to name them as they please. But one might reply that readers might appreciate it if the spec just tells you what to name stuff.

@teoxoy
Copy link
Member

teoxoy commented Oct 11, 2023

Besides 1, 5 and 7 the others look good to me.

@Kangz
Copy link
Contributor

Kangz commented Oct 12, 2023

Same as @teoxoy though small preference for 2 and 3 overall.

@jimblandy
Copy link
Contributor

2 and 3 are my favorites.

@kainino0x
Copy link
Contributor

kainino0x commented Oct 13, 2023

Between 2 and 3 (or in 4/6/9), preference to match the spelling of "source"/"src" we use in the API spec.

@kdashg
Copy link
Contributor

kdashg commented Nov 30, 2023

GPU Web 2023-11-29 Atlantic-time
  • Corentin: Only thing is to bikeshed the WGSL syntax, see Jim's comment Dual Source Blending Investigation + Feature Proposal #4283 (comment)
  • CW: Most people seem satisfied with Is a clean break from WebGL really necessary? #2 and s/GPU for the Web/GPU on the Web/ #3 (@location(0) @blend_source(0) and @location(0) @blend_src(0) respectively)
  • TT: I would prefer s/GPU for the Web/GPU on the Web/ #3 as well, since it’s most similar to what we do on API side.
  • KN: What would make it required?
  • CW: Only when there’s one that's blend_src(1) as well.
  • KG: What happens if WGSL doesn't write to the blend_src.
  • TT: The question is what if you use the src1 factor but don't specify it in the shader?
  • KG: Grammar-wise blend_src is optional, when would blend_src(0) be required?
  • KN: We would require you to write @blend_src(0) if you also have a @blend_src(e.g. 1) on the same location. (but otherwise @blend_src(0) is implied)
  • TT: Would like it to not be optional since they are often written together.
  • CW: Consensus: blend_src(N), if blend_src(N) is used for a location, all other outputs for the same location must have a blend_src(M).
  • CW: What about question of setting blend_src on API side, but not writing blend_src(0) in WGSL?
  • TT: Remember looking at what happens in the backend APIs and it is undefined to use src1 if it is not in the shader.
  • KN: We already have rules like that for alpha / color.
  • CW: Ok let's validate it out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants