mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
Implement perceptual gamma / contrast correction for Linux font rendering (#38862)
Part of https://github.com/zed-industries/zed/issues/7992 Port of https://github.com/zed-industries/zed/pull/37167 to Linux When using Blade rendering (Linux platforms and self-compiled builds with the Blade renderer enabled), Zed reads `ZED_FONTS_GAMMA` and `ZED_FONTS_GRAYSCALE_ENHANCED_CONTRAST` environment variables for the values to use for font rendering. `ZED_FONTS_GAMMA` corresponds to [getgamma](https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwriterenderingparams-getgamma) values. Allowed range [1.0, 2.2], other values are clipped. Default: 1.8 `ZED_FONTS_GRAYSCALE_ENHANCED_CONTRAST` corresponds to [getgrayscaleenhancedcontrast](https://learn.microsoft.com/en-us/windows/win32/api/dwrite_1/nf-dwrite_1-idwriterenderingparams1-getgrayscaleenhancedcontrast) values. Allowed range: [0.0, ..), other values are clipped. Default: 1.0 Screenshots (left is Nightly, right is the new code): * Non-lodpi display With the defaults: <img width="2560" height="1600" alt="image" src="https://github.com/user-attachments/assets/987168b4-3f5f-45a0-a740-9c0e49efbb9c" /> With `env ZED_FONTS_GRAYSCALE_ENHANCED_CONTRAST=7777`: <img width="2560" height="1600" alt="image" src="https://github.com/user-attachments/assets/893bc2c7-9db4-4874-8ef6-3425d079db63" /> Lodpi, default settings: <img width="3830" height="2160" alt="image" src="https://github.com/user-attachments/assets/ec009e00-69b3-4c01-a18c-8286e2015e74" /> Lodpi, font size 7: <img width="3830" height="2160" alt="image" src="https://github.com/user-attachments/assets/f33e3df6-971b-4e18-b425-53d3404b19be" /> Release Notes: - Implement perceptual gamma / contrast correction for Linux font rendering --------- Co-authored-by: localcc <work@localcc.cc>
This commit is contained in:
parent
f25ace6be0
commit
e72021a26b
3 changed files with 149 additions and 18 deletions
|
|
@ -83,6 +83,8 @@ struct ShaderUnderlinesData {
|
|||
#[derive(blade_macros::ShaderData)]
|
||||
struct ShaderMonoSpritesData {
|
||||
globals: GlobalParams,
|
||||
gamma_ratios: [f32; 4],
|
||||
grayscale_enhanced_contrast: f32,
|
||||
t_sprite: gpu::TextureView,
|
||||
s_sprite: gpu::Sampler,
|
||||
b_mono_sprites: gpu::BufferPiece,
|
||||
|
|
@ -334,11 +336,11 @@ pub struct BladeRenderer {
|
|||
atlas_sampler: gpu::Sampler,
|
||||
#[cfg(target_os = "macos")]
|
||||
core_video_texture_cache: CVMetalTextureCache,
|
||||
path_sample_count: u32,
|
||||
path_intermediate_texture: gpu::Texture,
|
||||
path_intermediate_texture_view: gpu::TextureView,
|
||||
path_intermediate_msaa_texture: Option<gpu::Texture>,
|
||||
path_intermediate_msaa_texture_view: Option<gpu::TextureView>,
|
||||
rendering_parameters: RenderingParameters,
|
||||
}
|
||||
|
||||
impl BladeRenderer {
|
||||
|
|
@ -364,17 +366,12 @@ impl BladeRenderer {
|
|||
name: "main",
|
||||
buffer_count: 2,
|
||||
});
|
||||
// workaround for https://github.com/zed-industries/zed/issues/26143
|
||||
let path_sample_count = std::env::var("ZED_PATH_SAMPLE_COUNT")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.or_else(|| {
|
||||
[4, 2, 1]
|
||||
.into_iter()
|
||||
.find(|&n| (context.gpu.capabilities().sample_count_mask & n) != 0)
|
||||
})
|
||||
.unwrap_or(1);
|
||||
let pipelines = BladePipelines::new(&context.gpu, surface.info(), path_sample_count);
|
||||
let rendering_parameters = RenderingParameters::from_env(context);
|
||||
let pipelines = BladePipelines::new(
|
||||
&context.gpu,
|
||||
surface.info(),
|
||||
rendering_parameters.path_sample_count,
|
||||
);
|
||||
let instance_belt = BufferBelt::new(BufferBeltDescriptor {
|
||||
memory: gpu::Memory::Shared,
|
||||
min_chunk_size: 0x1000,
|
||||
|
|
@ -401,7 +398,7 @@ impl BladeRenderer {
|
|||
surface.info().format,
|
||||
config.size.width,
|
||||
config.size.height,
|
||||
path_sample_count,
|
||||
rendering_parameters.path_sample_count,
|
||||
)
|
||||
.unzip();
|
||||
|
||||
|
|
@ -425,11 +422,11 @@ impl BladeRenderer {
|
|||
atlas_sampler,
|
||||
#[cfg(target_os = "macos")]
|
||||
core_video_texture_cache,
|
||||
path_sample_count,
|
||||
path_intermediate_texture,
|
||||
path_intermediate_texture_view,
|
||||
path_intermediate_msaa_texture,
|
||||
path_intermediate_msaa_texture_view,
|
||||
rendering_parameters,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -506,7 +503,7 @@ impl BladeRenderer {
|
|||
self.surface.info().format,
|
||||
gpu_size.width,
|
||||
gpu_size.height,
|
||||
self.path_sample_count,
|
||||
self.rendering_parameters.path_sample_count,
|
||||
)
|
||||
.unzip();
|
||||
self.path_intermediate_msaa_texture = path_intermediate_msaa_texture;
|
||||
|
|
@ -521,8 +518,11 @@ impl BladeRenderer {
|
|||
self.gpu
|
||||
.reconfigure_surface(&mut self.surface, self.surface_config);
|
||||
self.pipelines.destroy(&self.gpu);
|
||||
self.pipelines =
|
||||
BladePipelines::new(&self.gpu, self.surface.info(), self.path_sample_count);
|
||||
self.pipelines = BladePipelines::new(
|
||||
&self.gpu,
|
||||
self.surface.info(),
|
||||
self.rendering_parameters.path_sample_count,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -783,6 +783,10 @@ impl BladeRenderer {
|
|||
0,
|
||||
&ShaderMonoSpritesData {
|
||||
globals,
|
||||
gamma_ratios: self.rendering_parameters.gamma_ratios,
|
||||
grayscale_enhanced_contrast: self
|
||||
.rendering_parameters
|
||||
.grayscale_enhanced_contrast,
|
||||
t_sprite: tex_info.raw_view,
|
||||
s_sprite: self.atlas_sampler,
|
||||
b_mono_sprites: instance_buf,
|
||||
|
|
@ -984,3 +988,85 @@ fn create_msaa_texture_if_needed(
|
|||
|
||||
Some((texture_msaa, texture_view_msaa))
|
||||
}
|
||||
|
||||
/// A set of parameters that can be set using a corresponding environment variable.
|
||||
struct RenderingParameters {
|
||||
// Env var: ZED_PATH_SAMPLE_COUNT
|
||||
// workaround for https://github.com/zed-industries/zed/issues/26143
|
||||
path_sample_count: u32,
|
||||
|
||||
// Env var: ZED_FONTS_GAMMA
|
||||
// Allowed range [1.0, 2.2], other values are clipped
|
||||
// Default: 1.8
|
||||
gamma_ratios: [f32; 4],
|
||||
// Env var: ZED_FONTS_GRAYSCALE_ENHANCED_CONTRAST
|
||||
// Allowed range: [0.0, ..), other values are clipped
|
||||
// Default: 1.0
|
||||
grayscale_enhanced_contrast: f32,
|
||||
}
|
||||
|
||||
impl RenderingParameters {
|
||||
fn from_env(context: &BladeContext) -> Self {
|
||||
use std::env;
|
||||
|
||||
let path_sample_count = env::var("ZED_PATH_SAMPLE_COUNT")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.or_else(|| {
|
||||
[4, 2, 1]
|
||||
.into_iter()
|
||||
.find(|&n| (context.gpu.capabilities().sample_count_mask & n) != 0)
|
||||
})
|
||||
.unwrap_or(1);
|
||||
let gamma = env::var("ZED_FONTS_GAMMA")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(1.8_f32)
|
||||
.clamp(1.0, 2.2);
|
||||
let gamma_ratios = Self::get_gamma_ratios(gamma);
|
||||
let grayscale_enhanced_contrast = env::var("ZED_FONTS_GRAYSCALE_ENHANCED_CONTRAST")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(1.0_f32)
|
||||
.max(0.0);
|
||||
|
||||
Self {
|
||||
path_sample_count,
|
||||
gamma_ratios,
|
||||
grayscale_enhanced_contrast,
|
||||
}
|
||||
}
|
||||
|
||||
// Gamma ratios for brightening/darkening edges for better contrast
|
||||
// https://github.com/microsoft/terminal/blob/1283c0f5b99a2961673249fa77c6b986efb5086c/src/renderer/atlas/dwrite.cpp#L50
|
||||
fn get_gamma_ratios(gamma: f32) -> [f32; 4] {
|
||||
const GAMMA_INCORRECT_TARGET_RATIOS: [[f32; 4]; 13] = [
|
||||
[0.0000 / 4.0, 0.0000 / 4.0, 0.0000 / 4.0, 0.0000 / 4.0], // gamma = 1.0
|
||||
[0.0166 / 4.0, -0.0807 / 4.0, 0.2227 / 4.0, -0.0751 / 4.0], // gamma = 1.1
|
||||
[0.0350 / 4.0, -0.1760 / 4.0, 0.4325 / 4.0, -0.1370 / 4.0], // gamma = 1.2
|
||||
[0.0543 / 4.0, -0.2821 / 4.0, 0.6302 / 4.0, -0.1876 / 4.0], // gamma = 1.3
|
||||
[0.0739 / 4.0, -0.3963 / 4.0, 0.8167 / 4.0, -0.2287 / 4.0], // gamma = 1.4
|
||||
[0.0933 / 4.0, -0.5161 / 4.0, 0.9926 / 4.0, -0.2616 / 4.0], // gamma = 1.5
|
||||
[0.1121 / 4.0, -0.6395 / 4.0, 1.1588 / 4.0, -0.2877 / 4.0], // gamma = 1.6
|
||||
[0.1300 / 4.0, -0.7649 / 4.0, 1.3159 / 4.0, -0.3080 / 4.0], // gamma = 1.7
|
||||
[0.1469 / 4.0, -0.8911 / 4.0, 1.4644 / 4.0, -0.3234 / 4.0], // gamma = 1.8
|
||||
[0.1627 / 4.0, -1.0170 / 4.0, 1.6051 / 4.0, -0.3347 / 4.0], // gamma = 1.9
|
||||
[0.1773 / 4.0, -1.1420 / 4.0, 1.7385 / 4.0, -0.3426 / 4.0], // gamma = 2.0
|
||||
[0.1908 / 4.0, -1.2652 / 4.0, 1.8650 / 4.0, -0.3476 / 4.0], // gamma = 2.1
|
||||
[0.2031 / 4.0, -1.3864 / 4.0, 1.9851 / 4.0, -0.3501 / 4.0], // gamma = 2.2
|
||||
];
|
||||
|
||||
const NORM13: f32 = ((0x10000 as f64) / (255.0 * 255.0) * 4.0) as f32;
|
||||
const NORM24: f32 = ((0x100 as f64) / (255.0) * 4.0) as f32;
|
||||
|
||||
let index = ((gamma * 10.0).round() as usize).clamp(10, 22) - 10;
|
||||
let ratios = GAMMA_INCORRECT_TARGET_RATIOS[index];
|
||||
|
||||
[
|
||||
ratios[0] * NORM13,
|
||||
ratios[1] * NORM24,
|
||||
ratios[2] * NORM13,
|
||||
ratios[3] * NORM24,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,35 @@ fn heat_map_color(value: f32, minValue: f32, maxValue: f32, position: vec2<f32>)
|
|||
|
||||
*/
|
||||
|
||||
fn color_brightness(color: vec3<f32>) -> f32 {
|
||||
// REC. 601 luminance coefficients for perceived brightness
|
||||
return dot(color, vec3<f32>(0.30, 0.59, 0.11));
|
||||
}
|
||||
|
||||
fn light_on_dark_contrast(enhancedContrast: f32, color: vec3<f32>) -> f32 {
|
||||
let brightness = color_brightness(color);
|
||||
let multiplier = saturate(4.0 * (0.75 - brightness));
|
||||
return enhancedContrast * multiplier;
|
||||
}
|
||||
|
||||
fn enhance_contrast(alpha: f32, k: f32) -> f32 {
|
||||
return alpha * (k + 1.0) / (alpha * k + 1.0);
|
||||
}
|
||||
|
||||
fn apply_alpha_correction(a: f32, b: f32, g: vec4<f32>) -> f32 {
|
||||
let brightness_adjustment = g.x * b + g.y;
|
||||
let correction = brightness_adjustment * a + (g.z * b + g.w);
|
||||
return a + a * (1.0 - a) * correction;
|
||||
}
|
||||
|
||||
fn apply_contrast_and_gamma_correction(sample: f32, color: vec3<f32>, enhanced_contrast_factor: f32, gamma_ratios: vec4<f32>) -> f32 {
|
||||
let enhanced_contrast = light_on_dark_contrast(enhanced_contrast_factor, color);
|
||||
let brightness = color_brightness(color);
|
||||
|
||||
let contrasted = enhance_contrast(sample, enhanced_contrast);
|
||||
return apply_alpha_correction(contrasted, brightness, gamma_ratios);
|
||||
}
|
||||
|
||||
struct GlobalParams {
|
||||
viewport_size: vec2<f32>,
|
||||
premultiplied_alpha: u32,
|
||||
|
|
@ -35,6 +64,8 @@ struct GlobalParams {
|
|||
}
|
||||
|
||||
var<uniform> globals: GlobalParams;
|
||||
var<uniform> gamma_ratios: vec4<f32>;
|
||||
var<uniform> grayscale_enhanced_contrast: f32;
|
||||
var t_sprite: texture_2d<f32>;
|
||||
var s_sprite: sampler;
|
||||
|
||||
|
|
@ -1124,11 +1155,13 @@ fn vs_mono_sprite(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index
|
|||
@fragment
|
||||
fn fs_mono_sprite(input: MonoSpriteVarying) -> @location(0) vec4<f32> {
|
||||
let sample = textureSample(t_sprite, s_sprite, input.tile_position).r;
|
||||
let alpha_corrected = apply_contrast_and_gamma_correction(sample, input.color.rgb, grayscale_enhanced_contrast, gamma_ratios);
|
||||
|
||||
// Alpha clip after using the derivatives.
|
||||
if (any(input.clip_distances < vec4<f32>(0.0))) {
|
||||
return vec4<f32>(0.0);
|
||||
}
|
||||
return blend_color(input.color, sample);
|
||||
return blend_color(input.color, alpha_corrected);
|
||||
}
|
||||
|
||||
// --- polychrome sprites --- //
|
||||
|
|
|
|||
|
|
@ -368,3 +368,15 @@ xrandr --dpi 192
|
|||
```
|
||||
|
||||
Replace `192` with your desired DPI value. This affects the system globally and will be used by Zed's automatic RandR detection when `Xft.dpi` is not set.
|
||||
|
||||
### Font rendering parameters
|
||||
|
||||
When using Blade rendering (Linux platforms and self-compiled builds with the Blade renderer enabled), Zed reads `ZED_FONTS_GAMMA` and `ZED_FONTS_GRAYSCALE_ENHANCED_CONTRAST` environment variables for the values to use for font rendering.
|
||||
|
||||
`ZED_FONTS_GAMMA` corresponds to [getgamma](https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwriterenderingparams-getgamma) values.
|
||||
Allowed range [1.0, 2.2], other values are clipped.
|
||||
Default: 1.8
|
||||
|
||||
`ZED_FONTS_GRAYSCALE_ENHANCED_CONTRAST` corresponds to [getgrayscaleenhancedcontrast](https://learn.microsoft.com/en-us/windows/win32/api/dwrite_1/nf-dwrite_1-idwriterenderingparams1-getgrayscaleenhancedcontrast) values.
|
||||
Allowed range: [0.0, ..), other values are clipped.
|
||||
Default: 1.0
|
||||
|
|
|
|||
Loading…
Reference in a new issue