Skip to content

Commit

Permalink
Improve text redering and do all color operation in gamma space (#2071)
Browse files Browse the repository at this point in the history
  • Loading branch information
emilk authored Sep 24, 2022
1 parent 29fa633 commit 4ac1e28
Show file tree
Hide file tree
Showing 36 changed files with 465 additions and 730 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG

## Unreleased
* ⚠️ BREAKING: Fix text being too small ([#2069](https://github.com/emilk/egui/pull/2069)).
* ⚠️ BREAKING: egui now expects integrations to do all color blending in gamma space ([#2071](https://github.com/emilk/egui/pull/2071)).

### Fixed 🐛
* Improved text rendering ([#2071](https://github.com/emilk/egui/pull/2071)).


## 0.19.0 - 2022-08-20
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions crates/eframe/src/native/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ mod glow_integration {
.with_hardware_acceleration(hardware_acceleration)
.with_depth_buffer(native_options.depth_buffer)
.with_multisampling(native_options.multisampling)
.with_srgb(true)
.with_srgb(false)
.with_stencil_buffer(native_options.stencil_buffer)
.with_vsync(native_options.vsync)
.build_windowed(window_builder, event_loop)
Expand All @@ -365,7 +365,7 @@ mod glow_integration {
let gl = Arc::new(gl);

let painter =
egui_glow::Painter::new(gl.clone(), None, "", self.native_options.shader_version)
egui_glow::Painter::new(gl.clone(), "", self.native_options.shader_version)
.unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error));

let system_theme = self.native_options.system_theme();
Expand Down
3 changes: 1 addition & 2 deletions crates/eframe/src/web/web_glow_painter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ impl WebPainter {
let (gl, shader_prefix) = init_glow_context_from_canvas(&canvas, options)?;
let gl = std::sync::Arc::new(gl);

let dimension = [canvas.width() as i32, canvas.height() as i32];
let painter = egui_glow::Painter::new(gl, Some(dimension), shader_prefix, None)
let painter = egui_glow::Painter::new(gl, shader_prefix, None)
.map_err(|error| format!("Error starting glow painter: {}", error))?;

Ok(Self {
Expand Down
8 changes: 4 additions & 4 deletions crates/egui-wgpu/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ All notable changes to the `egui-wgpu` integration will be noted in this file.


## Unreleased
* Rename `RenderPass` to `Renderer`
* Rename `RenderPass::execute` to `RenderPass::render`
* Rename `RenderPass::execute_with_renderpass` to `Renderer::render_onto_renderpass`
* Reexport `Renderer`
* Renamed `RenderPass` to `Renderer`.
* Renamed `RenderPass::execute` to `RenderPass::render`.
* Renamed `RenderPass::execute_with_renderpass` to `Renderer::render_onto_renderpass`.
* Reexported `Renderer`.


## 0.19.0 - 2022-08-20
Expand Down
63 changes: 37 additions & 26 deletions crates/egui-wgpu/src/egui.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
};

Expand All @@ -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> {
Expand All @@ -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;
}
Expand All @@ -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;
}
20 changes: 11 additions & 9 deletions crates/egui-wgpu/src/renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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 {
Expand Down
16 changes: 15 additions & 1 deletion crates/egui-wgpu/src/winit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
}
Loading

0 comments on commit 4ac1e28

Please sign in to comment.