Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 15 commits into from
Sep 24, 2022
Merged
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