From 469261e5a68384fda36763839ed37519d719531b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 3 May 2025 22:22:23 +0200 Subject: [PATCH 1/4] Replace `mix` calls with proper alpha blending in `quad` shaders --- wgpu/src/quad.rs | 13 +----------- wgpu/src/shader/color/oklab.wgsl | 2 +- wgpu/src/shader/quad.wgsl | 4 ++++ wgpu/src/shader/quad/gradient.wgsl | 34 ++++++++++++++++++------------ wgpu/src/shader/quad/solid.wgsl | 24 ++++++++------------- 5 files changed, 35 insertions(+), 42 deletions(-) diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 4c00945c..b3ac3f48 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -317,18 +317,7 @@ fn color_target_state( ) -> [Option; 1] { [Some(wgpu::ColorTargetState { format, - blend: Some(wgpu::BlendState { - color: wgpu::BlendComponent { - src_factor: wgpu::BlendFactor::SrcAlpha, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - alpha: wgpu::BlendComponent { - src_factor: wgpu::BlendFactor::One, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - }), + blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING), write_mask: wgpu::ColorWrites::ALL, })] } diff --git a/wgpu/src/shader/color/oklab.wgsl b/wgpu/src/shader/color/oklab.wgsl index 0dc37ba6..343eaa15 100644 --- a/wgpu/src/shader/color/oklab.wgsl +++ b/wgpu/src/shader/color/oklab.wgsl @@ -20,7 +20,7 @@ fn interpolate_color(from_: vec4, to_: vec4, factor: f32) -> vec4 var color = to_rgb * (mixed * mixed * mixed); // Alpha interpolation - color.a = mix(from_.a, to_.a, factor); + color = to_ * factor + from_ * (1.0 - factor); return color; } diff --git a/wgpu/src/shader/quad.wgsl b/wgpu/src/shader/quad.wgsl index b213c8cf..0a4cd7b4 100644 --- a/wgpu/src/shader/quad.wgsl +++ b/wgpu/src/shader/quad.wgsl @@ -5,6 +5,10 @@ struct Globals { @group(0) @binding(0) var globals: Globals; +fn blend(over: vec4, under: vec4, alpha: f32) -> vec4 { + return over * alpha + under * (1.0 - alpha); +} + fn distance_alg( frag_coord: vec2, position: vec2, diff --git a/wgpu/src/shader/quad/gradient.wgsl b/wgpu/src/shader/quad/gradient.wgsl index 4ad2fea8..ddd7d1c4 100644 --- a/wgpu/src/shader/quad/gradient.wgsl +++ b/wgpu/src/shader/quad/gradient.wgsl @@ -56,7 +56,7 @@ fn gradient_vs_main(input: GradientVertexInput) -> GradientVertexOutput { out.offsets = input.offsets; out.direction = input.direction * globals.scale; out.position_and_scale = vec4(pos, scale); - out.border_color = input.border_color; + out.border_color = vec4(input.border_color.xyz * input.border_color.a, input.border_color.a); out.border_radius = border_radius * globals.scale; out.border_width = input.border_width * globals.scale; @@ -119,14 +119,14 @@ fn gradient( @fragment fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4 { let colors = array, 8>( - unpack_u32(input.colors_1.xy), - unpack_u32(input.colors_1.zw), - unpack_u32(input.colors_2.xy), - unpack_u32(input.colors_2.zw), - unpack_u32(input.colors_3.xy), - unpack_u32(input.colors_3.zw), - unpack_u32(input.colors_4.xy), - unpack_u32(input.colors_4.zw), + unpack_color(input.colors_1.xy), + unpack_color(input.colors_1.zw), + unpack_color(input.colors_2.xy), + unpack_color(input.colors_2.zw), + unpack_color(input.colors_3.xy), + unpack_color(input.colors_3.zw), + unpack_color(input.colors_4.xy), + unpack_color(input.colors_4.zw), ); let offsets_1: vec4 = unpack_u32(input.offsets.xy); @@ -179,7 +179,7 @@ fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4 { internal_distance ); - mixed_color = mix(mixed_color, input.border_color, vec4(border_mix, border_mix, border_mix, border_mix)); + mixed_color = blend(input.border_color, mixed_color, border_mix); } var dist: f32 = distance_alg( @@ -194,12 +194,18 @@ fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4 { border_radius + 0.5, dist); - return vec4(mixed_color.x, mixed_color.y, mixed_color.z, mixed_color.w * radius_alpha); + return mixed_color * radius_alpha; } -fn unpack_u32(color: vec2) -> vec4 { - let rg: vec2 = unpack2x16float(color.x); - let ba: vec2 = unpack2x16float(color.y); +fn unpack_color(data: vec2) -> vec4 { + let color = unpack_u32(data); + + return vec4(color.xyz * color.a, color.a); +} + +fn unpack_u32(data: vec2) -> vec4 { + let rg: vec2 = unpack2x16float(data.x); + let ba: vec2 = unpack2x16float(data.y); return vec4(rg.y, rg.x, ba.y, ba.x); } diff --git a/wgpu/src/shader/quad/solid.wgsl b/wgpu/src/shader/quad/solid.wgsl index 8eee16bb..979d7174 100644 --- a/wgpu/src/shader/quad/solid.wgsl +++ b/wgpu/src/shader/quad/solid.wgsl @@ -56,13 +56,13 @@ fn solid_vs_main(input: SolidVertexInput) -> SolidVertexOutput { ); out.position = globals.transform * transform * vec4(vertex_position(input.vertex_index), 0.0, 1.0); - out.color = input.color; - out.border_color = input.border_color; + out.color = vec4(input.color.xyz * input.color.a, input.color.a); + out.border_color = vec4(input.border_color.xyz * input.border_color.a, input.border_color.a); out.pos = input.pos * globals.scale + snap; out.scale = input.scale * globals.scale; out.border_radius = border_radius * globals.scale; out.border_width = input.border_width * globals.scale; - out.shadow_color = input.shadow_color; + out.shadow_color = vec4(input.shadow_color.xyz, input.shadow_color.a); out.shadow_offset = input.shadow_offset * globals.scale; out.shadow_blur_radius = input.shadow_blur_radius * globals.scale; @@ -97,7 +97,7 @@ fn solid_fs_main( internal_distance ); - mixed_color = mix(input.color, input.border_color, vec4(border_mix, border_mix, border_mix, border_mix)); + mixed_color = blend(input.border_color, input.color, border_mix); } var dist: f32 = distance_alg( @@ -113,7 +113,7 @@ fn solid_fs_main( dist ); - let quad_color = vec4(mixed_color.x, mixed_color.y, mixed_color.z, mixed_color.w * radius_alpha); + let quad_color = mixed_color * radius_alpha; if input.shadow_color.a > 0.0 { let shadow_radius = select_border_radius( @@ -121,17 +121,11 @@ fn solid_fs_main( input.position.xy - input.shadow_offset, (input.pos + input.scale * 0.5).xy ); - let shadow_distance = max(rounded_box_sdf(input.position.xy - input.pos - input.shadow_offset - (input.scale / 2.0), input.scale / 2.0, shadow_radius), 0.); - - let shadow_alpha = 1.0 - smoothstep(-input.shadow_blur_radius, input.shadow_blur_radius, shadow_distance); - let shadow_color = input.shadow_color; - let base_color = mix( - vec4(shadow_color.x, shadow_color.y, shadow_color.z, 0.0), - quad_color, - quad_color.a - ); - return mix(base_color, shadow_color, (1.0 - radius_alpha) * shadow_alpha); + let shadow_distance = max(rounded_box_sdf(input.position.xy - input.pos - input.shadow_offset - (input.scale / 2.0), input.scale / 2.0, shadow_radius), 0.); + let shadow_alpha = 1.0 - smoothstep(-input.shadow_blur_radius, input.shadow_blur_radius, shadow_distance); + + return blend(input.shadow_color, quad_color, (1.0 - radius_alpha) * shadow_alpha); } else { return quad_color; } From 9ac4c9f13d6caa1d64852554e6856c4a18d499bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 3 May 2025 22:33:55 +0200 Subject: [PATCH 2/4] Define `premultiply` helper for `quad` shaders --- wgpu/src/shader/quad.wgsl | 4 ++++ wgpu/src/shader/quad/gradient.wgsl | 6 ++---- wgpu/src/shader/quad/solid.wgsl | 6 +++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/wgpu/src/shader/quad.wgsl b/wgpu/src/shader/quad.wgsl index 0a4cd7b4..bef8bd40 100644 --- a/wgpu/src/shader/quad.wgsl +++ b/wgpu/src/shader/quad.wgsl @@ -5,6 +5,10 @@ struct Globals { @group(0) @binding(0) var globals: Globals; +fn premultiply(color: vec4) -> vec4 { + return vec4(color.xyz * color.a, color.a); +} + fn blend(over: vec4, under: vec4, alpha: f32) -> vec4 { return over * alpha + under * (1.0 - alpha); } diff --git a/wgpu/src/shader/quad/gradient.wgsl b/wgpu/src/shader/quad/gradient.wgsl index ddd7d1c4..d743e26d 100644 --- a/wgpu/src/shader/quad/gradient.wgsl +++ b/wgpu/src/shader/quad/gradient.wgsl @@ -56,7 +56,7 @@ fn gradient_vs_main(input: GradientVertexInput) -> GradientVertexOutput { out.offsets = input.offsets; out.direction = input.direction * globals.scale; out.position_and_scale = vec4(pos, scale); - out.border_color = vec4(input.border_color.xyz * input.border_color.a, input.border_color.a); + out.border_color = premultiply(input.border_color); out.border_radius = border_radius * globals.scale; out.border_width = input.border_width * globals.scale; @@ -198,9 +198,7 @@ fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4 { } fn unpack_color(data: vec2) -> vec4 { - let color = unpack_u32(data); - - return vec4(color.xyz * color.a, color.a); + return premultiply(unpack_u32(data)); } fn unpack_u32(data: vec2) -> vec4 { diff --git a/wgpu/src/shader/quad/solid.wgsl b/wgpu/src/shader/quad/solid.wgsl index 979d7174..d5d1e604 100644 --- a/wgpu/src/shader/quad/solid.wgsl +++ b/wgpu/src/shader/quad/solid.wgsl @@ -56,13 +56,13 @@ fn solid_vs_main(input: SolidVertexInput) -> SolidVertexOutput { ); out.position = globals.transform * transform * vec4(vertex_position(input.vertex_index), 0.0, 1.0); - out.color = vec4(input.color.xyz * input.color.a, input.color.a); - out.border_color = vec4(input.border_color.xyz * input.border_color.a, input.border_color.a); + out.color = premultiply(input.color); + out.border_color = premultiply(input.border_color); out.pos = input.pos * globals.scale + snap; out.scale = input.scale * globals.scale; out.border_radius = border_radius * globals.scale; out.border_width = input.border_width * globals.scale; - out.shadow_color = vec4(input.shadow_color.xyz, input.shadow_color.a); + out.shadow_color = premultiply(input.shadow_color); out.shadow_offset = input.shadow_offset * globals.scale; out.shadow_blur_radius = input.shadow_blur_radius * globals.scale; From df37d6f9bc0471cd6c495ae523aa6242d88b5aa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 12 May 2025 18:11:51 +0200 Subject: [PATCH 3/4] Replace `blend` with `mix` equivalent in `quad` shaders --- wgpu/src/shader/quad.wgsl | 4 ---- wgpu/src/shader/quad/gradient.wgsl | 2 +- wgpu/src/shader/quad/solid.wgsl | 4 ++-- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/wgpu/src/shader/quad.wgsl b/wgpu/src/shader/quad.wgsl index bef8bd40..ff9fdae6 100644 --- a/wgpu/src/shader/quad.wgsl +++ b/wgpu/src/shader/quad.wgsl @@ -9,10 +9,6 @@ fn premultiply(color: vec4) -> vec4 { return vec4(color.xyz * color.a, color.a); } -fn blend(over: vec4, under: vec4, alpha: f32) -> vec4 { - return over * alpha + under * (1.0 - alpha); -} - fn distance_alg( frag_coord: vec2, position: vec2, diff --git a/wgpu/src/shader/quad/gradient.wgsl b/wgpu/src/shader/quad/gradient.wgsl index d743e26d..e8c54790 100644 --- a/wgpu/src/shader/quad/gradient.wgsl +++ b/wgpu/src/shader/quad/gradient.wgsl @@ -179,7 +179,7 @@ fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4 { internal_distance ); - mixed_color = blend(input.border_color, mixed_color, border_mix); + mixed_color = mix(mixed_color, input.border_color, border_mix); } var dist: f32 = distance_alg( diff --git a/wgpu/src/shader/quad/solid.wgsl b/wgpu/src/shader/quad/solid.wgsl index d5d1e604..f4c39f3d 100644 --- a/wgpu/src/shader/quad/solid.wgsl +++ b/wgpu/src/shader/quad/solid.wgsl @@ -97,7 +97,7 @@ fn solid_fs_main( internal_distance ); - mixed_color = blend(input.border_color, input.color, border_mix); + mixed_color = mix(input.color, input.border_color, border_mix); } var dist: f32 = distance_alg( @@ -125,7 +125,7 @@ fn solid_fs_main( let shadow_distance = max(rounded_box_sdf(input.position.xy - input.pos - input.shadow_offset - (input.scale / 2.0), input.scale / 2.0, shadow_radius), 0.); let shadow_alpha = 1.0 - smoothstep(-input.shadow_blur_radius, input.shadow_blur_radius, shadow_distance); - return blend(input.shadow_color, quad_color, (1.0 - radius_alpha) * shadow_alpha); + return mix(quad_color, input.shadow_color, (1.0 - radius_alpha) * shadow_alpha); } else { return quad_color; } From 098bd1b8a407eb568db1d938f2f7ffca4e18dbd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 12 May 2025 18:35:27 +0200 Subject: [PATCH 4/4] Use premultiplied colors in `triangle` pipelines --- wgpu/src/quad/gradient.rs | 4 ++++ wgpu/src/quad/solid.rs | 2 ++ wgpu/src/shader/color.wgsl | 14 ++++++++++++++ wgpu/src/shader/color/oklab.wgsl | 2 +- wgpu/src/shader/quad.wgsl | 4 ---- wgpu/src/shader/quad/gradient.wgsl | 11 ----------- wgpu/src/shader/triangle/gradient.wgsl | 23 ++++++++--------------- wgpu/src/shader/triangle/solid.wgsl | 2 +- wgpu/src/triangle.rs | 8 +++++++- 9 files changed, 37 insertions(+), 33 deletions(-) create mode 100644 wgpu/src/shader/color.wgsl diff --git a/wgpu/src/quad/gradient.rs b/wgpu/src/quad/gradient.rs index 3c5fc33f..351e941d 100644 --- a/wgpu/src/quad/gradient.rs +++ b/wgpu/src/quad/gradient.rs @@ -97,6 +97,8 @@ impl Pipeline { "../shader/quad/gradient.wgsl" ), "\n", + include_str!("../shader/color.wgsl"), + "\n", include_str!("../shader/color/oklab.wgsl") ) } else { @@ -109,6 +111,8 @@ impl Pipeline { "../shader/quad/gradient.wgsl" ), "\n", + include_str!("../shader/color.wgsl"), + "\n", include_str!( "../shader/color/linear_rgb.wgsl" ) diff --git a/wgpu/src/quad/solid.rs b/wgpu/src/quad/solid.rs index 317a248c..fa1db172 100644 --- a/wgpu/src/quad/solid.rs +++ b/wgpu/src/quad/solid.rs @@ -74,6 +74,8 @@ impl Pipeline { label: Some("iced_wgpu.quad.solid.shader"), source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( concat!( + include_str!("../shader/color.wgsl"), + "\n", include_str!("../shader/quad.wgsl"), "\n", include_str!("../shader/vertex.wgsl"), diff --git a/wgpu/src/shader/color.wgsl b/wgpu/src/shader/color.wgsl new file mode 100644 index 00000000..a8066368 --- /dev/null +++ b/wgpu/src/shader/color.wgsl @@ -0,0 +1,14 @@ +fn premultiply(color: vec4) -> vec4 { + return vec4(color.xyz * color.a, color.a); +} + +fn unpack_color(data: vec2) -> vec4 { + return premultiply(unpack_u32(data)); +} + +fn unpack_u32(data: vec2) -> vec4 { + let rg: vec2 = unpack2x16float(data.x); + let ba: vec2 = unpack2x16float(data.y); + + return vec4(rg.y, rg.x, ba.y, ba.x); +} diff --git a/wgpu/src/shader/color/oklab.wgsl b/wgpu/src/shader/color/oklab.wgsl index 343eaa15..0dc37ba6 100644 --- a/wgpu/src/shader/color/oklab.wgsl +++ b/wgpu/src/shader/color/oklab.wgsl @@ -20,7 +20,7 @@ fn interpolate_color(from_: vec4, to_: vec4, factor: f32) -> vec4 var color = to_rgb * (mixed * mixed * mixed); // Alpha interpolation - color = to_ * factor + from_ * (1.0 - factor); + color.a = mix(from_.a, to_.a, factor); return color; } diff --git a/wgpu/src/shader/quad.wgsl b/wgpu/src/shader/quad.wgsl index ff9fdae6..b213c8cf 100644 --- a/wgpu/src/shader/quad.wgsl +++ b/wgpu/src/shader/quad.wgsl @@ -5,10 +5,6 @@ struct Globals { @group(0) @binding(0) var globals: Globals; -fn premultiply(color: vec4) -> vec4 { - return vec4(color.xyz * color.a, color.a); -} - fn distance_alg( frag_coord: vec2, position: vec2, diff --git a/wgpu/src/shader/quad/gradient.wgsl b/wgpu/src/shader/quad/gradient.wgsl index e8c54790..a1b4b107 100644 --- a/wgpu/src/shader/quad/gradient.wgsl +++ b/wgpu/src/shader/quad/gradient.wgsl @@ -196,14 +196,3 @@ fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4 { return mixed_color * radius_alpha; } - -fn unpack_color(data: vec2) -> vec4 { - return premultiply(unpack_u32(data)); -} - -fn unpack_u32(data: vec2) -> vec4 { - let rg: vec2 = unpack2x16float(data.x); - let ba: vec2 = unpack2x16float(data.y); - - return vec4(rg.y, rg.x, ba.y, ba.x); -} diff --git a/wgpu/src/shader/triangle/gradient.wgsl b/wgpu/src/shader/triangle/gradient.wgsl index 1a8ae3b5..31a48026 100644 --- a/wgpu/src/shader/triangle/gradient.wgsl +++ b/wgpu/src/shader/triangle/gradient.wgsl @@ -87,14 +87,14 @@ fn gradient( @fragment fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4 { let colors = array, 8>( - unpack_u32(input.colors_1.xy), - unpack_u32(input.colors_1.zw), - unpack_u32(input.colors_2.xy), - unpack_u32(input.colors_2.zw), - unpack_u32(input.colors_3.xy), - unpack_u32(input.colors_3.zw), - unpack_u32(input.colors_4.xy), - unpack_u32(input.colors_4.zw), + unpack_color(input.colors_1.xy), + unpack_color(input.colors_1.zw), + unpack_color(input.colors_2.xy), + unpack_color(input.colors_2.zw), + unpack_color(input.colors_3.xy), + unpack_color(input.colors_3.zw), + unpack_color(input.colors_4.xy), + unpack_color(input.colors_4.zw), ); let offsets_1: vec4 = unpack_u32(input.offsets.xy); @@ -122,13 +122,6 @@ fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4 { return gradient(input.raw_position, input.direction, colors, offsets, last_index); } -fn unpack_u32(color: vec2) -> vec4 { - let rg: vec2 = unpack2x16float(color.x); - let ba: vec2 = unpack2x16float(color.y); - - return vec4(rg.y, rg.x, ba.y, ba.x); -} - fn random(coords: vec2) -> f32 { return fract(sin(dot(coords, vec2(12.9898,78.233))) * 43758.5453); } diff --git a/wgpu/src/shader/triangle/solid.wgsl b/wgpu/src/shader/triangle/solid.wgsl index 9ef81982..b0d8057b 100644 --- a/wgpu/src/shader/triangle/solid.wgsl +++ b/wgpu/src/shader/triangle/solid.wgsl @@ -12,7 +12,7 @@ struct SolidVertexOutput { fn solid_vs_main(input: SolidVertexInput) -> SolidVertexOutput { var out: SolidVertexOutput; - out.color = input.color; + out.color = premultiply(input.color); out.position = globals.transform * vec4(input.position, 0.0, 1.0); return out; diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 6d0b0322..a3f850dd 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -595,7 +595,7 @@ fn fragment_target( ) -> wgpu::ColorTargetState { wgpu::ColorTargetState { format: texture_format, - blend: Some(wgpu::BlendState::ALPHA_BLENDING), + blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING), write_mask: wgpu::ColorWrites::ALL, } } @@ -754,6 +754,8 @@ mod solid { include_str!("shader/triangle.wgsl"), "\n", include_str!("shader/triangle/solid.wgsl"), + "\n", + include_str!("shader/color.wgsl"), )), ), }); @@ -913,6 +915,8 @@ mod gradient { "shader/triangle/gradient.wgsl" ), "\n", + include_str!("shader/color.wgsl"), + "\n", include_str!("shader/color/oklab.wgsl") ) } else { @@ -923,6 +927,8 @@ mod gradient { "shader/triangle/gradient.wgsl" ), "\n", + include_str!("shader/color.wgsl"), + "\n", include_str!( "shader/color/linear_rgb.wgsl" )