vello/piet-gpu/shader/blend.h
Raph Levien 18563101b2 Fix blending math
The blending math had two errors: first, colors were not separated for the purpose of blending (blending was wrongly applied to premultiplied values), and second, alpha was applied over-aggressively to the alpha channel.

This PR does *not* address the issue of gamma correctness. That is a complex issue and should probably be handled in the short term by disabling sRGB conversions and doing the internal math in sRGB color space rather than linear. This will degrade the quality of antialiasing but on the other hand give spec-compliant results for compositing.

We remove the plus-darker mode as its specification does not appear to be valid. The plus-lighter mode remains as it is quite useful for cross-fading effects.

Also the generated shaders were compiled on mac so the DXIL is unsigned. Those should be compiled on Windows before this PR is merged. (and we should figure out a better strategy for all that)
2022-05-13 10:18:29 -07:00

289 lines
6.2 KiB
GLSL

// SPDX-License-Identifier: Apache-2.0 OR MIT OR Unlicense
// Mode definitions and functions for blending and composition.
#define Blend_Normal 0
#define Blend_Multiply 1
#define Blend_Screen 2
#define Blend_Overlay 3
#define Blend_Darken 4
#define Blend_Lighten 5
#define Blend_ColorDodge 6
#define Blend_ColorBurn 7
#define Blend_HardLight 8
#define Blend_SoftLight 9
#define Blend_Difference 10
#define Blend_Exclusion 11
#define Blend_Hue 12
#define Blend_Saturation 13
#define Blend_Color 14
#define Blend_Luminosity 15
vec3 screen(vec3 cb, vec3 cs) {
return cb + cs - (cb * cs);
}
float color_dodge(float cb, float cs) {
if (cb == 0.0)
return 0.0;
else if (cs == 1.0)
return 1.0;
else
return min(1.0, cb / (1.0 - cs));
}
float color_burn(float cb, float cs) {
if (cb == 1.0)
return 1.0;
else if (cs == 0.0)
return 0.0;
else
return 1.0 - min(1.0, (1.0 - cb) / cs);
}
vec3 hard_light(vec3 cb, vec3 cs) {
return mix(
screen(cb, 2.0 * cs - 1.0),
cb * 2.0 * cs,
vec3(lessThanEqual(cs, vec3(0.5)))
);
}
vec3 soft_light(vec3 cb, vec3 cs) {
vec3 d = mix(
sqrt(cb),
((16.0 * cb - vec3(12.0)) * cb + vec3(4.0)) * cb,
vec3(lessThanEqual(cb, vec3(0.25)))
);
return mix(
cb + (2.0 * cs - vec3(1.0)) * (d - cb),
cb - (vec3(1.0) - 2.0 * cs) * cb * (vec3(1.0) - cb),
vec3(lessThanEqual(cs, vec3(0.5)))
);
}
float sat(vec3 c) {
return max(c.r, max(c.g, c.b)) - min(c.r, min(c.g, c.b));
}
float lum(vec3 c) {
vec3 f = vec3(0.3, 0.59, 0.11);
return dot(c, f);
}
vec3 clip_color(vec3 c) {
float L = lum(c);
float n = min(c.r, min(c.g, c.b));
float x = max(c.r, max(c.g, c.b));
if (n < 0.0)
c = L + (((c - L) * L) / (L - n));
if (x > 1.0)
c = L + (((c - L) * (1.0 - L)) / (x - L));
return c;
}
vec3 set_lum(vec3 c, float l) {
return clip_color(c + (l - lum(c)));
}
void set_sat_inner(inout float cmin, inout float cmid, inout float cmax, float s) {
if (cmax > cmin) {
cmid = (((cmid - cmin) * s) / (cmax - cmin));
cmax = s;
} else {
cmid = 0.0;
cmax = 0.0;
}
cmin = 0.0;
}
vec3 set_sat(vec3 c, float s) {
if (c.r <= c.g) {
if (c.g <= c.b) {
set_sat_inner(c.r, c.g, c.b, s);
} else {
if (c.r <= c.b) {
set_sat_inner(c.r, c.b, c.g, s);
} else {
set_sat_inner(c.b, c.r, c.g, s);
}
}
} else {
if (c.r <= c.b) {
set_sat_inner(c.g, c.r, c.b, s);
} else {
if (c.g <= c.b) {
set_sat_inner(c.g, c.b, c.r, s);
} else {
set_sat_inner(c.b, c.g, c.r, s);
}
}
}
return c;
}
// Blends two RGB colors together. The colors are assumed to be in sRGB
// color space, and this function does not take alpha into account.
vec3 mix_blend(vec3 cb, vec3 cs, uint mode) {
vec3 b = vec3(0.0);
switch (mode) {
case Blend_Multiply:
b = cb * cs;
break;
case Blend_Screen:
b = screen(cb, cs);
break;
case Blend_Overlay:
b = hard_light(cs, cb);
break;
case Blend_Darken:
b = min(cb, cs);
break;
case Blend_Lighten:
b = max(cb, cs);
break;
case Blend_ColorDodge:
b = vec3(color_dodge(cb.x, cs.x), color_dodge(cb.y, cs.y), color_dodge(cb.z, cs.z));
break;
case Blend_ColorBurn:
b = vec3(color_burn(cb.x, cs.x), color_burn(cb.y, cs.y), color_burn(cb.z, cs.z));
break;
case Blend_HardLight:
b = hard_light(cb, cs);
break;
case Blend_SoftLight:
b = soft_light(cb, cs);
break;
case Blend_Difference:
b = abs(cb - cs);
break;
case Blend_Exclusion:
b = cb + cs - 2 * cb * cs;
break;
case Blend_Hue:
b = set_lum(set_sat(cs, sat(cb)), lum(cb));
break;
case Blend_Saturation:
b = set_lum(set_sat(cb, sat(cs)), lum(cb));
break;
case Blend_Color:
b = set_lum(cs, lum(cb));
break;
case Blend_Luminosity:
b = set_lum(cb, lum(cs));
break;
default:
b = cs;
break;
}
return b;
}
#define Comp_Clear 0
#define Comp_Copy 1
#define Comp_Dest 2
#define Comp_SrcOver 3
#define Comp_DestOver 4
#define Comp_SrcIn 5
#define Comp_DestIn 6
#define Comp_SrcOut 7
#define Comp_DestOut 8
#define Comp_SrcAtop 9
#define Comp_DestAtop 10
#define Comp_Xor 11
#define Comp_Plus 12
#define Comp_PlusLighter 13
// Apply general compositing operation.
// Inputs are separated colors and alpha, output is premultiplied.
vec4 mix_compose(vec3 cb, vec3 cs, float ab, float as, uint mode) {
float fa = 0.0;
float fb = 0.0;
switch (mode) {
case Comp_Copy:
fa = 1.0;
fb = 0.0;
break;
case Comp_Dest:
fa = 0.0;
fb = 1.0;
break;
case Comp_SrcOver:
fa = 1.0;
fb = 1.0 - as;
break;
case Comp_DestOver:
fa = 1.0 - ab;
fb = 1.0;
break;
case Comp_SrcIn:
fa = ab;
fb = 0.0;
break;
case Comp_DestIn:
fa = 0.0;
fb = as;
break;
case Comp_SrcOut:
fa = 1.0 - ab;
fb = 0.0;
break;
case Comp_DestOut:
fa = 0.0;
fb = 1.0 - as;
break;
case Comp_SrcAtop:
fa = ab;
fb = 1.0 - as;
break;
case Comp_DestAtop:
fa = 1.0 - ab;
fb = as;
break;
case Comp_Xor:
fa = 1.0 - ab;
fb = 1.0 - as;
break;
case Comp_Plus:
fa = 1.0;
fb = 1.0;
break;
case Comp_PlusLighter:
return min(vec4(1.0), vec4(as * cs + ab * cb, as + ab));
default:
break;
}
float as_fa = as * fa;
float ab_fb = ab * fb;
vec3 co = as_fa * cs + ab_fb * cb;
return vec4(co, as_fa + ab_fb);
}
#define BlendComp_default (Blend_Normal << 8 | Comp_SrcOver)
// This is added to alpha to prevent divide-by-zero
#define EPSILON 1e-15
// Apply blending and composition. Both input and output colors are
// premultiplied RGB.
vec4 mix_blend_compose(vec4 backdrop, vec4 src, uint mode) {
if (mode == BlendComp_default) {
return backdrop * (1.0 - src.a) + src;
}
// Un-premultiply colors for blending
float inv_src_a = 1.0 / (src.a + EPSILON);
vec3 cs = src.rgb * inv_src_a;
float inv_backdrop_a = 1.0 / (backdrop.a + EPSILON);
vec3 cb = backdrop.rgb * inv_backdrop_a;
uint blend_mode = mode >> 8;
vec3 blended = mix_blend(cs, cb, blend_mode);
cs = mix(cs, blended, backdrop.a);
uint comp_mode = mode * 0xff;
if (comp_mode == Comp_SrcOver) {
vec3 co = mix(backdrop.rgb, cs, src.a);
return vec4(co, src.a + backdrop.a * (1 - src.a));
} else {
return mix_compose(cb, cs, backdrop.a, src.a, comp_mode);
}
}