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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user