1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-30 21:30:03 -04:00

Improve text redering and do all color operation in gamma space (#2071)

This commit is contained in:
Emil Ernerfeldt
2022-09-24 17:53:11 +02:00
committed by GitHub
parent 29fa63317e
commit 4ac1e28eae
36 changed files with 468 additions and 733 deletions

View File

@@ -2,7 +2,7 @@
struct VertexOutput {
@location(0) tex_coord: vec2<f32>,
@location(1) color: vec4<f32>,
@location(1) color: vec4<f32>, // gamma 0-1
@builtin(position) position: vec4<f32>,
};
@@ -14,22 +14,35 @@ struct Locals {
};
@group(0) @binding(0) var<uniform> r_locals: Locals;
// 0-1 from 0-255
fn linear_from_srgb(srgb: vec3<f32>) -> vec3<f32> {
let cutoff = srgb < vec3<f32>(10.31475);
let lower = srgb / vec3<f32>(3294.6);
let higher = pow((srgb + vec3<f32>(14.025)) / vec3<f32>(269.025), vec3<f32>(2.4));
// 0-1 linear from 0-1 sRGB gamma
fn linear_from_gamma_rgb(srgb: vec3<f32>) -> vec3<f32> {
let cutoff = srgb < vec3<f32>(0.04045);
let lower = srgb / vec3<f32>(12.92);
let higher = pow((srgb + vec3<f32>(0.055)) / vec3<f32>(1.055), vec3<f32>(2.4));
return select(higher, lower, cutoff);
}
// [u8; 4] SRGB as u32 -> [r, g, b, a]
// 0-1 sRGB gamma from 0-1 linear
fn gamma_from_linear_rgb(rgb: vec3<f32>) -> vec3<f32> {
let cutoff = rgb < vec3<f32>(0.0031308);
let lower = rgb * vec3<f32>(12.92);
let higher = vec3<f32>(1.055) * pow(rgb, vec3<f32>(1.0 / 2.4)) - vec3<f32>(0.055);
return select(higher, lower, cutoff);
}
// 0-1 sRGBA gamma from 0-1 linear
fn gamma_from_linear_rgba(linear_rgba: vec4<f32>) -> vec4<f32> {
return vec4<f32>(gamma_from_linear_rgb(linear_rgba.rgb), linear_rgba.a);
}
// [u8; 4] SRGB as u32 -> [r, g, b, a] in 0.-1
fn unpack_color(color: u32) -> vec4<f32> {
return vec4<f32>(
f32(color & 255u),
f32((color >> 8u) & 255u),
f32((color >> 16u) & 255u),
f32((color >> 24u) & 255u),
);
) / 255.0;
}
fn position_from_screen(screen_pos: vec2<f32>) -> vec4<f32> {
@@ -49,22 +62,7 @@ fn vs_main(
) -> VertexOutput {
var out: VertexOutput;
out.tex_coord = a_tex_coord;
let color = unpack_color(a_color);
out.color = vec4<f32>(linear_from_srgb(color.rgb), color.a / 255.0);
out.position = position_from_screen(a_pos);
return out;
}
@vertex
fn vs_conv_main(
@location(0) a_pos: vec2<f32>,
@location(1) a_tex_coord: vec2<f32>,
@location(2) a_color: u32,
) -> VertexOutput {
var out: VertexOutput;
out.tex_coord = a_tex_coord;
let color = unpack_color(a_color);
out.color = vec4<f32>(color.rgba / 255.0);
out.color = unpack_color(a_color);
out.position = position_from_screen(a_pos);
return out;
}
@@ -75,6 +73,19 @@ fn vs_conv_main(
@group(1) @binding(1) var r_tex_sampler: sampler;
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
return in.color * textureSample(r_tex_color, r_tex_sampler, in.tex_coord);
fn fs_main_linear_framebuffer(in: VertexOutput) -> @location(0) vec4<f32> {
// We always have an sRGB aware texture at the moment.
let tex_linear = textureSample(r_tex_color, r_tex_sampler, in.tex_coord);
let tex_gamma = gamma_from_linear_rgba(tex_linear);
let out_color_gamma = in.color * tex_gamma;
return vec4<f32>(linear_from_gamma_rgb(out_color_gamma.rgb), out_color_gamma.a);
}
@fragment
fn fs_main_gamma_framebuffer(in: VertexOutput) -> @location(0) vec4<f32> {
// We always have an sRGB aware texture at the moment.
let tex_linear = textureSample(r_tex_color, r_tex_sampler, in.tex_coord);
let tex_gamma = gamma_from_linear_rgba(tex_linear);
let out_color_gamma = in.color * tex_gamma;
return out_color_gamma;
}

View File

@@ -140,7 +140,8 @@ pub struct Renderer {
impl Renderer {
/// Creates a renderer for a egui UI.
///
/// If the format passed is not a *Srgb format, the shader will automatically convert to `sRGB` colors in the shader.
/// `output_format` should preferably be [`wgpu::TextureFormat::Rgba8Unorm`] or
/// [`wgpu::TextureFormat::Bgra8Unorm`], i.e. in gamma-space.
pub fn new(
device: &wgpu::Device,
output_format: wgpu::TextureFormat,
@@ -235,11 +236,7 @@ impl Renderer {
label: Some("egui_pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
entry_point: if output_format.describe().srgb {
"vs_main"
} else {
"vs_conv_main"
},
entry_point: "vs_main",
module: &module,
buffers: &[wgpu::VertexBufferLayout {
array_stride: 5 * 4,
@@ -268,7 +265,12 @@ impl Renderer {
fragment: Some(wgpu::FragmentState {
module: &module,
entry_point: "fs_main",
entry_point: if output_format.describe().srgb {
tracing::warn!("Detected a linear (sRGBA aware) framebuffer {:?}. egui prefers Rgba8Unorm or Bgra8Unorm", output_format);
"fs_main_linear_framebuffer"
} else {
"fs_main_gamma_framebuffer" // this is what we prefer
},
targets: &[Some(wgpu::ColorTargetState {
format: output_format,
blend: Some(wgpu::BlendState {
@@ -518,7 +520,7 @@ impl Renderer {
image.pixels.len(),
"Mismatch between texture size and texel count"
);
Cow::Owned(image.srgba_pixels(1.0).collect::<Vec<_>>())
Cow::Owned(image.srgba_pixels(None).collect::<Vec<_>>())
}
};
let data_bytes: &[u8] = bytemuck::cast_slice(data_color32.as_slice());
@@ -564,7 +566,7 @@ impl Renderer {
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
format: wgpu::TextureFormat::Rgba8UnormSrgb, // TODO(emilk): handle WebGL1 where this is not always supported!
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
});
let filter = match image_delta.filter {

View File

@@ -117,7 +117,9 @@ impl<'a> Painter<'a> {
if self.render_state.is_none() {
let adapter = self.adapter.as_ref().unwrap();
let swapchain_format = surface.get_supported_formats(adapter)[0];
let swapchain_format =
select_framebuffer_format(&surface.get_supported_formats(adapter));
let rs = pollster::block_on(self.init_render_state(adapter, swapchain_format));
self.render_state = Some(rs);
@@ -322,3 +324,15 @@ impl<'a> Painter<'a> {
// TODO(emilk): something here?
}
}
fn select_framebuffer_format(formats: &[wgpu::TextureFormat]) -> wgpu::TextureFormat {
for &format in formats {
if matches!(
format,
wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Bgra8Unorm
) {
return format;
}
}
formats[0] // take the first
}