1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-23 23:21:08 +03:00

window+gui: enable dual source blending

This replaces the slightly gnarly subpixel enabled blending in the
shader with Dual Source Blending, which is a technique where the
fragment shader can specify both the primary color (RGBA) as well
as an additional per-channel mask that can be used to alpha blend
with the destination.

This enables artifact-free alpha blending when used together
with a transparent window background.

refs: https://github.com/wez/wezterm/issues/932
This commit is contained in:
Wez Furlong 2021-08-09 22:27:44 -07:00
parent 1d94fe5fda
commit ee71d478c4
6 changed files with 47 additions and 80 deletions

18
Cargo.lock generated
View File

@ -922,7 +922,7 @@ dependencies = [
"cfg-if 1.0.0",
"crossbeam-utils",
"lazy_static",
"memoffset 0.6.4",
"memoffset",
"scopeguard",
]
@ -1759,15 +1759,14 @@ dependencies = [
[[package]]
name = "glium"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eeec7b733d14519e2541f4cc8a1230de9143d4ec439dd51b6c048d8ec991759"
version = "0.30.1"
source = "git+https://github.com/glium/glium.git?rev=aed95270f0714036003589d6e52de196e7ff75d1#aed95270f0714036003589d6e52de196e7ff75d1"
dependencies = [
"backtrace",
"fnv",
"gl_generator",
"lazy_static",
"memoffset 0.5.6",
"memoffset",
"smallvec 1.6.1",
"takeable-option",
]
@ -2371,15 +2370,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15"
[[package]]
name = "memoffset"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa"
dependencies = [
"autocfg",
]
[[package]]
name = "memoffset"
version = "0.6.4"

View File

@ -1,6 +1,6 @@
// This is the Glyph fragment shader.
// It is responsible for laying down the glyph graphics on top of the other layers.
#extension GL_EXT_blend_func_extended: enable
precision highp float;
in float o_has_color;
@ -9,29 +9,16 @@ in vec3 o_hsv;
in vec4 o_fg_color;
in vec4 o_bg_color;
out vec4 color;
// The color + alpha
layout(location=0, index=0) out vec4 color;
// Individual alpha channels for RGBA in color, used for subpixel
// antialiasing blending
layout(location=0, index=1) out vec4 colorMask;
uniform vec3 foreground_text_hsb;
uniform sampler2D atlas_nearest_sampler;
uniform sampler2D atlas_linear_sampler;
float multiply_one(float src, float dst, float inv_dst_alpha, float inv_src_alpha) {
return (src * dst) + (src * (inv_dst_alpha)) + (dst * (inv_src_alpha));
}
// Alpha-regulated multiply to colorize the glyph bitmap.
vec4 multiply(vec4 src, vec4 dst) {
float inv_src_alpha = 1.0 - src.a;
float inv_dst_alpha = 1.0 - dst.a;
return vec4(
multiply_one(src.r, dst.r, inv_dst_alpha, inv_src_alpha),
multiply_one(src.g, dst.g, inv_dst_alpha, inv_src_alpha),
multiply_one(src.b, dst.b, inv_dst_alpha, inv_src_alpha),
dst.a);
}
vec3 rgb2hsv(vec3 c)
{
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
@ -61,41 +48,6 @@ vec4 apply_hsv(vec4 c, vec3 transform)
return vec4(hsv2rgb(hsv).rgb, c.a);
}
// Given glyph, the greyscale rgba value computed by freetype,
// and color, the desired color, compute the resultant pixel
// value for rendering over the top of the given background
// color.
//
// The freetype glyph is greyscale (R=G=B=A) when font_antialias=Greyscale,
// where each channel holds the brightness of the pixel.
// It holds separate intensity values for the R, G and B channels when
// subpixel anti-aliasing is in use, with an approximated A value
// derived from the R, G, B values.
//
// In sub-pixel mode we don't want to look at glyph.a as we effective
// have per-channel alpha. In greyscale mode, glyph.a is the same
// as the other channels, so this routine ignores glyph.a when
// computing the blend, but does include that value for the returned
// alpha value.
//
// See also: https://www.puredevsoftware.com/blog/2019/01/22/sub-pixel-gamma-correct-font-rendering/
vec4 colorize(vec4 glyph, vec4 color, vec4 background) {
float r = glyph.r * color.r + (1.0 - glyph.r) * background.r;
float g = glyph.g * color.g + (1.0 - glyph.g) * background.g;
float b = glyph.b * color.b + (1.0 - glyph.b) * background.b;
return vec4(r, g, b, glyph.a);
}
// Like colorize, but for when the background color is unknown.
// This isn't "good" because it generally results in dark fringes.
// Ideally we wouldn't need to know the background and we could
// use dual source blending instead of colorize and colorize2.
// <https://github.com/wez/wezterm/issues/932>
vec4 colorize2(vec4 glyph, vec4 color) {
return vec4(glyph.rgb * color.rgb, glyph.a);
}
vec4 from_linear(vec4 v) {
return pow(v, vec4(2.2));
}
@ -120,18 +72,22 @@ void main() {
if (o_has_color == 3.0) {
// Solid color block
color = o_fg_color;
colorMask = vec4(1.0, 1.0, 1.0, 1.0);
} else if (o_has_color == 2.0) {
// The window background attachment
color = sample_texture(atlas_linear_sampler, o_tex);
// Apply window_background_image_opacity to the background image
color.a = o_fg_color.a;
colorMask = o_fg_color.aaaa;
} else {
color = sample_texture(atlas_nearest_sampler, o_tex);
if (o_has_color == 0.0) {
// if it's not a color emoji it will be grayscale
// and we need to tint with the fg_color
color = colorize(color, o_fg_color, o_bg_color);
colorMask = color;
color = o_fg_color;
color = apply_hsv(color, foreground_text_hsb);
} else {
colorMask = color.aaaa;
}
}

View File

@ -152,7 +152,7 @@ impl RenderState {
fragment_shader: fn(&str) -> (String, String),
) -> anyhow::Result<glium::Program> {
let mut errors = vec![];
for version in &["330", "300 es"] {
for version in &["330 core", "330", "320 es", "300 es"] {
let (vertex_shader, fragment_shader) = fragment_shader(version);
let source = glium::program::ProgramCreationInput::SourceCode {
vertex_shader: &vertex_shader,
@ -164,12 +164,11 @@ impl RenderState {
uses_point_size: false,
geometry_shader: None,
};
log::trace!("compiling a prog with version {}", version);
match glium::Program::new(context, source) {
Ok(prog) => {
return Ok(prog);
}
Err(err) => errors.push(err.to_string()),
Err(err) => errors.push(format!("shader version: {}: {:#}", version, err)),
};
}

View File

@ -467,12 +467,12 @@ impl super::TermWindow {
let alpha_blending = glium::DrawParameters {
blend: glium::Blend {
color: BlendingFunction::Addition {
source: LinearBlendingFactor::SourceAlpha,
destination: LinearBlendingFactor::OneMinusSourceAlpha,
source: LinearBlendingFactor::SourceOneColor,
destination: LinearBlendingFactor::OneMinusSourceOneColor,
},
alpha: BlendingFunction::Addition {
source: LinearBlendingFactor::One,
destination: LinearBlendingFactor::OneMinusSourceAlpha,
source: LinearBlendingFactor::SourceOneColor,
destination: LinearBlendingFactor::OneMinusSourceOneColor,
},
constant_value: (0.0, 0.0, 0.0, 0.0),
},

View File

@ -42,7 +42,7 @@ raw-window-handle = "0.3"
resize = "0.5"
serde = {version="1.0", features = ["rc", "derive"]}
tiny-skia = "0.5"
glium = { version = "0.28", default-features = false}
glium = { version = "0.30", default-features = false, git = "https://github.com/glium/glium.git", rev="aed95270f0714036003589d6e52de196e7ff75d1" }
wezterm-font = { path = "../wezterm-font" }
wezterm-input-types = { path = "../wezterm-input-types" }

View File

@ -54,6 +54,7 @@ impl std::fmt::Debug for EglWrapper {
pub struct GlConnection {
egl: EglWrapper,
display: ffi::types::EGLDisplay,
is_opengl: bool,
}
impl std::ops::Deref for GlConnection {
@ -490,9 +491,22 @@ impl GlState {
let (major, minor) = egl.initialize_and_get_version(egl_display)?;
log::trace!("initialized EGL version {}.{}", major, minor);
let is_opengl = unsafe {
if egl.egl.BindAPI(ffi::OPENGL_API) != 0 {
log::trace!("using OpenGL");
true
} else if egl.egl.BindAPI(ffi::OPENGL_ES_API) != 0 {
log::trace!("using GLES");
false
} else {
anyhow::bail!("Unable to bind to OpenGL or GL ES!?");
}
};
let connection = Rc::new(GlConnection {
display: egl_display,
egl,
is_opengl,
});
Self::create_with_existing_connection(&connection, window)
@ -539,9 +553,17 @@ impl GlState {
ffi::DEPTH_SIZE,
24,
ffi::CONFORMANT,
ffi::OPENGL_ES3_BIT,
if connection.is_opengl {
ffi::OPENGL_BIT
} else {
ffi::OPENGL_ES3_BIT
},
ffi::RENDERABLE_TYPE,
ffi::OPENGL_ES3_BIT,
if connection.is_opengl {
ffi::OPENGL_BIT
} else {
ffi::OPENGL_ES3_BIT
},
// Wayland EGL doesn't give us a working context if we request
// PBUFFER|PIXMAP. We don't appear to require these for X11,
// so we're just asking for a WINDOW capable context