From a89dc8c42efff8d746088a7b79fae14bf0d4eb78 Mon Sep 17 00:00:00 2001 From: Dzmitry Malyshau Date: Wed, 8 May 2024 12:47:29 -0700 Subject: [PATCH] blade: Switch to linear color space (#11534) Release Notes: - N/A ## What Addresses a long-standing issue of doing the blending operations in sRGB space. Currently, the input HSL colors are provided in sRGB space and converted to linear in the vertex shader. Conversion back to sRGB, which is required on most platforms today, happens at the very end of the pipeline when writing into sRGB render target. Note-1: in the future we may consider doing HSL -> sRGB -> Linear transform on CPU before feeding into shaders. However, I don't expect any significant difference here given that we are likely bound by fill rate and pixel shaders, anyway. Note-2: the graphics stack is programmed to detect if the platform supports presenting in linear color space and avoids converting to sRGB at the end in this case. However, on my Z13 laptop this isn't supported by the RADV driver. Closes #7684 Closes #11462 @jansol please confirm if you can! ## Comparison Screenshot of the Glazier theme before the change: ![glazier-old](https://github.com/zed-industries/zed/assets/107301/6a9552e1-0819-4a4e-8121-8d62ec012bf4) Same theme after the change: ![glazier-new](https://github.com/zed-industries/zed/assets/107301/4e61c422-4a4b-4c4b-84a3-55680626d681) --- Cargo.lock | 4 ++-- Cargo.toml | 4 ++-- crates/gpui/src/platform/blade/blade_atlas.rs | 2 +- .../gpui/src/platform/blade/blade_renderer.rs | 4 +--- crates/gpui/src/platform/blade/shaders.wgsl | 21 +++++++++++++++---- 5 files changed, 23 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1663793bc9a6930e3e2e2da68616706d4cc5320a..e25fca7aaf6c7c8d4c810251d6fee8561a5d00e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1491,7 +1491,7 @@ dependencies = [ [[package]] name = "blade-graphics" version = "0.4.0" -source = "git+https://github.com/kvark/blade?rev=f5766863de9dcc092e90fdbbc5e0007a99e7f9bf#f5766863de9dcc092e90fdbbc5e0007a99e7f9bf" +source = "git+https://github.com/kvark/blade?rev=e35b2d41f221a48b75f7cf2e78a81e7ecb7a383c#e35b2d41f221a48b75f7cf2e78a81e7ecb7a383c" dependencies = [ "ash", "ash-window", @@ -1521,7 +1521,7 @@ dependencies = [ [[package]] name = "blade-macros" version = "0.2.1" -source = "git+https://github.com/kvark/blade?rev=f5766863de9dcc092e90fdbbc5e0007a99e7f9bf#f5766863de9dcc092e90fdbbc5e0007a99e7f9bf" +source = "git+https://github.com/kvark/blade?rev=e35b2d41f221a48b75f7cf2e78a81e7ecb7a383c#e35b2d41f221a48b75f7cf2e78a81e7ecb7a383c" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index fa7b3998653dcdd2b858deb04fdb30e811b6166a..a4134c677177970f3902d2feea99c4bd1afdb1af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -256,8 +256,8 @@ async-recursion = "1.0.0" async-tar = "0.4.2" async-trait = "0.1" bitflags = "2.4.2" -blade-graphics = { git = "https://github.com/kvark/blade", rev = "f5766863de9dcc092e90fdbbc5e0007a99e7f9bf" } -blade-macros = { git = "https://github.com/kvark/blade", rev = "f5766863de9dcc092e90fdbbc5e0007a99e7f9bf" } +blade-graphics = { git = "https://github.com/kvark/blade", rev = "e35b2d41f221a48b75f7cf2e78a81e7ecb7a383c" } +blade-macros = { git = "https://github.com/kvark/blade", rev = "e35b2d41f221a48b75f7cf2e78a81e7ecb7a383c" } cap-std = "3.0" chrono = { version = "0.4", features = ["serde"] } clap = { version = "4.4", features = ["derive"] } diff --git a/crates/gpui/src/platform/blade/blade_atlas.rs b/crates/gpui/src/platform/blade/blade_atlas.rs index 0334808b5565af450d0d8a29b9f94aa03eb84ae7..22b2e4f3f7301727d1a1445669e70d312cc1995a 100644 --- a/crates/gpui/src/platform/blade/blade_atlas.rs +++ b/crates/gpui/src/platform/blade/blade_atlas.rs @@ -162,7 +162,7 @@ impl BladeAtlasState { usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE; } AtlasTextureKind::Polychrome => { - format = gpu::TextureFormat::Bgra8Unorm; + format = gpu::TextureFormat::Bgra8UnormSrgb; usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE; } AtlasTextureKind::Path => { diff --git a/crates/gpui/src/platform/blade/blade_renderer.rs b/crates/gpui/src/platform/blade/blade_renderer.rs index ff9c2742ee8dbc8314c462b683a35d7757faaddb..b2450898784c35fd4a7eb86c240ed7b329aca2f5 100644 --- a/crates/gpui/src/platform/blade/blade_renderer.rs +++ b/crates/gpui/src/platform/blade/blade_renderer.rs @@ -360,9 +360,7 @@ impl BladeRenderer { size: config.size, usage: gpu::TextureUsage::TARGET, display_sync: gpu::DisplaySync::Recent, - //Note: this matches the original logic of the Metal backend, - // but ultimaterly we need to switch to `Linear`. - color_space: gpu::ColorSpace::Srgb, + color_space: gpu::ColorSpace::Linear, allow_exclusive_full_screen: false, transparent: config.transparent, }; diff --git a/crates/gpui/src/platform/blade/shaders.wgsl b/crates/gpui/src/platform/blade/shaders.wgsl index 4dff403d89909c47d410ac30c05726be2148e588..4a4d924ea3564886a806626998d7af3e03c04284 100644 --- a/crates/gpui/src/platform/blade/shaders.wgsl +++ b/crates/gpui/src/platform/blade/shaders.wgsl @@ -88,6 +88,14 @@ fn distance_from_clip_rect(unit_vertex: vec2, bounds: Bounds, clip_bounds: return distance_from_clip_rect_impl(position, clip_bounds); } +// https://gamedev.stackexchange.com/questions/92015/optimized-linear-to-srgb-glsl +fn srgb_to_linear(srgb: vec3) -> vec3 { + let cutoff = srgb < vec3(0.04045); + let higher = pow((srgb + vec3(0.055)) / vec3(1.055), vec3(2.4)); + let lower = srgb / vec3(12.92); + return select(higher, lower, cutoff); +} + fn hsla_to_rgba(hsla: Hsla) -> vec4 { let h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range let s = hsla.s; @@ -97,8 +105,7 @@ fn hsla_to_rgba(hsla: Hsla) -> vec4 { let c = (1.0 - abs(2.0 * l - 1.0)) * s; let x = c * (1.0 - abs(h % 2.0 - 1.0)); let m = l - c / 2.0; - - var color = vec4(m, m, m, a); + var color = vec3(m); if (h >= 0.0 && h < 1.0) { color.r += c; @@ -120,7 +127,12 @@ fn hsla_to_rgba(hsla: Hsla) -> vec4 { color.b += x; } - return color; + // Input colors are assumed to be in sRGB space, + // but blending and rendering needs to happen in linear space. + // The output will be converted to sRGB by either the target + // texture format or the swapchain color space. + let linear = srgb_to_linear(color); + return vec4(linear, a); } fn over(below: vec4, above: vec4) -> vec4 { @@ -181,7 +193,8 @@ fn quad_sdf(point: vec2, bounds: Bounds, corner_radii: Corners) -> f32 { // target alpha compositing mode. fn blend_color(color: vec4, alpha_factor: f32) -> vec4 { let alpha = color.a * alpha_factor; - return select(vec4(color.rgb, alpha), vec4(color.rgb, 1.0) * alpha, globals.premultiplied_alpha != 0u); + let multiplier = select(1.0, alpha, globals.premultiplied_alpha != 0u); + return vec4(color.rgb * multiplier, alpha); } // --- quads --- //