diff --git a/src/config/Config.zig b/src/config/Config.zig index 839656169..1e1bca74a 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -259,7 +259,8 @@ pub const renamed = std.StaticStringMap([]const u8).initComptime(&.{ /// What color space to use when performing alpha blending. /// -/// This affects how text looks for different background/foreground color pairs. +/// This affects the appearance of text and of any images with transparency. +/// Additionally, custom shaders will receive colors in the configured space. /// /// Valid values: /// @@ -273,15 +274,10 @@ pub const renamed = std.StaticStringMap([]const u8).initComptime(&.{ /// This is also sometimes known as "gamma correction". /// (Currently only supported on macOS. Has no effect on Linux.) /// -/// * `linear-corrected` - Corrects the thinning/thickening effect of linear -/// by applying a correction curve to the text alpha depending on its -/// brightness. This compensates for the thinning and makes the weight of -/// most text appear very similar to when it's blended non-linearly. -/// -/// Note: This setting affects more than just text, images will also be blended -/// in the selected color space, and custom shaders will receive colors in that -/// color space as well. -@"text-blending": TextBlending = .native, +/// * `linear-corrected` - Same as `linear`, but with a correction step applied +/// for text that makes it look nearly or completely identical to `native`, +/// but without any of the darkening artifacts. +@"alpha-blending": AlphaBlending = .native, /// All of the configurations behavior adjust various metrics determined by the /// font. The values can be integers (1, -1, etc.) or a percentage (20%, -15%, @@ -1221,12 +1217,16 @@ keybind: Keybinds = .{}, /// This is currently only supported on macOS and Linux. @"window-theme": WindowTheme = .auto, -/// The colorspace to use for the terminal window. The default is `srgb` but -/// this can also be set to `display-p3` to use the Display P3 colorspace. +/// The color space to use when interpreting terminal colors. "Terminal colors" +/// refers to colors specified in your configuration and colors produced by +/// direct-color SGR sequences. /// -/// Changing this value at runtime will only affect new windows. +/// Valid values: /// -/// This setting is only supported on macOS. +/// * `srgb` - Interpret colors in the sRGB color space. This is the default. +/// * `display-p3` - Interpret colors in the Display P3 color space. +/// +/// This setting is currently only supported on macOS. @"window-colorspace": WindowColorspace = .srgb, /// The initial window size. This size is in terminal grid cells by default. @@ -5826,13 +5826,13 @@ pub const GraphemeWidthMethod = enum { unicode, }; -/// See text-blending -pub const TextBlending = enum { +/// See alpha-blending +pub const AlphaBlending = enum { native, linear, @"linear-corrected", - pub fn isLinear(self: TextBlending) bool { + pub fn isLinear(self: AlphaBlending) bool { return switch (self) { .native => false, .linear, .@"linear-corrected" => true, diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 52a5437c6..866f9682d 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -391,7 +391,7 @@ pub const DerivedConfig = struct { links: link.Set, vsync: bool, colorspace: configpkg.Config.WindowColorspace, - blending: configpkg.Config.TextBlending, + blending: configpkg.Config.AlphaBlending, pub fn init( alloc_gpa: Allocator, @@ -463,7 +463,7 @@ pub const DerivedConfig = struct { .links = links, .vsync = config.@"window-vsync", .colorspace = config.@"window-colorspace", - .blending = config.@"text-blending", + .blending = config.@"alpha-blending", .arena = arena, }; } @@ -667,7 +667,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal { .cursor_wide = false, .use_display_p3 = options.config.colorspace == .@"display-p3", .use_linear_blending = options.config.blending.isLinear(), - .use_experimental_linear_correction = options.config.blending == .@"linear-corrected", + .use_linear_correction = options.config.blending == .@"linear-corrected", }, // Fonts @@ -2099,7 +2099,7 @@ pub fn changeConfig(self: *Metal, config: *DerivedConfig) !void { // Set our new color space and blending self.uniforms.use_display_p3 = config.colorspace == .@"display-p3"; self.uniforms.use_linear_blending = config.blending.isLinear(); - self.uniforms.use_experimental_linear_correction = config.blending == .@"linear-corrected"; + self.uniforms.use_linear_correction = config.blending == .@"linear-corrected"; // Set our new colors self.default_background_color = config.background; @@ -2242,7 +2242,7 @@ pub fn setScreenSize( .cursor_wide = old.cursor_wide, .use_display_p3 = old.use_display_p3, .use_linear_blending = old.use_linear_blending, - .use_experimental_linear_correction = old.use_experimental_linear_correction, + .use_linear_correction = old.use_linear_correction, }; // Reset our cell contents if our grid size has changed. diff --git a/src/renderer/metal/shaders.zig b/src/renderer/metal/shaders.zig index 62d363173..b297de809 100644 --- a/src/renderer/metal/shaders.zig +++ b/src/renderer/metal/shaders.zig @@ -158,7 +158,7 @@ pub const Uniforms = extern struct { /// Enables a weight correction step that makes text rendered /// with linear alpha blending have a similar apparent weight /// (thickness) to gamma-incorrect blending. - use_experimental_linear_correction: bool align(1) = false, + use_linear_correction: bool align(1) = false, const PaddingExtend = packed struct(u8) { left: bool = false, diff --git a/src/renderer/shaders/cell.metal b/src/renderer/shaders/cell.metal index 17f811a19..3ca0f9149 100644 --- a/src/renderer/shaders/cell.metal +++ b/src/renderer/shaders/cell.metal @@ -22,7 +22,7 @@ struct Uniforms { bool cursor_wide; bool use_display_p3; bool use_linear_blending; - bool use_experimental_linear_correction; + bool use_linear_correction; }; //------------------------------------------------------------------- @@ -59,22 +59,28 @@ float3 srgb_to_display_p3(float3 srgb) { // Converts a color from sRGB gamma encoding to linear. float4 linearize(float4 srgb) { - bool3 cutoff = srgb.rgb <= 0.04045; - float3 lower = srgb.rgb / 12.92; - float3 higher = pow((srgb.rgb + 0.055) / 1.055, 2.4); - srgb.rgb = mix(higher, lower, float3(cutoff)); + bool3 cutoff = srgb.rgb <= 0.04045; + float3 lower = srgb.rgb / 12.92; + float3 higher = pow((srgb.rgb + 0.055) / 1.055, 2.4); + srgb.rgb = mix(higher, lower, float3(cutoff)); - return srgb; + return srgb; +} +float linearize(float v) { + return v <= 0.04045 ? v / 12.92 : pow((v + 0.055) / 1.055, 2.4); } // Converts a color from linear to sRGB gamma encoding. float4 unlinearize(float4 linear) { - bool3 cutoff = linear.rgb <= 0.0031308; - float3 lower = linear.rgb * 12.92; - float3 higher = pow(linear.rgb, 1.0 / 2.4) * 1.055 - 0.055; - linear.rgb = mix(higher, lower, float3(cutoff)); + bool3 cutoff = linear.rgb <= 0.0031308; + float3 lower = linear.rgb * 12.92; + float3 higher = pow(linear.rgb, 1.0 / 2.4) * 1.055 - 0.055; + linear.rgb = mix(higher, lower, float3(cutoff)); - return linear; + return linear; +} +float unlinearize(float v) { + return v <= 0.0031308 ? v * 12.92 : pow(v, 1.0 / 2.4) * 1.055 - 0.055; } // Compute the luminance of the provided color. @@ -353,8 +359,9 @@ struct CellTextVertexIn { struct CellTextVertexOut { float4 position [[position]]; - uint8_t mode; - float4 color; + uint8_t mode [[flat]]; + float4 color [[flat]]; + float4 bg_color [[flat]]; float2 tex_coord; }; @@ -445,6 +452,13 @@ vertex CellTextVertexOut cell_text_vertex( true ); + // Get the BG color + out.bg_color = load_color( + bg_colors[in.grid_pos.y * uniforms.grid_size.x + in.grid_pos.x], + uniforms.use_display_p3, + true + ); + // If we have a minimum contrast, we need to check if we need to // change the color of the text to ensure it has enough contrast // with the background. @@ -453,14 +467,8 @@ vertex CellTextVertexOut cell_text_vertex( // and Powerline glyphs to be unaffected (else parts of the line would // have different colors as some parts are displayed via background colors). if (uniforms.min_contrast > 1.0f && in.mode == MODE_TEXT) { - // Get the BG color - float4 bg_color = load_color( - bg_colors[in.grid_pos.y * uniforms.grid_size.x + in.grid_pos.x], - uniforms.use_display_p3, - true - ); // Ensure our minimum contrast - out.color = contrasted_color(uniforms.min_contrast, out.color, bg_color); + out.color = contrasted_color(uniforms.min_contrast, out.color, out.bg_color); } // If this cell is the cursor cell, then we need to change the color. @@ -473,7 +481,17 @@ vertex CellTextVertexOut cell_text_vertex( ) && in.grid_pos.y == uniforms.cursor_pos.y ) { - out.color = float4(uniforms.cursor_color) / 255.0f; + out.color = load_color( + uniforms.cursor_color, + uniforms.use_display_p3, + false + ); + } + + // Don't bother rendering if the bg and fg colors are identical, just return + // the same point which will be culled because it makes the quad zero sized. + if (all(out.color == out.bg_color)) { + out.position = float4(0.0); } return out; @@ -514,19 +532,28 @@ fragment float4 cell_text_fragment( // Fetch our alpha mask for this pixel. float a = textureGrayscale.sample(textureSampler, in.tex_coord).r; - // Experimental linear blending weight correction. - if (uniforms.use_experimental_linear_correction) { - float l = luminance(color.rgb); - - // TODO: This is a dynamic dilation term that biases - // the alpha adjustment for small font sizes; - // it should be computed by dividing the font - // size in `pt`s by `13.0` and using that if - // it's less than `1.0`, but for now it's - // hard coded at 1.0, which has no effect. - float d = 13.0 / 13.0; - - a += pow(a, d + d * l) - pow(a, d + 1.0 - d * l); + // Linear blending weight correction corrects the alpha value to + // produce blending results which match gamma-incorrect blending. + if (uniforms.use_linear_correction) { + // Short explanation of how this works: + // + // We get the luminances of the foreground and background colors, + // and then unlinearize them and perform blending on them. This + // gives us our desired luminance, which we derive our new alpha + // value from by mapping the range [bg_l, fg_l] to [0, 1], since + // our final blend will be a linear interpolation from bg to fg. + // + // This yields virtually identical results for grayscale blending, + // and very similar but non-identical results for color blending. + float4 bg = in.bg_color; + float fg_l = luminance(color.rgb); + float bg_l = luminance(bg.rgb); + // To avoid numbers going haywire, we don't apply correction + // when the bg and fg luminances are within 0.001 of each other. + if (abs(fg_l - bg_l) > 0.001) { + float blend_l = linearize(unlinearize(fg_l) * a + unlinearize(bg_l) * (1.0 - a)); + a = clamp((blend_l - bg_l) / (fg_l - bg_l), 0.0, 1.0); + } } // Multiply our whole color by the alpha mask.