Improve text redering and do all color operation in gamma space (#2071)
This commit is contained in:
parent
29fa63317e
commit
4ac1e28eae
36 changed files with 468 additions and 733 deletions
|
@ -6,6 +6,10 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
* ⚠️ BREAKING: Fix text being too small ([#2069](https://github.com/emilk/egui/pull/2069)).
|
* ⚠️ 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
|
## 0.19.0 - 2022-08-20
|
||||||
|
|
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1304,6 +1304,7 @@ dependencies = [
|
||||||
"document-features",
|
"document-features",
|
||||||
"egui",
|
"egui",
|
||||||
"egui-winit",
|
"egui-winit",
|
||||||
|
"egui_demo_lib",
|
||||||
"glium",
|
"glium",
|
||||||
"image",
|
"image",
|
||||||
]
|
]
|
||||||
|
|
|
@ -338,7 +338,7 @@ mod glow_integration {
|
||||||
.with_hardware_acceleration(hardware_acceleration)
|
.with_hardware_acceleration(hardware_acceleration)
|
||||||
.with_depth_buffer(native_options.depth_buffer)
|
.with_depth_buffer(native_options.depth_buffer)
|
||||||
.with_multisampling(native_options.multisampling)
|
.with_multisampling(native_options.multisampling)
|
||||||
.with_srgb(true)
|
.with_srgb(false)
|
||||||
.with_stencil_buffer(native_options.stencil_buffer)
|
.with_stencil_buffer(native_options.stencil_buffer)
|
||||||
.with_vsync(native_options.vsync)
|
.with_vsync(native_options.vsync)
|
||||||
.build_windowed(window_builder, event_loop)
|
.build_windowed(window_builder, event_loop)
|
||||||
|
@ -365,7 +365,7 @@ mod glow_integration {
|
||||||
let gl = Arc::new(gl);
|
let gl = Arc::new(gl);
|
||||||
|
|
||||||
let painter =
|
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));
|
.unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error));
|
||||||
|
|
||||||
let system_theme = self.native_options.system_theme();
|
let system_theme = self.native_options.system_theme();
|
||||||
|
|
|
@ -22,8 +22,7 @@ impl WebPainter {
|
||||||
let (gl, shader_prefix) = init_glow_context_from_canvas(&canvas, options)?;
|
let (gl, shader_prefix) = init_glow_context_from_canvas(&canvas, options)?;
|
||||||
let gl = std::sync::Arc::new(gl);
|
let gl = std::sync::Arc::new(gl);
|
||||||
|
|
||||||
let dimension = [canvas.width() as i32, canvas.height() as i32];
|
let painter = egui_glow::Painter::new(gl, shader_prefix, None)
|
||||||
let painter = egui_glow::Painter::new(gl, Some(dimension), shader_prefix, None)
|
|
||||||
.map_err(|error| format!("Error starting glow painter: {}", error))?;
|
.map_err(|error| format!("Error starting glow painter: {}", error))?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
|
|
@ -3,10 +3,10 @@ All notable changes to the `egui-wgpu` integration will be noted in this file.
|
||||||
|
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
* Rename `RenderPass` to `Renderer`
|
* Renamed `RenderPass` to `Renderer`.
|
||||||
* Rename `RenderPass::execute` to `RenderPass::render`
|
* Renamed `RenderPass::execute` to `RenderPass::render`.
|
||||||
* Rename `RenderPass::execute_with_renderpass` to `Renderer::render_onto_renderpass`
|
* Renamed `RenderPass::execute_with_renderpass` to `Renderer::render_onto_renderpass`.
|
||||||
* Reexport `Renderer`
|
* Reexported `Renderer`.
|
||||||
|
|
||||||
|
|
||||||
## 0.19.0 - 2022-08-20
|
## 0.19.0 - 2022-08-20
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
struct VertexOutput {
|
struct VertexOutput {
|
||||||
@location(0) tex_coord: vec2<f32>,
|
@location(0) tex_coord: vec2<f32>,
|
||||||
@location(1) color: vec4<f32>,
|
@location(1) color: vec4<f32>, // gamma 0-1
|
||||||
@builtin(position) position: vec4<f32>,
|
@builtin(position) position: vec4<f32>,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -14,22 +14,35 @@ struct Locals {
|
||||||
};
|
};
|
||||||
@group(0) @binding(0) var<uniform> r_locals: Locals;
|
@group(0) @binding(0) var<uniform> r_locals: Locals;
|
||||||
|
|
||||||
// 0-1 from 0-255
|
// 0-1 linear from 0-1 sRGB gamma
|
||||||
fn linear_from_srgb(srgb: vec3<f32>) -> vec3<f32> {
|
fn linear_from_gamma_rgb(srgb: vec3<f32>) -> vec3<f32> {
|
||||||
let cutoff = srgb < vec3<f32>(10.31475);
|
let cutoff = srgb < vec3<f32>(0.04045);
|
||||||
let lower = srgb / vec3<f32>(3294.6);
|
let lower = srgb / vec3<f32>(12.92);
|
||||||
let higher = pow((srgb + vec3<f32>(14.025)) / vec3<f32>(269.025), vec3<f32>(2.4));
|
let higher = pow((srgb + vec3<f32>(0.055)) / vec3<f32>(1.055), vec3<f32>(2.4));
|
||||||
return select(higher, lower, cutoff);
|
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> {
|
fn unpack_color(color: u32) -> vec4<f32> {
|
||||||
return vec4<f32>(
|
return vec4<f32>(
|
||||||
f32(color & 255u),
|
f32(color & 255u),
|
||||||
f32((color >> 8u) & 255u),
|
f32((color >> 8u) & 255u),
|
||||||
f32((color >> 16u) & 255u),
|
f32((color >> 16u) & 255u),
|
||||||
f32((color >> 24u) & 255u),
|
f32((color >> 24u) & 255u),
|
||||||
);
|
) / 255.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn position_from_screen(screen_pos: vec2<f32>) -> vec4<f32> {
|
fn position_from_screen(screen_pos: vec2<f32>) -> vec4<f32> {
|
||||||
|
@ -49,22 +62,7 @@ fn vs_main(
|
||||||
) -> VertexOutput {
|
) -> VertexOutput {
|
||||||
var out: VertexOutput;
|
var out: VertexOutput;
|
||||||
out.tex_coord = a_tex_coord;
|
out.tex_coord = a_tex_coord;
|
||||||
let color = unpack_color(a_color);
|
out.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.position = position_from_screen(a_pos);
|
out.position = position_from_screen(a_pos);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
@ -75,6 +73,19 @@ fn vs_conv_main(
|
||||||
@group(1) @binding(1) var r_tex_sampler: sampler;
|
@group(1) @binding(1) var r_tex_sampler: sampler;
|
||||||
|
|
||||||
@fragment
|
@fragment
|
||||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
fn fs_main_linear_framebuffer(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
return in.color * textureSample(r_tex_color, r_tex_sampler, in.tex_coord);
|
// 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 {
|
impl Renderer {
|
||||||
/// Creates a renderer for a egui UI.
|
/// 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(
|
pub fn new(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
output_format: wgpu::TextureFormat,
|
output_format: wgpu::TextureFormat,
|
||||||
|
@ -235,11 +236,7 @@ impl Renderer {
|
||||||
label: Some("egui_pipeline"),
|
label: Some("egui_pipeline"),
|
||||||
layout: Some(&pipeline_layout),
|
layout: Some(&pipeline_layout),
|
||||||
vertex: wgpu::VertexState {
|
vertex: wgpu::VertexState {
|
||||||
entry_point: if output_format.describe().srgb {
|
entry_point: "vs_main",
|
||||||
"vs_main"
|
|
||||||
} else {
|
|
||||||
"vs_conv_main"
|
|
||||||
},
|
|
||||||
module: &module,
|
module: &module,
|
||||||
buffers: &[wgpu::VertexBufferLayout {
|
buffers: &[wgpu::VertexBufferLayout {
|
||||||
array_stride: 5 * 4,
|
array_stride: 5 * 4,
|
||||||
|
@ -268,7 +265,12 @@ impl Renderer {
|
||||||
|
|
||||||
fragment: Some(wgpu::FragmentState {
|
fragment: Some(wgpu::FragmentState {
|
||||||
module: &module,
|
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 {
|
targets: &[Some(wgpu::ColorTargetState {
|
||||||
format: output_format,
|
format: output_format,
|
||||||
blend: Some(wgpu::BlendState {
|
blend: Some(wgpu::BlendState {
|
||||||
|
@ -518,7 +520,7 @@ impl Renderer {
|
||||||
image.pixels.len(),
|
image.pixels.len(),
|
||||||
"Mismatch between texture size and texel count"
|
"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());
|
let data_bytes: &[u8] = bytemuck::cast_slice(data_color32.as_slice());
|
||||||
|
@ -564,7 +566,7 @@ impl Renderer {
|
||||||
mip_level_count: 1,
|
mip_level_count: 1,
|
||||||
sample_count: 1,
|
sample_count: 1,
|
||||||
dimension: wgpu::TextureDimension::D2,
|
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,
|
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||||
});
|
});
|
||||||
let filter = match image_delta.filter {
|
let filter = match image_delta.filter {
|
||||||
|
|
|
@ -117,7 +117,9 @@ impl<'a> Painter<'a> {
|
||||||
|
|
||||||
if self.render_state.is_none() {
|
if self.render_state.is_none() {
|
||||||
let adapter = self.adapter.as_ref().unwrap();
|
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));
|
let rs = pollster::block_on(self.init_render_state(adapter, swapchain_format));
|
||||||
self.render_state = Some(rs);
|
self.render_state = Some(rs);
|
||||||
|
@ -322,3 +324,15 @@ impl<'a> Painter<'a> {
|
||||||
// TODO(emilk): something here?
|
// 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
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use egui::{color::*, widgets::color_picker::show_color, TextureFilter, *};
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
const GRADIENT_SIZE: Vec2 = vec2(256.0, 24.0);
|
use egui::{color::*, widgets::color_picker::show_color, TextureFilter, *};
|
||||||
|
|
||||||
|
const GRADIENT_SIZE: Vec2 = vec2(256.0, 18.0);
|
||||||
|
|
||||||
const BLACK: Color32 = Color32::BLACK;
|
const BLACK: Color32 = Color32::BLACK;
|
||||||
const GREEN: Color32 = Color32::GREEN;
|
const GREEN: Color32 = Color32::GREEN;
|
||||||
|
@ -16,7 +17,6 @@ pub struct ColorTest {
|
||||||
tex_mngr: TextureManager,
|
tex_mngr: TextureManager,
|
||||||
vertex_gradients: bool,
|
vertex_gradients: bool,
|
||||||
texture_gradients: bool,
|
texture_gradients: bool,
|
||||||
srgb: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ColorTest {
|
impl Default for ColorTest {
|
||||||
|
@ -25,7 +25,6 @@ impl Default for ColorTest {
|
||||||
tex_mngr: Default::default(),
|
tex_mngr: Default::default(),
|
||||||
vertex_gradients: true,
|
vertex_gradients: true,
|
||||||
texture_gradients: true,
|
texture_gradients: true,
|
||||||
srgb: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,12 +37,17 @@ impl ColorTest {
|
||||||
ui.add(crate::egui_github_link_file!());
|
ui.add(crate::egui_github_link_file!());
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.label("This is made to test that the egui painter backend is set up correctly, so that all colors are interpolated and blended in linear space with premultiplied alpha.");
|
ui.horizontal_wrapped(|ui|{
|
||||||
ui.label("If everything is set up correctly, all groups of gradients will look uniform");
|
ui.label("This is made to test that the egui painter backend is set up correctly.");
|
||||||
|
ui.add(egui::Label::new("❓").sense(egui::Sense::click()))
|
||||||
|
.on_hover_text("The texture sampling should be sRGB-aware, and everyt other color operation should be done in gamma-space (sRGB). All colors should use pre-multiplied alpha");
|
||||||
|
});
|
||||||
|
ui.label("If the rendering is done right, all groups of gradients will look uniform.");
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
ui.checkbox(&mut self.vertex_gradients, "Vertex gradients");
|
ui.checkbox(&mut self.vertex_gradients, "Vertex gradients");
|
||||||
ui.checkbox(&mut self.texture_gradients, "Texture gradients");
|
ui.checkbox(&mut self.texture_gradients, "Texture gradients");
|
||||||
ui.checkbox(&mut self.srgb, "Show naive sRGBA horror");
|
});
|
||||||
|
|
||||||
ui.heading("sRGB color test");
|
ui.heading("sRGB color test");
|
||||||
ui.label("Use a color picker to ensure this color is (255, 165, 0) / #ffa500");
|
ui.label("Use a color picker to ensure this color is (255, 165, 0) / #ffa500");
|
||||||
|
@ -56,12 +60,13 @@ impl ColorTest {
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
ui.label("Test that vertex color times texture color is done in linear space:");
|
ui.label("Test that vertex color times texture color is done in gamma space:");
|
||||||
ui.scope(|ui| {
|
ui.scope(|ui| {
|
||||||
ui.spacing_mut().item_spacing.y = 0.0; // No spacing between gradients
|
ui.spacing_mut().item_spacing.y = 0.0; // No spacing between gradients
|
||||||
|
|
||||||
let tex_color = Rgba::from_rgb(1.0, 0.25, 0.25);
|
let tex_color = Color32::from_rgb(64, 128, 255);
|
||||||
let vertex_color = Rgba::from_rgb(0.5, 0.75, 0.75);
|
let vertex_color = Color32::from_rgb(128, 196, 196);
|
||||||
|
let ground_truth = mul_color_gamma(tex_color, vertex_color);
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
let color_size = ui.spacing().interact_size;
|
let color_size = ui.spacing().interact_size;
|
||||||
|
@ -72,13 +77,13 @@ impl ColorTest {
|
||||||
ui.label(" vertex color =");
|
ui.label(" vertex color =");
|
||||||
});
|
});
|
||||||
{
|
{
|
||||||
let g = Gradient::one_color(Color32::from(tex_color * vertex_color));
|
let g = Gradient::one_color(ground_truth);
|
||||||
self.vertex_gradient(ui, "Ground truth (vertices)", WHITE, &g);
|
self.vertex_gradient(ui, "Ground truth (vertices)", WHITE, &g);
|
||||||
self.tex_gradient(ui, "Ground truth (texture)", WHITE, &g);
|
self.tex_gradient(ui, "Ground truth (texture)", WHITE, &g);
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
let g = Gradient::one_color(Color32::from(tex_color));
|
let g = Gradient::one_color(tex_color);
|
||||||
let tex = self.tex_mngr.get(ui.ctx(), &g);
|
let tex = self.tex_mngr.get(ui.ctx(), &g);
|
||||||
let texel_offset = 0.5 / (g.0.len() as f32);
|
let texel_offset = 0.5 / (g.0.len() as f32);
|
||||||
let uv = Rect::from_min_max(pos2(texel_offset, 0.0), pos2(1.0 - texel_offset, 1.0));
|
let uv = Rect::from_min_max(pos2(texel_offset, 0.0), pos2(1.0 - texel_offset, 1.0));
|
||||||
|
@ -93,31 +98,25 @@ impl ColorTest {
|
||||||
// TODO(emilk): test color multiplication (image tint),
|
// TODO(emilk): test color multiplication (image tint),
|
||||||
// to make sure vertex and texture color multiplication is done in linear space.
|
// to make sure vertex and texture color multiplication is done in linear space.
|
||||||
|
|
||||||
self.show_gradients(ui, WHITE, (RED, GREEN));
|
ui.separator();
|
||||||
if self.srgb {
|
ui.label("Gamma interpolation:");
|
||||||
ui.label("Notice the darkening in the center of the naive sRGB interpolation.");
|
self.show_gradients(ui, WHITE, (RED, GREEN), Interpolation::Gamma);
|
||||||
}
|
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
self.show_gradients(ui, RED, (TRANSPARENT, GREEN));
|
self.show_gradients(ui, RED, (TRANSPARENT, GREEN), Interpolation::Gamma);
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
self.show_gradients(ui, WHITE, (TRANSPARENT, GREEN));
|
self.show_gradients(ui, WHITE, (TRANSPARENT, GREEN), Interpolation::Gamma);
|
||||||
if self.srgb {
|
|
||||||
ui.label(
|
|
||||||
"Notice how the linear blend stays green while the naive sRGBA interpolation looks gray in the middle.",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
self.show_gradients(ui, BLACK, (BLACK, WHITE));
|
self.show_gradients(ui, BLACK, (BLACK, WHITE), Interpolation::Gamma);
|
||||||
ui.separator();
|
ui.separator();
|
||||||
self.show_gradients(ui, WHITE, (BLACK, TRANSPARENT));
|
self.show_gradients(ui, WHITE, (BLACK, TRANSPARENT), Interpolation::Gamma);
|
||||||
ui.separator();
|
ui.separator();
|
||||||
self.show_gradients(ui, BLACK, (TRANSPARENT, WHITE));
|
self.show_gradients(ui, BLACK, (TRANSPARENT, WHITE), Interpolation::Gamma);
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
ui.label("Additive blending: add more and more blue to the red background:");
|
ui.label("Additive blending: add more and more blue to the red background:");
|
||||||
|
@ -125,18 +124,40 @@ impl ColorTest {
|
||||||
ui,
|
ui,
|
||||||
RED,
|
RED,
|
||||||
(TRANSPARENT, Color32::from_rgb_additive(0, 0, 255)),
|
(TRANSPARENT, Color32::from_rgb_additive(0, 0, 255)),
|
||||||
|
Interpolation::Gamma,
|
||||||
);
|
);
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
|
ui.label("Linear interpolation (texture sampling):");
|
||||||
|
self.show_gradients(ui, WHITE, (RED, GREEN), Interpolation::Linear);
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
pixel_test(ui);
|
pixel_test(ui);
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
ui.label("Testing text rendering:");
|
||||||
|
|
||||||
|
text_on_bg(ui, Color32::from_gray(200), Color32::from_gray(230)); // gray on gray
|
||||||
|
text_on_bg(ui, Color32::from_gray(140), Color32::from_gray(28)); // dark mode normal text
|
||||||
|
|
||||||
|
// Matches Mac Font book (useful for testing):
|
||||||
|
text_on_bg(ui, Color32::from_gray(39), Color32::from_gray(255));
|
||||||
|
text_on_bg(ui, Color32::from_gray(220), Color32::from_gray(30));
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
blending_and_feathering_test(ui);
|
blending_and_feathering_test(ui);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_gradients(&mut self, ui: &mut Ui, bg_fill: Color32, (left, right): (Color32, Color32)) {
|
fn show_gradients(
|
||||||
|
&mut self,
|
||||||
|
ui: &mut Ui,
|
||||||
|
bg_fill: Color32,
|
||||||
|
(left, right): (Color32, Color32),
|
||||||
|
interpolation: Interpolation,
|
||||||
|
) {
|
||||||
let is_opaque = left.is_opaque() && right.is_opaque();
|
let is_opaque = left.is_opaque() && right.is_opaque();
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
|
@ -154,11 +175,12 @@ impl ColorTest {
|
||||||
ui.scope(|ui| {
|
ui.scope(|ui| {
|
||||||
ui.spacing_mut().item_spacing.y = 0.0; // No spacing between gradients
|
ui.spacing_mut().item_spacing.y = 0.0; // No spacing between gradients
|
||||||
if is_opaque {
|
if is_opaque {
|
||||||
let g = Gradient::ground_truth_linear_gradient(left, right);
|
let g = Gradient::ground_truth_gradient(left, right, interpolation);
|
||||||
self.vertex_gradient(ui, "Ground Truth (CPU gradient) - vertices", bg_fill, &g);
|
self.vertex_gradient(ui, "Ground Truth (CPU gradient) - vertices", bg_fill, &g);
|
||||||
self.tex_gradient(ui, "Ground Truth (CPU gradient) - texture", bg_fill, &g);
|
self.tex_gradient(ui, "Ground Truth (CPU gradient) - texture", bg_fill, &g);
|
||||||
} else {
|
} else {
|
||||||
let g = Gradient::ground_truth_linear_gradient(left, right).with_bg_fill(bg_fill);
|
let g = Gradient::ground_truth_gradient(left, right, interpolation)
|
||||||
|
.with_bg_fill(bg_fill);
|
||||||
self.vertex_gradient(
|
self.vertex_gradient(
|
||||||
ui,
|
ui,
|
||||||
"Ground Truth (CPU gradient, CPU blending) - vertices",
|
"Ground Truth (CPU gradient, CPU blending) - vertices",
|
||||||
|
@ -171,30 +193,27 @@ impl ColorTest {
|
||||||
bg_fill,
|
bg_fill,
|
||||||
&g,
|
&g,
|
||||||
);
|
);
|
||||||
let g = Gradient::ground_truth_linear_gradient(left, right);
|
let g = Gradient::ground_truth_gradient(left, right, interpolation);
|
||||||
self.vertex_gradient(ui, "CPU gradient, GPU blending - vertices", bg_fill, &g);
|
self.vertex_gradient(ui, "CPU gradient, GPU blending - vertices", bg_fill, &g);
|
||||||
self.tex_gradient(ui, "CPU gradient, GPU blending - texture", bg_fill, &g);
|
self.tex_gradient(ui, "CPU gradient, GPU blending - texture", bg_fill, &g);
|
||||||
}
|
}
|
||||||
|
|
||||||
let g = Gradient::texture_gradient(left, right);
|
let g = Gradient::endpoints(left, right);
|
||||||
|
|
||||||
|
match interpolation {
|
||||||
|
Interpolation::Linear => {
|
||||||
|
// texture sampler is sRGBA aware, and should therefore be linear
|
||||||
|
self.tex_gradient(ui, "Texture of width 2 (test texture sampler)", bg_fill, &g);
|
||||||
|
}
|
||||||
|
Interpolation::Gamma => {
|
||||||
|
// vertex shader uses gamma
|
||||||
self.vertex_gradient(
|
self.vertex_gradient(
|
||||||
ui,
|
ui,
|
||||||
"Triangle mesh of width 2 (test vertex decode and interpolation)",
|
"Triangle mesh of width 2 (test vertex decode and interpolation)",
|
||||||
bg_fill,
|
bg_fill,
|
||||||
&g,
|
&g,
|
||||||
);
|
);
|
||||||
self.tex_gradient(ui, "Texture of width 2 (test texture sampler)", bg_fill, &g);
|
}
|
||||||
|
|
||||||
if self.srgb {
|
|
||||||
let g =
|
|
||||||
Gradient::ground_truth_bad_srgba_gradient(left, right).with_bg_fill(bg_fill);
|
|
||||||
self.vertex_gradient(
|
|
||||||
ui,
|
|
||||||
"Triangle mesh with naive sRGBA interpolation (WRONG)",
|
|
||||||
bg_fill,
|
|
||||||
&g,
|
|
||||||
);
|
|
||||||
self.tex_gradient(ui, "Naive sRGBA interpolation (WRONG)", bg_fill, &g);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -258,6 +277,12 @@ fn vertex_gradient(ui: &mut Ui, bg_fill: Color32, gradient: &Gradient) -> Respon
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum Interpolation {
|
||||||
|
Linear,
|
||||||
|
Gamma,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Hash, PartialEq, Eq)]
|
#[derive(Clone, Hash, PartialEq, Eq)]
|
||||||
struct Gradient(pub Vec<Color32>);
|
struct Gradient(pub Vec<Color32>);
|
||||||
|
|
||||||
|
@ -266,10 +291,21 @@ impl Gradient {
|
||||||
Self(vec![srgba, srgba])
|
Self(vec![srgba, srgba])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn texture_gradient(left: Color32, right: Color32) -> Self {
|
pub fn endpoints(left: Color32, right: Color32) -> Self {
|
||||||
Self(vec![left, right])
|
Self(vec![left, right])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ground_truth_gradient(
|
||||||
|
left: Color32,
|
||||||
|
right: Color32,
|
||||||
|
interpolation: Interpolation,
|
||||||
|
) -> Self {
|
||||||
|
match interpolation {
|
||||||
|
Interpolation::Linear => Self::ground_truth_linear_gradient(left, right),
|
||||||
|
Interpolation::Gamma => Self::ground_truth_gamma_gradient(left, right),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn ground_truth_linear_gradient(left: Color32, right: Color32) -> Self {
|
pub fn ground_truth_linear_gradient(left: Color32, right: Color32) -> Self {
|
||||||
let left = Rgba::from(left);
|
let left = Rgba::from(left);
|
||||||
let right = Rgba::from(right);
|
let right = Rgba::from(right);
|
||||||
|
@ -285,33 +321,32 @@ impl Gradient {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This is how a bad person blends `sRGBA`
|
pub fn ground_truth_gamma_gradient(left: Color32, right: Color32) -> Self {
|
||||||
pub fn ground_truth_bad_srgba_gradient(left: Color32, right: Color32) -> Self {
|
|
||||||
let n = 255;
|
let n = 255;
|
||||||
Self(
|
Self(
|
||||||
(0..=n)
|
(0..=n)
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
let t = i as f32 / n as f32;
|
let t = i as f32 / n as f32;
|
||||||
Color32::from_rgba_premultiplied(
|
lerp_color_gamma(left, right, t)
|
||||||
lerp((left[0] as f32)..=(right[0] as f32), t).round() as u8, // Don't ever do this please!
|
|
||||||
lerp((left[1] as f32)..=(right[1] as f32), t).round() as u8, // Don't ever do this please!
|
|
||||||
lerp((left[2] as f32)..=(right[2] as f32), t).round() as u8, // Don't ever do this please!
|
|
||||||
lerp((left[3] as f32)..=(right[3] as f32), t).round() as u8, // Don't ever do this please!
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Do premultiplied alpha-aware blending of the gradient on top of the fill color
|
/// Do premultiplied alpha-aware blending of the gradient on top of the fill color
|
||||||
|
/// in gamma-space.
|
||||||
pub fn with_bg_fill(self, bg: Color32) -> Self {
|
pub fn with_bg_fill(self, bg: Color32) -> Self {
|
||||||
let bg = Rgba::from(bg);
|
|
||||||
Self(
|
Self(
|
||||||
self.0
|
self.0
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|fg| {
|
.map(|fg| {
|
||||||
let fg = Rgba::from(fg);
|
let a = fg.a() as f32 / 255.0;
|
||||||
Color32::from(bg * (1.0 - fg.a()) + fg)
|
Color32::from_rgba_premultiplied(
|
||||||
|
(bg[0] as f32 * (1.0 - a) + fg[0] as f32).round() as u8,
|
||||||
|
(bg[1] as f32 * (1.0 - a) + fg[1] as f32).round() as u8,
|
||||||
|
(bg[2] as f32 * (1.0 - a) + fg[2] as f32).round() as u8,
|
||||||
|
(bg[3] as f32 * (1.0 - a) + fg[3] as f32).round() as u8,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
|
@ -380,8 +415,6 @@ fn pixel_test(ui: &mut Ui) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn blending_and_feathering_test(ui: &mut Ui) {
|
fn blending_and_feathering_test(ui: &mut Ui) {
|
||||||
ui.label("Some fine lines for testing anti-aliasing and blending:");
|
|
||||||
|
|
||||||
let size = vec2(512.0, 512.0);
|
let size = vec2(512.0, 512.0);
|
||||||
let (response, painter) = ui.allocate_painter(size, Sense::hover());
|
let (response, painter) = ui.allocate_painter(size, Sense::hover());
|
||||||
let rect = response.rect;
|
let rect = response.rect;
|
||||||
|
@ -397,32 +430,67 @@ fn blending_and_feathering_test(ui: &mut Ui) {
|
||||||
paint_fine_lines_and_text(&painter, bottom_half, Color32::BLACK);
|
paint_fine_lines_and_text(&painter, bottom_half, Color32::BLACK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn text_on_bg(ui: &mut egui::Ui, fg: Color32, bg: Color32) {
|
||||||
|
assert!(fg.is_opaque());
|
||||||
|
assert!(bg.is_opaque());
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label(
|
||||||
|
RichText::from("▣ The quick brown fox jumps over the lazy dog and runs away.")
|
||||||
|
.background_color(bg)
|
||||||
|
.color(fg),
|
||||||
|
);
|
||||||
|
ui.label(format!(
|
||||||
|
"({} {} {}) on ({} {} {})",
|
||||||
|
fg.r(),
|
||||||
|
fg.g(),
|
||||||
|
fg.b(),
|
||||||
|
bg.r(),
|
||||||
|
bg.g(),
|
||||||
|
bg.b(),
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn paint_fine_lines_and_text(painter: &egui::Painter, mut rect: Rect, color: Color32) {
|
fn paint_fine_lines_and_text(painter: &egui::Painter, mut rect: Rect, color: Color32) {
|
||||||
{
|
{
|
||||||
let mut x = 0.0;
|
let mut y = 0.0;
|
||||||
for opacity in [1.00, 0.50, 0.25, 0.10, 0.05, 0.02, 0.01, 0.00] {
|
for opacity in [1.00, 0.50, 0.25, 0.10, 0.05, 0.02, 0.01, 0.00] {
|
||||||
painter.text(
|
painter.text(
|
||||||
rect.center_top() + vec2(0.0, x),
|
rect.center_top() + vec2(0.0, y),
|
||||||
Align2::LEFT_TOP,
|
Align2::LEFT_TOP,
|
||||||
format!("{:.0}% white", 100.0 * opacity),
|
format!("{:.0}% white", 100.0 * opacity),
|
||||||
FontId::proportional(14.0),
|
FontId::proportional(14.0),
|
||||||
Color32::WHITE.linear_multiply(opacity),
|
Color32::WHITE.linear_multiply(opacity),
|
||||||
);
|
);
|
||||||
painter.text(
|
painter.text(
|
||||||
rect.center_top() + vec2(80.0, x),
|
rect.center_top() + vec2(80.0, y),
|
||||||
Align2::LEFT_TOP,
|
Align2::LEFT_TOP,
|
||||||
format!("{:.0}% gray", 100.0 * opacity),
|
format!("{:.0}% gray", 100.0 * opacity),
|
||||||
FontId::proportional(14.0),
|
FontId::proportional(14.0),
|
||||||
Color32::GRAY.linear_multiply(opacity),
|
Color32::GRAY.linear_multiply(opacity),
|
||||||
);
|
);
|
||||||
painter.text(
|
painter.text(
|
||||||
rect.center_top() + vec2(160.0, x),
|
rect.center_top() + vec2(160.0, y),
|
||||||
Align2::LEFT_TOP,
|
Align2::LEFT_TOP,
|
||||||
format!("{:.0}% black", 100.0 * opacity),
|
format!("{:.0}% black", 100.0 * opacity),
|
||||||
FontId::proportional(14.0),
|
FontId::proportional(14.0),
|
||||||
Color32::BLACK.linear_multiply(opacity),
|
Color32::BLACK.linear_multiply(opacity),
|
||||||
);
|
);
|
||||||
x += 20.0;
|
y += 20.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for font_size in [6.0, 7.0, 8.0, 9.0, 10.0, 12.0, 14.0] {
|
||||||
|
painter.text(
|
||||||
|
rect.center_top() + vec2(0.0, y),
|
||||||
|
Align2::LEFT_TOP,
|
||||||
|
format!(
|
||||||
|
"{font_size}px - The quick brown fox jumps over the lazy dog and runs away."
|
||||||
|
),
|
||||||
|
FontId::proportional(font_size),
|
||||||
|
color,
|
||||||
|
);
|
||||||
|
y += font_size + 1.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -472,3 +540,21 @@ fn paint_fine_lines_and_text(painter: &egui::Painter, mut rect: Rect, color: Col
|
||||||
mesh.add_triangle(1, 2, 3);
|
mesh.add_triangle(1, 2, 3);
|
||||||
painter.add(mesh);
|
painter.add(mesh);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn mul_color_gamma(left: Color32, right: Color32) -> Color32 {
|
||||||
|
Color32::from_rgba_premultiplied(
|
||||||
|
(left.r() as f32 * right.r() as f32 / 255.0).round() as u8,
|
||||||
|
(left.g() as f32 * right.g() as f32 / 255.0).round() as u8,
|
||||||
|
(left.b() as f32 * right.b() as f32 / 255.0).round() as u8,
|
||||||
|
(left.a() as f32 * right.a() as f32 / 255.0).round() as u8,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lerp_color_gamma(left: Color32, right: Color32, t: f32) -> Color32 {
|
||||||
|
Color32::from_rgba_premultiplied(
|
||||||
|
lerp((left[0] as f32)..=(right[0] as f32), t).round() as u8,
|
||||||
|
lerp((left[1] as f32)..=(right[1] as f32), t).round() as u8,
|
||||||
|
lerp((left[2] as f32)..=(right[2] as f32), t).round() as u8,
|
||||||
|
lerp((left[3] as f32)..=(right[3] as f32), t).round() as u8,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -54,4 +54,5 @@ document-features = { version = "0.2", optional = true }
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
egui_demo_lib = { version = "0.19.0", path = "../egui_demo_lib", default-features = false }
|
||||||
image = { version = "0.24", default-features = false, features = ["png"] }
|
image = { version = "0.24", default-features = false, features = ["png"] }
|
||||||
|
|
|
@ -115,7 +115,7 @@ fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Disp
|
||||||
|
|
||||||
let context_builder = glutin::ContextBuilder::new()
|
let context_builder = glutin::ContextBuilder::new()
|
||||||
.with_depth_buffer(0)
|
.with_depth_buffer(0)
|
||||||
.with_srgb(true)
|
.with_srgb(false)
|
||||||
.with_stencil_buffer(0)
|
.with_stencil_buffer(0)
|
||||||
.with_vsync(true);
|
.with_vsync(true);
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,8 @@ fn main() {
|
||||||
|
|
||||||
let mut egui_glium = egui_glium::EguiGlium::new(&display, &event_loop);
|
let mut egui_glium = egui_glium::EguiGlium::new(&display, &event_loop);
|
||||||
|
|
||||||
|
let mut color_test = egui_demo_lib::ColorTest::default();
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
event_loop.run(move |event, _, control_flow| {
|
||||||
let mut redraw = || {
|
let mut redraw = || {
|
||||||
let mut quit = false;
|
let mut quit = false;
|
||||||
|
@ -21,6 +23,12 @@ fn main() {
|
||||||
quit = true;
|
quit = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
egui::CentralPanel::default().show(egui_ctx, |ui| {
|
||||||
|
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||||
|
color_test.ui(ui);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
*control_flow = if quit {
|
*control_flow = if quit {
|
||||||
|
@ -93,7 +101,7 @@ fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Disp
|
||||||
|
|
||||||
let context_builder = glutin::ContextBuilder::new()
|
let context_builder = glutin::ContextBuilder::new()
|
||||||
.with_depth_buffer(0)
|
.with_depth_buffer(0)
|
||||||
.with_srgb(true)
|
.with_srgb(false)
|
||||||
.with_stencil_buffer(0)
|
.with_stencil_buffer(0)
|
||||||
.with_vsync(true);
|
.with_vsync(true);
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ use {
|
||||||
glium::{
|
glium::{
|
||||||
implement_vertex,
|
implement_vertex,
|
||||||
index::PrimitiveType,
|
index::PrimitiveType,
|
||||||
program,
|
|
||||||
texture::{self, srgb_texture2d::SrgbTexture2d},
|
texture::{self, srgb_texture2d::SrgbTexture2d},
|
||||||
uniform,
|
uniform,
|
||||||
uniforms::{MagnifySamplerFilter, SamplerWrapFunction},
|
uniforms::{MagnifySamplerFilter, SamplerWrapFunction},
|
||||||
|
@ -26,31 +25,77 @@ pub struct Painter {
|
||||||
next_native_tex_id: u64,
|
next_native_tex_id: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_program(
|
||||||
|
facade: &dyn glium::backend::Facade,
|
||||||
|
vertex_shader: &str,
|
||||||
|
fragment_shader: &str,
|
||||||
|
) -> glium::program::Program {
|
||||||
|
let input = glium::program::ProgramCreationInput::SourceCode {
|
||||||
|
vertex_shader,
|
||||||
|
tessellation_control_shader: None,
|
||||||
|
tessellation_evaluation_shader: None,
|
||||||
|
geometry_shader: None,
|
||||||
|
fragment_shader,
|
||||||
|
transform_feedback_varyings: None,
|
||||||
|
outputs_srgb: true,
|
||||||
|
uses_point_size: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
glium::program::Program::new(facade, input)
|
||||||
|
.unwrap_or_else(|err| panic!("Failed to compile shader: {}", err))
|
||||||
|
}
|
||||||
|
|
||||||
impl Painter {
|
impl Painter {
|
||||||
pub fn new(facade: &dyn glium::backend::Facade) -> Painter {
|
pub fn new(facade: &dyn glium::backend::Facade) -> Painter {
|
||||||
use glium::CapabilitiesSource as _;
|
use glium::CapabilitiesSource as _;
|
||||||
let max_texture_side = facade.get_capabilities().max_texture_size as _;
|
let max_texture_side = facade.get_capabilities().max_texture_size as _;
|
||||||
|
|
||||||
let program = program! {
|
let program = if facade
|
||||||
|
.get_context()
|
||||||
|
.is_glsl_version_supported(&glium::Version(glium::Api::Gl, 1, 4))
|
||||||
|
{
|
||||||
|
eprintln!("Using GL 1.4");
|
||||||
|
create_program(
|
||||||
facade,
|
facade,
|
||||||
120 => {
|
include_str!("shader/vertex_140.glsl"),
|
||||||
vertex: include_str!("shader/vertex_120.glsl"),
|
include_str!("shader/fragment_140.glsl"),
|
||||||
fragment: include_str!("shader/fragment_120.glsl"),
|
)
|
||||||
},
|
} else if facade
|
||||||
140 => {
|
.get_context()
|
||||||
vertex: include_str!("shader/vertex_140.glsl"),
|
.is_glsl_version_supported(&glium::Version(glium::Api::Gl, 1, 2))
|
||||||
fragment: include_str!("shader/fragment_140.glsl"),
|
{
|
||||||
},
|
eprintln!("Using GL 1.2");
|
||||||
100 es => {
|
create_program(
|
||||||
vertex: include_str!("shader/vertex_100es.glsl"),
|
facade,
|
||||||
fragment: include_str!("shader/fragment_100es.glsl"),
|
include_str!("shader/vertex_120.glsl"),
|
||||||
},
|
include_str!("shader/fragment_120.glsl"),
|
||||||
300 es => {
|
)
|
||||||
vertex: include_str!("shader/vertex_300es.glsl"),
|
} else if facade
|
||||||
fragment: include_str!("shader/fragment_300es.glsl"),
|
.get_context()
|
||||||
},
|
.is_glsl_version_supported(&glium::Version(glium::Api::GlEs, 3, 0))
|
||||||
}
|
{
|
||||||
.expect("Failed to compile shader");
|
eprintln!("Using GL ES 3.0");
|
||||||
|
create_program(
|
||||||
|
facade,
|
||||||
|
include_str!("shader/vertex_300es.glsl"),
|
||||||
|
include_str!("shader/fragment_300es.glsl"),
|
||||||
|
)
|
||||||
|
} else if facade
|
||||||
|
.get_context()
|
||||||
|
.is_glsl_version_supported(&glium::Version(glium::Api::GlEs, 1, 0))
|
||||||
|
{
|
||||||
|
eprintln!("Using GL ES 1.0");
|
||||||
|
create_program(
|
||||||
|
facade,
|
||||||
|
include_str!("shader/vertex_100es.glsl"),
|
||||||
|
include_str!("shader/fragment_100es.glsl"),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
panic!(
|
||||||
|
"Failed to find a compatible shader for OpenGL version {:?}",
|
||||||
|
facade.get_version()
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
Painter {
|
Painter {
|
||||||
max_texture_side,
|
max_texture_side,
|
||||||
|
@ -236,13 +281,10 @@ impl Painter {
|
||||||
);
|
);
|
||||||
image.pixels.iter().map(|color| color.to_tuple()).collect()
|
image.pixels.iter().map(|color| color.to_tuple()).collect()
|
||||||
}
|
}
|
||||||
egui::ImageData::Font(image) => {
|
egui::ImageData::Font(image) => image
|
||||||
let gamma = 1.0;
|
.srgba_pixels(None)
|
||||||
image
|
|
||||||
.srgba_pixels(gamma)
|
|
||||||
.map(|color| color.to_tuple())
|
.map(|color| color.to_tuple())
|
||||||
.collect()
|
.collect(),
|
||||||
}
|
|
||||||
};
|
};
|
||||||
let glium_image = glium::texture::RawImage2d {
|
let glium_image = glium::texture::RawImage2d {
|
||||||
data: std::borrow::Cow::Owned(pixels),
|
data: std::borrow::Cow::Owned(pixels),
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
precision mediump float;
|
precision mediump float;
|
||||||
uniform sampler2D u_sampler;
|
uniform sampler2D u_sampler;
|
||||||
varying vec4 v_rgba;
|
varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA
|
||||||
varying vec2 v_tc;
|
varying vec2 v_tc;
|
||||||
|
|
||||||
// 0-255 sRGB from 0-1 linear
|
// 0-255 sRGB from 0-1 linear
|
||||||
|
@ -30,16 +30,9 @@ vec4 linear_from_srgba(vec4 srgba) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
// We must decode the colors, since WebGL doesn't come with sRGBA textures:
|
// WebGL doesn't come with sRGBA textures:
|
||||||
vec4 texture_rgba = linear_from_srgba(texture2D(u_sampler, v_tc) * 255.0);
|
vec4 texture_in_gamma = texture2D(u_sampler, v_tc);
|
||||||
|
|
||||||
/// Multiply vertex color with texture color (in linear space).
|
// Multiply vertex color with texture color (in gamma space).
|
||||||
gl_FragColor = v_rgba * texture_rgba;
|
gl_FragColor = v_rgba_gamma * texture_in_gamma;
|
||||||
|
|
||||||
// We must gamma-encode again since WebGL doesn't support linear blending in the framebuffer.
|
|
||||||
gl_FragColor = srgba_from_linear(v_rgba * texture_rgba) / 255.0;
|
|
||||||
|
|
||||||
// WebGL doesn't support linear blending in the framebuffer,
|
|
||||||
// so we apply this hack to at least get a bit closer to the desired blending:
|
|
||||||
gl_FragColor.a = pow(gl_FragColor.a, 1.6); // Empiric nonsense
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,31 @@
|
||||||
#version 120
|
#version 120
|
||||||
|
|
||||||
uniform sampler2D u_sampler;
|
uniform sampler2D u_sampler;
|
||||||
varying vec4 v_rgba;
|
varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA
|
||||||
varying vec2 v_tc;
|
varying vec2 v_tc;
|
||||||
|
|
||||||
void main() {
|
// 0-255 sRGB from 0-1 linear
|
||||||
// The texture sampler is sRGB aware, and glium already expects linear rgba output
|
vec3 srgb_from_linear(vec3 rgb) {
|
||||||
// so no need for any sRGB conversions here:
|
bvec3 cutoff = lessThan(rgb, vec3(0.0031308));
|
||||||
gl_FragColor = v_rgba * texture2D(u_sampler, v_tc);
|
vec3 lower = rgb * vec3(3294.6);
|
||||||
|
vec3 higher = vec3(269.025) * pow(rgb, vec3(1.0 / 2.4)) - vec3(14.025);
|
||||||
|
return mix(higher, lower, vec3(cutoff));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0-255 sRGBA from 0-1 linear
|
||||||
|
vec4 srgba_from_linear(vec4 rgba) {
|
||||||
|
return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0-1 gamma from 0-1 linear
|
||||||
|
vec4 gamma_from_linear_rgba(vec4 linear_rgba) {
|
||||||
|
return vec4(srgb_from_linear(linear_rgba.rgb) / 255.0, linear_rgba.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// The texture is set up with `SRGB8_ALPHA8`
|
||||||
|
vec4 texture_in_gamma = gamma_from_linear_rgba(texture2D(u_sampler, v_tc));
|
||||||
|
|
||||||
|
// Multiply vertex color with texture color (in gamma space).
|
||||||
|
gl_FragColor = v_rgba_gamma * texture_in_gamma;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,32 @@
|
||||||
#version 140
|
#version 140
|
||||||
|
|
||||||
uniform sampler2D u_sampler;
|
uniform sampler2D u_sampler;
|
||||||
in vec4 v_rgba;
|
in vec4 v_rgba_gamma;
|
||||||
in vec2 v_tc;
|
in vec2 v_tc;
|
||||||
out vec4 f_color;
|
out vec4 f_color;
|
||||||
|
|
||||||
void main() {
|
// 0-255 sRGB from 0-1 linear
|
||||||
// The texture sampler is sRGB aware, and glium already expects linear rgba output
|
vec3 srgb_from_linear(vec3 rgb) {
|
||||||
// so no need for any sRGB conversions here:
|
bvec3 cutoff = lessThan(rgb, vec3(0.0031308));
|
||||||
f_color = v_rgba * texture(u_sampler, v_tc);
|
vec3 lower = rgb * vec3(3294.6);
|
||||||
|
vec3 higher = vec3(269.025) * pow(rgb, vec3(1.0 / 2.4)) - vec3(14.025);
|
||||||
|
return mix(higher, lower, vec3(cutoff));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0-255 sRGBA from 0-1 linear
|
||||||
|
vec4 srgba_from_linear(vec4 rgba) {
|
||||||
|
return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0-1 gamma from 0-1 linear
|
||||||
|
vec4 gamma_from_linear_rgba(vec4 linear_rgba) {
|
||||||
|
return vec4(srgb_from_linear(linear_rgba.rgb) / 255.0, linear_rgba.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// The texture is set up with `SRGB8_ALPHA8`
|
||||||
|
vec4 texture_in_gamma = gamma_from_linear_rgba(texture(u_sampler, v_tc));
|
||||||
|
|
||||||
|
// Multiply vertex color with texture color (in gamma space).
|
||||||
|
f_color = v_rgba_gamma * texture_in_gamma;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
precision mediump float;
|
precision mediump float;
|
||||||
uniform sampler2D u_sampler;
|
uniform sampler2D u_sampler;
|
||||||
varying vec4 v_rgba;
|
varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA
|
||||||
varying vec2 v_tc;
|
varying vec2 v_tc;
|
||||||
|
|
||||||
// 0-255 sRGB from 0-1 linear
|
// 0-255 sRGB from 0-1 linear
|
||||||
|
@ -13,21 +13,20 @@ vec3 srgb_from_linear(vec3 rgb) {
|
||||||
return mix(higher, lower, vec3(cutoff));
|
return mix(higher, lower, vec3(cutoff));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 0-255 sRGBA from 0-1 linear
|
||||||
vec4 srgba_from_linear(vec4 rgba) {
|
vec4 srgba_from_linear(vec4 rgba) {
|
||||||
return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a);
|
return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a);
|
||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
// 0-1 gamma from 0-1 linear
|
||||||
// The texture is set up with `SRGB8_ALPHA8`, so no need to decode here!
|
vec4 gamma_from_linear_rgba(vec4 linear_rgba) {
|
||||||
vec4 texture_rgba = texture2D(u_sampler, v_tc);
|
return vec4(srgb_from_linear(linear_rgba.rgb) / 255.0, linear_rgba.a);
|
||||||
|
}
|
||||||
/// Multiply vertex color with texture color (in linear space).
|
|
||||||
gl_FragColor = v_rgba * texture_rgba;
|
void main() {
|
||||||
|
// The texture is set up with `SRGB8_ALPHA8`
|
||||||
// We must gamma-encode again since WebGL doesn't support linear blending in the framebuffer.
|
vec4 texture_in_gamma = gamma_from_linear_rgba(texture2D(u_sampler, v_tc));
|
||||||
gl_FragColor = srgba_from_linear(v_rgba * texture_rgba) / 255.0;
|
|
||||||
|
// Multiply vertex color with texture color (in gamma space).
|
||||||
// WebGL doesn't support linear blending in the framebuffer,
|
gl_FragColor = v_rgba_gamma * texture_in_gamma;
|
||||||
// so we apply this hack to at least get a bit closer to the desired blending:
|
|
||||||
gl_FragColor.a = pow(gl_FragColor.a, 1.6); // Empiric nonsense
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,28 +5,15 @@ uniform vec2 u_screen_size;
|
||||||
attribute vec2 a_pos;
|
attribute vec2 a_pos;
|
||||||
attribute vec2 a_tc;
|
attribute vec2 a_tc;
|
||||||
attribute vec4 a_srgba;
|
attribute vec4 a_srgba;
|
||||||
varying vec4 v_rgba;
|
varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA
|
||||||
varying vec2 v_tc;
|
varying vec2 v_tc;
|
||||||
|
|
||||||
// 0-1 linear from 0-255 sRGB
|
|
||||||
vec3 linear_from_srgb(vec3 srgb) {
|
|
||||||
bvec3 cutoff = lessThan(srgb, vec3(10.31475));
|
|
||||||
vec3 lower = srgb / vec3(3294.6);
|
|
||||||
vec3 higher = pow((srgb + vec3(14.025)) / vec3(269.025), vec3(2.4));
|
|
||||||
return mix(higher, lower, vec3(cutoff));
|
|
||||||
}
|
|
||||||
|
|
||||||
vec4 linear_from_srgba(vec4 srgba) {
|
|
||||||
return vec4(linear_from_srgb(srgba.rgb), srgba.a / 255.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
gl_Position = vec4(
|
gl_Position = vec4(
|
||||||
2.0 * a_pos.x / u_screen_size.x - 1.0,
|
2.0 * a_pos.x / u_screen_size.x - 1.0,
|
||||||
1.0 - 2.0 * a_pos.y / u_screen_size.y,
|
1.0 - 2.0 * a_pos.y / u_screen_size.y,
|
||||||
0.0,
|
0.0,
|
||||||
1.0);
|
1.0);
|
||||||
// egui encodes vertex colors in gamma spaces, so we must decode the colors here:
|
v_rgba_gamma = a_srgba / 255.0;
|
||||||
v_rgba = linear_from_srgba(a_srgba);
|
|
||||||
v_tc = a_tc;
|
v_tc = a_tc;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,28 +4,15 @@ uniform vec2 u_screen_size;
|
||||||
attribute vec2 a_pos;
|
attribute vec2 a_pos;
|
||||||
attribute vec4 a_srgba; // 0-255 sRGB
|
attribute vec4 a_srgba; // 0-255 sRGB
|
||||||
attribute vec2 a_tc;
|
attribute vec2 a_tc;
|
||||||
varying vec4 v_rgba;
|
varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA
|
||||||
varying vec2 v_tc;
|
varying vec2 v_tc;
|
||||||
|
|
||||||
// 0-1 linear from 0-255 sRGB
|
|
||||||
vec3 linear_from_srgb(vec3 srgb) {
|
|
||||||
bvec3 cutoff = lessThan(srgb, vec3(10.31475));
|
|
||||||
vec3 lower = srgb / vec3(3294.6);
|
|
||||||
vec3 higher = pow((srgb + vec3(14.025)) / vec3(269.025), vec3(2.4));
|
|
||||||
return mix(higher, lower, vec3(cutoff));
|
|
||||||
}
|
|
||||||
|
|
||||||
vec4 linear_from_srgba(vec4 srgba) {
|
|
||||||
return vec4(linear_from_srgb(srgba.rgb), srgba.a / 255.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
gl_Position = vec4(
|
gl_Position = vec4(
|
||||||
2.0 * a_pos.x / u_screen_size.x - 1.0,
|
2.0 * a_pos.x / u_screen_size.x - 1.0,
|
||||||
1.0 - 2.0 * a_pos.y / u_screen_size.y,
|
1.0 - 2.0 * a_pos.y / u_screen_size.y,
|
||||||
0.0,
|
0.0,
|
||||||
1.0);
|
1.0);
|
||||||
// egui encodes vertex colors in gamma spaces, so we must decode the colors here:
|
v_rgba_gamma = a_srgba / 255.0;
|
||||||
v_rgba = linear_from_srgba(a_srgba);
|
|
||||||
v_tc = a_tc;
|
v_tc = a_tc;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,28 +4,15 @@ uniform vec2 u_screen_size;
|
||||||
in vec2 a_pos;
|
in vec2 a_pos;
|
||||||
in vec4 a_srgba; // 0-255 sRGB
|
in vec4 a_srgba; // 0-255 sRGB
|
||||||
in vec2 a_tc;
|
in vec2 a_tc;
|
||||||
out vec4 v_rgba;
|
out vec4 v_rgba_gamma;
|
||||||
out vec2 v_tc;
|
out vec2 v_tc;
|
||||||
|
|
||||||
// 0-1 linear from 0-255 sRGB
|
|
||||||
vec3 linear_from_srgb(vec3 srgb) {
|
|
||||||
bvec3 cutoff = lessThan(srgb, vec3(10.31475));
|
|
||||||
vec3 lower = srgb / vec3(3294.6);
|
|
||||||
vec3 higher = pow((srgb + vec3(14.025)) / vec3(269.025), vec3(2.4));
|
|
||||||
return mix(higher, lower, cutoff);
|
|
||||||
}
|
|
||||||
|
|
||||||
vec4 linear_from_srgba(vec4 srgba) {
|
|
||||||
return vec4(linear_from_srgb(srgba.rgb), srgba.a / 255.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
gl_Position = vec4(
|
gl_Position = vec4(
|
||||||
2.0 * a_pos.x / u_screen_size.x - 1.0,
|
2.0 * a_pos.x / u_screen_size.x - 1.0,
|
||||||
1.0 - 2.0 * a_pos.y / u_screen_size.y,
|
1.0 - 2.0 * a_pos.y / u_screen_size.y,
|
||||||
0.0,
|
0.0,
|
||||||
1.0);
|
1.0);
|
||||||
// egui encodes vertex colors in gamma spaces, so we must decode the colors here:
|
v_rgba_gamma = a_srgba / 255.0;
|
||||||
v_rgba = linear_from_srgba(a_srgba);
|
|
||||||
v_tc = a_tc;
|
v_tc = a_tc;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,28 +5,15 @@ uniform vec2 u_screen_size;
|
||||||
attribute vec2 a_pos;
|
attribute vec2 a_pos;
|
||||||
attribute vec2 a_tc;
|
attribute vec2 a_tc;
|
||||||
attribute vec4 a_srgba;
|
attribute vec4 a_srgba;
|
||||||
varying vec4 v_rgba;
|
varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA
|
||||||
varying vec2 v_tc;
|
varying vec2 v_tc;
|
||||||
|
|
||||||
// 0-1 linear from 0-255 sRGB
|
|
||||||
vec3 linear_from_srgb(vec3 srgb) {
|
|
||||||
bvec3 cutoff = lessThan(srgb, vec3(10.31475));
|
|
||||||
vec3 lower = srgb / vec3(3294.6);
|
|
||||||
vec3 higher = pow((srgb + vec3(14.025)) / vec3(269.025), vec3(2.4));
|
|
||||||
return mix(higher, lower, vec3(cutoff));
|
|
||||||
}
|
|
||||||
|
|
||||||
vec4 linear_from_srgba(vec4 srgba) {
|
|
||||||
return vec4(linear_from_srgb(srgba.rgb), srgba.a / 255.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
gl_Position = vec4(
|
gl_Position = vec4(
|
||||||
2.0 * a_pos.x / u_screen_size.x - 1.0,
|
2.0 * a_pos.x / u_screen_size.x - 1.0,
|
||||||
1.0 - 2.0 * a_pos.y / u_screen_size.y,
|
1.0 - 2.0 * a_pos.y / u_screen_size.y,
|
||||||
0.0,
|
0.0,
|
||||||
1.0);
|
1.0);
|
||||||
// egui encodes vertex colors in gamma spaces, so we must decode the colors here:
|
v_rgba_gamma = a_srgba / 255.0;
|
||||||
v_rgba = linear_from_srgba(a_srgba);
|
|
||||||
v_tc = a_tc;
|
v_tc = a_tc;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ All notable changes to the `egui_glow` integration will be noted in this file.
|
||||||
* Allow empty textures.
|
* Allow empty textures.
|
||||||
* Added `shader_version` variable on `EguiGlow::new` for easier cross compilling on different OpenGL | ES targets ([#1993](https://github.com/emilk/egui/pull/1993)).
|
* Added `shader_version` variable on `EguiGlow::new` for easier cross compilling on different OpenGL | ES targets ([#1993](https://github.com/emilk/egui/pull/1993)).
|
||||||
|
|
||||||
|
|
||||||
## 0.19.0 - 2022-08-20
|
## 0.19.0 - 2022-08-20
|
||||||
* MSRV (Minimum Supported Rust Version) is now `1.61.0` ([#1846](https://github.com/emilk/egui/pull/1846)).
|
* MSRV (Minimum Supported Rust Version) is now `1.61.0` ([#1846](https://github.com/emilk/egui/pull/1846)).
|
||||||
* `EguiGlow::new` now takes an `EventLoopWindowTarget<E>` instead of a `winit::Window` ([#1634](https://github.com/emilk/egui/pull/1634)).
|
* `EguiGlow::new` now takes an `EventLoopWindowTarget<E>` instead of a `winit::Window` ([#1634](https://github.com/emilk/egui/pull/1634)).
|
||||||
|
|
|
@ -116,7 +116,7 @@ fn create_display(
|
||||||
let gl_window = unsafe {
|
let gl_window = unsafe {
|
||||||
glutin::ContextBuilder::new()
|
glutin::ContextBuilder::new()
|
||||||
.with_depth_buffer(0)
|
.with_depth_buffer(0)
|
||||||
.with_srgb(true)
|
.with_srgb(false)
|
||||||
.with_stencil_buffer(0)
|
.with_stencil_buffer(0)
|
||||||
.with_vsync(true)
|
.with_vsync(true)
|
||||||
.build_windowed(window_builder, event_loop)
|
.build_windowed(window_builder, event_loop)
|
||||||
|
|
|
@ -15,7 +15,6 @@ pub mod painter;
|
||||||
pub use glow;
|
pub use glow;
|
||||||
pub use painter::{CallbackFn, Painter};
|
pub use painter::{CallbackFn, Painter};
|
||||||
mod misc_util;
|
mod misc_util;
|
||||||
mod post_process;
|
|
||||||
mod shader_version;
|
mod shader_version;
|
||||||
mod vao;
|
mod vao;
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,6 @@ use memoffset::offset_of;
|
||||||
|
|
||||||
use crate::check_for_gl_error;
|
use crate::check_for_gl_error;
|
||||||
use crate::misc_util::{compile_shader, link_program};
|
use crate::misc_util::{compile_shader, link_program};
|
||||||
use crate::post_process::PostProcess;
|
|
||||||
use crate::shader_version::ShaderVersion;
|
use crate::shader_version::ShaderVersion;
|
||||||
use crate::vao;
|
use crate::vao;
|
||||||
|
|
||||||
|
@ -52,10 +51,8 @@ pub struct Painter {
|
||||||
u_screen_size: glow::UniformLocation,
|
u_screen_size: glow::UniformLocation,
|
||||||
u_sampler: glow::UniformLocation,
|
u_sampler: glow::UniformLocation,
|
||||||
is_webgl_1: bool,
|
is_webgl_1: bool,
|
||||||
is_embedded: bool,
|
|
||||||
vao: crate::vao::VertexArrayObject,
|
vao: crate::vao::VertexArrayObject,
|
||||||
srgb_textures: bool,
|
srgb_textures: bool,
|
||||||
post_process: Option<PostProcess>,
|
|
||||||
vbo: glow::Buffer,
|
vbo: glow::Buffer,
|
||||||
element_array_buffer: glow::Buffer,
|
element_array_buffer: glow::Buffer,
|
||||||
|
|
||||||
|
@ -105,7 +102,6 @@ impl Painter {
|
||||||
/// * failed to create buffer
|
/// * failed to create buffer
|
||||||
pub fn new(
|
pub fn new(
|
||||||
gl: Arc<glow::Context>,
|
gl: Arc<glow::Context>,
|
||||||
pp_fb_extent: Option<[i32; 2]>,
|
|
||||||
shader_prefix: &str,
|
shader_prefix: &str,
|
||||||
shader_version: Option<ShaderVersion>,
|
shader_version: Option<ShaderVersion>,
|
||||||
) -> Result<Painter, String> {
|
) -> Result<Painter, String> {
|
||||||
|
@ -115,8 +111,8 @@ impl Painter {
|
||||||
let max_texture_side = unsafe { gl.get_parameter_i32(glow::MAX_TEXTURE_SIZE) } as usize;
|
let max_texture_side = unsafe { gl.get_parameter_i32(glow::MAX_TEXTURE_SIZE) } as usize;
|
||||||
let shader_version = shader_version.unwrap_or_else(|| ShaderVersion::get(&gl));
|
let shader_version = shader_version.unwrap_or_else(|| ShaderVersion::get(&gl));
|
||||||
let is_webgl_1 = shader_version == ShaderVersion::Es100;
|
let is_webgl_1 = shader_version == ShaderVersion::Es100;
|
||||||
let header = shader_version.version_declaration();
|
let shader_version_declaration = shader_version.version_declaration();
|
||||||
tracing::debug!("Shader header: {:?}.", header);
|
tracing::debug!("Shader header: {:?}.", shader_version_declaration);
|
||||||
|
|
||||||
let supported_extensions = gl.supported_extensions();
|
let supported_extensions = gl.supported_extensions();
|
||||||
tracing::trace!("OpenGL extensions: {supported_extensions:?}");
|
tracing::trace!("OpenGL extensions: {supported_extensions:?}");
|
||||||
|
@ -125,44 +121,17 @@ impl Painter {
|
||||||
// EXT_sRGB, GL_ARB_framebuffer_sRGB, GL_EXT_sRGB, GL_EXT_texture_sRGB_decode, …
|
// EXT_sRGB, GL_ARB_framebuffer_sRGB, GL_EXT_sRGB, GL_EXT_texture_sRGB_decode, …
|
||||||
extension.contains("sRGB")
|
extension.contains("sRGB")
|
||||||
});
|
});
|
||||||
tracing::debug!("SRGB Support: {:?}", srgb_textures);
|
tracing::debug!("SRGB texture Support: {:?}", srgb_textures);
|
||||||
|
|
||||||
let (post_process, srgb_support_define) = if shader_version.is_embedded() {
|
|
||||||
// WebGL doesn't support linear framebuffer blending… but maybe we can emulate it with `PostProcess`?
|
|
||||||
|
|
||||||
if let Some(size) = pp_fb_extent {
|
|
||||||
tracing::debug!("WebGL with sRGB support. Turning on post processing for linear framebuffer blending.");
|
|
||||||
// install post process to correct sRGB color:
|
|
||||||
(
|
|
||||||
Some(unsafe { PostProcess::new(gl.clone(), shader_prefix, is_webgl_1, size)? }),
|
|
||||||
"#define SRGB_SUPPORTED",
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
tracing::warn!("WebGL or OpenGL ES detected but PostProcess disabled because dimension is None. Linear framebuffer blending will not be supported.");
|
|
||||||
(None, "")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if srgb_textures {
|
|
||||||
(None, "#define SRGB_SUPPORTED")
|
|
||||||
} else {
|
|
||||||
tracing::warn!("sRGB aware texture filtering not available");
|
|
||||||
(None, "")
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let vert = compile_shader(
|
let vert = compile_shader(
|
||||||
&gl,
|
&gl,
|
||||||
glow::VERTEX_SHADER,
|
glow::VERTEX_SHADER,
|
||||||
&format!(
|
&format!(
|
||||||
"{}\n{}\n{}\n{}",
|
"{}\n#define NEW_SHADER_INTERFACE {}\n{}\n{}",
|
||||||
header,
|
shader_version_declaration,
|
||||||
|
shader_version.is_new_shader_interface() as i32,
|
||||||
shader_prefix,
|
shader_prefix,
|
||||||
if shader_version.is_new_shader_interface() {
|
|
||||||
"#define NEW_SHADER_INTERFACE\n"
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
},
|
|
||||||
VERT_SRC
|
VERT_SRC
|
||||||
),
|
),
|
||||||
)?;
|
)?;
|
||||||
|
@ -170,15 +139,11 @@ impl Painter {
|
||||||
&gl,
|
&gl,
|
||||||
glow::FRAGMENT_SHADER,
|
glow::FRAGMENT_SHADER,
|
||||||
&format!(
|
&format!(
|
||||||
"{}\n{}\n{}\n{}\n{}",
|
"{}\n#define NEW_SHADER_INTERFACE {}\n#define SRGB_TEXTURES {}\n{}\n{}",
|
||||||
header,
|
shader_version_declaration,
|
||||||
|
shader_version.is_new_shader_interface() as i32,
|
||||||
|
srgb_textures as i32,
|
||||||
shader_prefix,
|
shader_prefix,
|
||||||
srgb_support_define,
|
|
||||||
if shader_version.is_new_shader_interface() {
|
|
||||||
"#define NEW_SHADER_INTERFACE\n"
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
},
|
|
||||||
FRAG_SRC
|
FRAG_SRC
|
||||||
),
|
),
|
||||||
)?;
|
)?;
|
||||||
|
@ -236,10 +201,8 @@ impl Painter {
|
||||||
u_screen_size,
|
u_screen_size,
|
||||||
u_sampler,
|
u_sampler,
|
||||||
is_webgl_1,
|
is_webgl_1,
|
||||||
is_embedded: shader_version.is_embedded(),
|
|
||||||
vao,
|
vao,
|
||||||
srgb_textures,
|
srgb_textures,
|
||||||
post_process,
|
|
||||||
vbo,
|
vbo,
|
||||||
element_array_buffer,
|
element_array_buffer,
|
||||||
textures: Default::default(),
|
textures: Default::default(),
|
||||||
|
@ -268,8 +231,11 @@ impl Painter {
|
||||||
/// So if in a [`egui::Shape::Callback`] you need to use an offscreen FBO, you should
|
/// So if in a [`egui::Shape::Callback`] you need to use an offscreen FBO, you should
|
||||||
/// then restore to this afterwards with
|
/// then restore to this afterwards with
|
||||||
/// `gl.bind_framebuffer(glow::FRAMEBUFFER, painter.intermediate_fbo());`
|
/// `gl.bind_framebuffer(glow::FRAMEBUFFER, painter.intermediate_fbo());`
|
||||||
|
#[allow(clippy::unused_self)]
|
||||||
pub fn intermediate_fbo(&self) -> Option<glow::Framebuffer> {
|
pub fn intermediate_fbo(&self) -> Option<glow::Framebuffer> {
|
||||||
self.post_process.as_ref().map(|pp| pp.fbo())
|
// We don't currently ever render to an offscreen buffer,
|
||||||
|
// but we may want to start to in order to do anti-aliasing on web, for instance.
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn prepare_painting(
|
unsafe fn prepare_painting(
|
||||||
|
@ -298,7 +264,7 @@ impl Painter {
|
||||||
);
|
);
|
||||||
|
|
||||||
if !cfg!(target_arch = "wasm32") {
|
if !cfg!(target_arch = "wasm32") {
|
||||||
self.gl.enable(glow::FRAMEBUFFER_SRGB);
|
self.gl.disable(glow::FRAMEBUFFER_SRGB);
|
||||||
check_for_gl_error!(&self.gl, "FRAMEBUFFER_SRGB");
|
check_for_gl_error!(&self.gl, "FRAMEBUFFER_SRGB");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -372,17 +338,6 @@ impl Painter {
|
||||||
crate::profile_function!();
|
crate::profile_function!();
|
||||||
self.assert_not_destroyed();
|
self.assert_not_destroyed();
|
||||||
|
|
||||||
if let Some(ref mut post_process) = self.post_process {
|
|
||||||
unsafe {
|
|
||||||
post_process.begin(screen_size_px[0] as i32, screen_size_px[1] as i32);
|
|
||||||
post_process.bind();
|
|
||||||
self.gl.disable(glow::SCISSOR_TEST);
|
|
||||||
self.gl
|
|
||||||
.viewport(0, 0, screen_size_px[0] as i32, screen_size_px[1] as i32);
|
|
||||||
// use the same clear-color as was set for the screen framebuffer.
|
|
||||||
self.gl.clear(glow::COLOR_BUFFER_BIT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let size_in_pixels = unsafe { self.prepare_painting(screen_size_px, pixels_per_point) };
|
let size_in_pixels = unsafe { self.prepare_painting(screen_size_px, pixels_per_point) };
|
||||||
|
|
||||||
for egui::ClippedPrimitive {
|
for egui::ClippedPrimitive {
|
||||||
|
@ -435,12 +390,7 @@ impl Painter {
|
||||||
check_for_gl_error!(&self.gl, "callback");
|
check_for_gl_error!(&self.gl, "callback");
|
||||||
|
|
||||||
// Restore state:
|
// Restore state:
|
||||||
unsafe {
|
unsafe { self.prepare_painting(screen_size_px, pixels_per_point) };
|
||||||
if let Some(ref mut post_process) = self.post_process {
|
|
||||||
post_process.bind();
|
|
||||||
}
|
|
||||||
self.prepare_painting(screen_size_px, pixels_per_point)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -450,10 +400,6 @@ impl Painter {
|
||||||
self.vao.unbind(&self.gl);
|
self.vao.unbind(&self.gl);
|
||||||
self.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None);
|
self.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None);
|
||||||
|
|
||||||
if let Some(ref post_process) = self.post_process {
|
|
||||||
post_process.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.gl.disable(glow::SCISSOR_TEST);
|
self.gl.disable(glow::SCISSOR_TEST);
|
||||||
|
|
||||||
check_for_gl_error!(&self.gl, "painting");
|
check_for_gl_error!(&self.gl, "painting");
|
||||||
|
@ -532,13 +478,8 @@ impl Painter {
|
||||||
"Mismatch between texture size and texel count"
|
"Mismatch between texture size and texel count"
|
||||||
);
|
);
|
||||||
|
|
||||||
let gamma = if self.is_embedded && self.post_process.is_none() {
|
|
||||||
1.0 / 2.2
|
|
||||||
} else {
|
|
||||||
1.0
|
|
||||||
};
|
|
||||||
let data: Vec<u8> = image
|
let data: Vec<u8> = image
|
||||||
.srgba_pixels(gamma)
|
.srgba_pixels(None)
|
||||||
.flat_map(|a| a.to_array())
|
.flat_map(|a| a.to_array())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -684,9 +625,6 @@ impl Painter {
|
||||||
if !self.destroyed {
|
if !self.destroyed {
|
||||||
unsafe {
|
unsafe {
|
||||||
self.destroy_gl();
|
self.destroy_gl();
|
||||||
if let Some(ref post_process) = self.post_process {
|
|
||||||
post_process.destroy();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self.destroyed = true;
|
self.destroyed = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,284 +0,0 @@
|
||||||
#![allow(unsafe_code)]
|
|
||||||
use crate::check_for_gl_error;
|
|
||||||
use crate::misc_util::{compile_shader, link_program};
|
|
||||||
use crate::vao::BufferInfo;
|
|
||||||
use glow::HasContext as _;
|
|
||||||
|
|
||||||
/// Uses a framebuffer to render everything in linear color space and convert it back to `sRGB`
|
|
||||||
/// in a separate "post processing" step
|
|
||||||
pub(crate) struct PostProcess {
|
|
||||||
gl: std::sync::Arc<glow::Context>,
|
|
||||||
pos_buffer: glow::Buffer,
|
|
||||||
index_buffer: glow::Buffer,
|
|
||||||
vao: crate::vao::VertexArrayObject,
|
|
||||||
is_webgl_1: bool,
|
|
||||||
color_texture: glow::Texture,
|
|
||||||
depth_renderbuffer: Option<glow::Renderbuffer>,
|
|
||||||
texture_size: (i32, i32),
|
|
||||||
fbo: glow::Framebuffer,
|
|
||||||
program: glow::Program,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PostProcess {
|
|
||||||
pub(crate) unsafe fn new(
|
|
||||||
gl: std::sync::Arc<glow::Context>,
|
|
||||||
shader_prefix: &str,
|
|
||||||
is_webgl_1: bool,
|
|
||||||
[width, height]: [i32; 2],
|
|
||||||
) -> Result<PostProcess, String> {
|
|
||||||
gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1);
|
|
||||||
|
|
||||||
let fbo = gl.create_framebuffer()?;
|
|
||||||
|
|
||||||
gl.bind_framebuffer(glow::FRAMEBUFFER, Some(fbo));
|
|
||||||
|
|
||||||
// ----------------------------------------------
|
|
||||||
// Set up color tesxture:
|
|
||||||
|
|
||||||
let color_texture = gl.create_texture()?;
|
|
||||||
gl.bind_texture(glow::TEXTURE_2D, Some(color_texture));
|
|
||||||
gl.tex_parameter_i32(
|
|
||||||
glow::TEXTURE_2D,
|
|
||||||
glow::TEXTURE_WRAP_S,
|
|
||||||
glow::CLAMP_TO_EDGE as i32,
|
|
||||||
);
|
|
||||||
gl.tex_parameter_i32(
|
|
||||||
glow::TEXTURE_2D,
|
|
||||||
glow::TEXTURE_WRAP_T,
|
|
||||||
glow::CLAMP_TO_EDGE as i32,
|
|
||||||
);
|
|
||||||
gl.tex_parameter_i32(
|
|
||||||
glow::TEXTURE_2D,
|
|
||||||
glow::TEXTURE_MIN_FILTER,
|
|
||||||
glow::NEAREST as i32,
|
|
||||||
);
|
|
||||||
gl.tex_parameter_i32(
|
|
||||||
glow::TEXTURE_2D,
|
|
||||||
glow::TEXTURE_MAG_FILTER,
|
|
||||||
glow::NEAREST as i32,
|
|
||||||
);
|
|
||||||
|
|
||||||
let (internal_format, format) = if is_webgl_1 {
|
|
||||||
(glow::SRGB_ALPHA, glow::SRGB_ALPHA)
|
|
||||||
} else {
|
|
||||||
(glow::SRGB8_ALPHA8, glow::RGBA)
|
|
||||||
};
|
|
||||||
|
|
||||||
gl.tex_image_2d(
|
|
||||||
glow::TEXTURE_2D,
|
|
||||||
0,
|
|
||||||
internal_format as i32,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
0,
|
|
||||||
format,
|
|
||||||
glow::UNSIGNED_BYTE,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
crate::check_for_gl_error_even_in_release!(&gl, "post process texture initialization");
|
|
||||||
|
|
||||||
gl.framebuffer_texture_2d(
|
|
||||||
glow::FRAMEBUFFER,
|
|
||||||
glow::COLOR_ATTACHMENT0,
|
|
||||||
glow::TEXTURE_2D,
|
|
||||||
Some(color_texture),
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
gl.bind_texture(glow::TEXTURE_2D, None);
|
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
// Depth buffer - we only need this when embedding 3D within egui using `egui::PaintCallback`.
|
|
||||||
// TODO(emilk): add a setting to enable/disable the depth buffer.
|
|
||||||
|
|
||||||
let with_depth_buffer = true;
|
|
||||||
let depth_renderbuffer = if with_depth_buffer {
|
|
||||||
let depth_renderbuffer = gl.create_renderbuffer()?;
|
|
||||||
gl.bind_renderbuffer(glow::RENDERBUFFER, Some(depth_renderbuffer));
|
|
||||||
gl.renderbuffer_storage(glow::RENDERBUFFER, glow::DEPTH_COMPONENT16, width, height);
|
|
||||||
gl.bind_renderbuffer(glow::RENDERBUFFER, None);
|
|
||||||
Some(depth_renderbuffer)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
|
|
||||||
gl.bind_framebuffer(glow::FRAMEBUFFER, None);
|
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
|
|
||||||
let vert_shader = compile_shader(
|
|
||||||
&gl,
|
|
||||||
glow::VERTEX_SHADER,
|
|
||||||
&format!(
|
|
||||||
"{}\n{}",
|
|
||||||
shader_prefix,
|
|
||||||
include_str!("shader/post_vertex_100es.glsl")
|
|
||||||
),
|
|
||||||
)?;
|
|
||||||
let frag_shader = compile_shader(
|
|
||||||
&gl,
|
|
||||||
glow::FRAGMENT_SHADER,
|
|
||||||
&format!(
|
|
||||||
"{}\n{}",
|
|
||||||
shader_prefix,
|
|
||||||
include_str!("shader/post_fragment_100es.glsl")
|
|
||||||
),
|
|
||||||
)?;
|
|
||||||
let program = link_program(&gl, [vert_shader, frag_shader].iter())?;
|
|
||||||
|
|
||||||
let positions: Vec<f32> = vec![0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0];
|
|
||||||
|
|
||||||
let indices: Vec<u8> = vec![0, 1, 2, 1, 2, 3];
|
|
||||||
|
|
||||||
let pos_buffer = gl.create_buffer()?;
|
|
||||||
gl.bind_buffer(glow::ARRAY_BUFFER, Some(pos_buffer));
|
|
||||||
gl.buffer_data_u8_slice(
|
|
||||||
glow::ARRAY_BUFFER,
|
|
||||||
bytemuck::cast_slice(&positions),
|
|
||||||
glow::STATIC_DRAW,
|
|
||||||
);
|
|
||||||
|
|
||||||
let a_pos_loc = gl
|
|
||||||
.get_attrib_location(program, "a_pos")
|
|
||||||
.ok_or_else(|| "failed to get location of a_pos".to_owned())?;
|
|
||||||
let vao = crate::vao::VertexArrayObject::new(
|
|
||||||
&gl,
|
|
||||||
pos_buffer,
|
|
||||||
vec![BufferInfo {
|
|
||||||
location: a_pos_loc,
|
|
||||||
vector_size: 2,
|
|
||||||
data_type: glow::FLOAT,
|
|
||||||
normalized: false,
|
|
||||||
stride: 0,
|
|
||||||
offset: 0,
|
|
||||||
}],
|
|
||||||
);
|
|
||||||
|
|
||||||
let index_buffer = gl.create_buffer()?;
|
|
||||||
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(index_buffer));
|
|
||||||
gl.buffer_data_u8_slice(glow::ELEMENT_ARRAY_BUFFER, &indices, glow::STATIC_DRAW);
|
|
||||||
|
|
||||||
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None);
|
|
||||||
crate::check_for_gl_error_even_in_release!(&gl, "post process initialization");
|
|
||||||
|
|
||||||
Ok(PostProcess {
|
|
||||||
gl,
|
|
||||||
pos_buffer,
|
|
||||||
index_buffer,
|
|
||||||
vao,
|
|
||||||
is_webgl_1,
|
|
||||||
color_texture,
|
|
||||||
depth_renderbuffer,
|
|
||||||
texture_size: (width, height),
|
|
||||||
fbo,
|
|
||||||
program,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// What we render to.
|
|
||||||
pub(crate) fn fbo(&self) -> glow::Framebuffer {
|
|
||||||
self.fbo
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) unsafe fn begin(&mut self, width: i32, height: i32) {
|
|
||||||
if (width, height) != self.texture_size {
|
|
||||||
self.gl
|
|
||||||
.bind_texture(glow::TEXTURE_2D, Some(self.color_texture));
|
|
||||||
self.gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1);
|
|
||||||
let (internal_format, format) = if self.is_webgl_1 {
|
|
||||||
(glow::SRGB_ALPHA, glow::SRGB_ALPHA)
|
|
||||||
} else {
|
|
||||||
(glow::SRGB8_ALPHA8, glow::RGBA)
|
|
||||||
};
|
|
||||||
self.gl.tex_image_2d(
|
|
||||||
glow::TEXTURE_2D,
|
|
||||||
0,
|
|
||||||
internal_format as i32,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
0,
|
|
||||||
format,
|
|
||||||
glow::UNSIGNED_BYTE,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
self.gl.bind_texture(glow::TEXTURE_2D, None);
|
|
||||||
|
|
||||||
if let Some(depth_renderbuffer) = self.depth_renderbuffer {
|
|
||||||
self.gl
|
|
||||||
.bind_renderbuffer(glow::RENDERBUFFER, Some(depth_renderbuffer));
|
|
||||||
self.gl.renderbuffer_storage(
|
|
||||||
glow::RENDERBUFFER,
|
|
||||||
glow::DEPTH_COMPONENT16,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
);
|
|
||||||
self.gl.bind_renderbuffer(glow::RENDERBUFFER, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.texture_size = (width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
check_for_gl_error!(&self.gl, "PostProcess::begin");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) unsafe fn bind(&self) {
|
|
||||||
self.gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.fbo));
|
|
||||||
|
|
||||||
self.gl.framebuffer_texture_2d(
|
|
||||||
glow::FRAMEBUFFER,
|
|
||||||
glow::COLOR_ATTACHMENT0,
|
|
||||||
glow::TEXTURE_2D,
|
|
||||||
Some(self.color_texture),
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
|
|
||||||
self.gl.framebuffer_renderbuffer(
|
|
||||||
glow::FRAMEBUFFER,
|
|
||||||
glow::DEPTH_ATTACHMENT,
|
|
||||||
glow::RENDERBUFFER,
|
|
||||||
self.depth_renderbuffer,
|
|
||||||
);
|
|
||||||
|
|
||||||
check_for_gl_error!(&self.gl, "PostProcess::bind");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) unsafe fn end(&self) {
|
|
||||||
self.gl.bind_framebuffer(glow::FRAMEBUFFER, None);
|
|
||||||
self.gl.disable(glow::SCISSOR_TEST);
|
|
||||||
|
|
||||||
self.gl.use_program(Some(self.program));
|
|
||||||
|
|
||||||
self.gl.active_texture(glow::TEXTURE0);
|
|
||||||
self.gl
|
|
||||||
.bind_texture(glow::TEXTURE_2D, Some(self.color_texture));
|
|
||||||
let u_sampler_loc = self
|
|
||||||
.gl
|
|
||||||
.get_uniform_location(self.program, "u_sampler")
|
|
||||||
.unwrap();
|
|
||||||
self.gl.uniform_1_i32(Some(&u_sampler_loc), 0);
|
|
||||||
self.vao.bind(&self.gl);
|
|
||||||
|
|
||||||
self.gl
|
|
||||||
.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.index_buffer));
|
|
||||||
self.gl
|
|
||||||
.draw_elements(glow::TRIANGLES, 6, glow::UNSIGNED_BYTE, 0);
|
|
||||||
self.vao.unbind(&self.gl);
|
|
||||||
self.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None);
|
|
||||||
self.gl.bind_texture(glow::TEXTURE_2D, None);
|
|
||||||
self.gl.use_program(None);
|
|
||||||
|
|
||||||
check_for_gl_error!(&self.gl, "PostProcess::end");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) unsafe fn destroy(&self) {
|
|
||||||
self.gl.delete_buffer(self.pos_buffer);
|
|
||||||
self.gl.delete_buffer(self.index_buffer);
|
|
||||||
self.gl.delete_program(self.program);
|
|
||||||
self.gl.delete_framebuffer(self.fbo);
|
|
||||||
self.gl.delete_texture(self.color_texture);
|
|
||||||
if let Some(depth_renderbuffer) = self.depth_renderbuffer {
|
|
||||||
self.gl.delete_renderbuffer(depth_renderbuffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,73 +4,38 @@
|
||||||
|
|
||||||
uniform sampler2D u_sampler;
|
uniform sampler2D u_sampler;
|
||||||
|
|
||||||
#ifdef NEW_SHADER_INTERFACE
|
#if NEW_SHADER_INTERFACE
|
||||||
in vec4 v_rgba;
|
in vec4 v_rgba_in_gamma;
|
||||||
in vec2 v_tc;
|
in vec2 v_tc;
|
||||||
out vec4 f_color;
|
out vec4 f_color;
|
||||||
// a dirty hack applied to support webGL2
|
// a dirty hack applied to support webGL2
|
||||||
#define gl_FragColor f_color
|
#define gl_FragColor f_color
|
||||||
#define texture2D texture
|
#define texture2D texture
|
||||||
#else
|
#else
|
||||||
varying vec4 v_rgba;
|
varying vec4 v_rgba_in_gamma;
|
||||||
varying vec2 v_tc;
|
varying vec2 v_tc;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef SRGB_SUPPORTED
|
// 0-1 sRGB gamma from 0-1 linear
|
||||||
void main() {
|
vec3 srgb_gamma_from_linear(vec3 rgb) {
|
||||||
// The texture sampler is sRGB aware, and OpenGL already expects linear rgba output
|
|
||||||
// so no need for any sRGB conversions here:
|
|
||||||
gl_FragColor = v_rgba * texture2D(u_sampler, v_tc);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
// 0-255 sRGB from 0-1 linear
|
|
||||||
vec3 srgb_from_linear(vec3 rgb) {
|
|
||||||
bvec3 cutoff = lessThan(rgb, vec3(0.0031308));
|
bvec3 cutoff = lessThan(rgb, vec3(0.0031308));
|
||||||
vec3 lower = rgb * vec3(3294.6);
|
vec3 lower = rgb * vec3(12.92);
|
||||||
vec3 higher = vec3(269.025) * pow(rgb, vec3(1.0 / 2.4)) - vec3(14.025);
|
vec3 higher = vec3(1.055) * pow(rgb, vec3(1.0 / 2.4)) - vec3(0.055);
|
||||||
return mix(higher, lower, vec3(cutoff));
|
return mix(higher, lower, vec3(cutoff));
|
||||||
}
|
}
|
||||||
|
|
||||||
vec4 srgba_from_linear(vec4 rgba) {
|
// 0-1 sRGBA gamma from 0-1 linear
|
||||||
return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a);
|
vec4 srgba_gamma_from_linear(vec4 rgba) {
|
||||||
}
|
return vec4(srgb_gamma_from_linear(rgba.rgb), rgba.a);
|
||||||
|
|
||||||
// 0-1 linear from 0-255 sRGB
|
|
||||||
vec3 linear_from_srgb(vec3 srgb) {
|
|
||||||
bvec3 cutoff = lessThan(srgb, vec3(10.31475));
|
|
||||||
vec3 lower = srgb / vec3(3294.6);
|
|
||||||
vec3 higher = pow((srgb + vec3(14.025)) / vec3(269.025), vec3(2.4));
|
|
||||||
return mix(higher, lower, vec3(cutoff));
|
|
||||||
}
|
|
||||||
|
|
||||||
vec4 linear_from_srgba(vec4 srgba) {
|
|
||||||
return vec4(linear_from_srgb(srgba.rgb), srgba.a / 255.0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
// We must decode the colors, since WebGL1 doesn't come with sRGBA textures:
|
#if SRGB_TEXTURES
|
||||||
vec4 texture_rgba = linear_from_srgba(texture2D(u_sampler, v_tc) * 255.0);
|
vec4 texture_in_gamma = srgba_gamma_from_linear(texture2D(u_sampler, v_tc));
|
||||||
/// Multiply vertex color with texture color (in linear space).
|
#else
|
||||||
gl_FragColor = v_rgba * texture_rgba;
|
vec4 texture_in_gamma = texture2D(u_sampler, v_tc);
|
||||||
|
|
||||||
// WebGL1 doesn't support linear blending in the framebuffer,
|
|
||||||
// so we do a hack here where we change the premultiplied alpha
|
|
||||||
// to do the multiplication in gamma space instead:
|
|
||||||
|
|
||||||
// Unmultiply alpha:
|
|
||||||
if (gl_FragColor.a > 0.0) {
|
|
||||||
gl_FragColor.rgb /= gl_FragColor.a;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empiric tweak to make e.g. shadows look more like they should:
|
|
||||||
gl_FragColor.a *= sqrt(gl_FragColor.a);
|
|
||||||
|
|
||||||
// To gamma:
|
|
||||||
gl_FragColor = srgba_from_linear(gl_FragColor) / 255.0;
|
|
||||||
|
|
||||||
// Premultiply alpha, this time in gamma space:
|
|
||||||
if (gl_FragColor.a > 0.0) {
|
|
||||||
gl_FragColor.rgb *= gl_FragColor.a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// We multiply the colors in gamma space, because that's the only way to get text to look right.
|
||||||
|
gl_FragColor = v_rgba_in_gamma * texture_in_gamma;
|
||||||
|
}
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
precision mediump float;
|
|
||||||
uniform sampler2D u_sampler;
|
|
||||||
varying vec2 v_tc;
|
|
||||||
|
|
||||||
// 0-255 sRGB from 0-1 linear
|
|
||||||
vec3 srgb_from_linear(vec3 rgb) {
|
|
||||||
bvec3 cutoff = lessThan(rgb, vec3(0.0031308));
|
|
||||||
vec3 lower = rgb * vec3(3294.6);
|
|
||||||
vec3 higher = vec3(269.025) * pow(rgb, vec3(1.0 / 2.4)) - vec3(14.025);
|
|
||||||
return mix(higher, lower, vec3(cutoff));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 0-255 sRGBA from 0-1 linear
|
|
||||||
vec4 srgba_from_linear(vec4 rgba) {
|
|
||||||
return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a);
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_FragColor = texture2D(u_sampler, v_tc);
|
|
||||||
|
|
||||||
gl_FragColor = srgba_from_linear(gl_FragColor) / 255.0;
|
|
||||||
|
|
||||||
#ifdef APPLY_BRIGHTENING_GAMMA
|
|
||||||
gl_FragColor = vec4(pow(gl_FragColor.rgb, vec3(1.0/2.2)), gl_FragColor.a);
|
|
||||||
#endif
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
precision mediump float;
|
|
||||||
attribute vec2 a_pos;
|
|
||||||
varying vec2 v_tc;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = vec4(a_pos * 2. - 1., 0.0, 1.0);
|
|
||||||
v_tc = a_pos;
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
#ifdef NEW_SHADER_INTERFACE
|
#if NEW_SHADER_INTERFACE
|
||||||
#define I in
|
#define I in
|
||||||
#define O out
|
#define O out
|
||||||
#define V(x) x
|
#define V(x) x
|
||||||
|
@ -16,28 +16,15 @@ uniform vec2 u_screen_size;
|
||||||
I vec2 a_pos;
|
I vec2 a_pos;
|
||||||
I vec4 a_srgba; // 0-255 sRGB
|
I vec4 a_srgba; // 0-255 sRGB
|
||||||
I vec2 a_tc;
|
I vec2 a_tc;
|
||||||
O vec4 v_rgba;
|
O vec4 v_rgba_in_gamma;
|
||||||
O vec2 v_tc;
|
O vec2 v_tc;
|
||||||
|
|
||||||
// 0-1 linear from 0-255 sRGB
|
|
||||||
vec3 linear_from_srgb(vec3 srgb) {
|
|
||||||
bvec3 cutoff = lessThan(srgb, vec3(10.31475));
|
|
||||||
vec3 lower = srgb / vec3(3294.6);
|
|
||||||
vec3 higher = pow((srgb + vec3(14.025)) / vec3(269.025), vec3(2.4));
|
|
||||||
return mix(higher, lower, V(cutoff));
|
|
||||||
}
|
|
||||||
|
|
||||||
vec4 linear_from_srgba(vec4 srgba) {
|
|
||||||
return vec4(linear_from_srgb(srgba.rgb), srgba.a / 255.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
gl_Position = vec4(
|
gl_Position = vec4(
|
||||||
2.0 * a_pos.x / u_screen_size.x - 1.0,
|
2.0 * a_pos.x / u_screen_size.x - 1.0,
|
||||||
1.0 - 2.0 * a_pos.y / u_screen_size.y,
|
1.0 - 2.0 * a_pos.y / u_screen_size.y,
|
||||||
0.0,
|
0.0,
|
||||||
1.0);
|
1.0);
|
||||||
// egui encodes vertex colors in gamma space, so we must decode the colors here:
|
v_rgba_in_gamma = a_srgba / 255.0;
|
||||||
v_rgba = linear_from_srgba(a_srgba);
|
|
||||||
v_tc = a_tc;
|
v_tc = a_tc;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ impl EguiGlow {
|
||||||
gl: std::sync::Arc<glow::Context>,
|
gl: std::sync::Arc<glow::Context>,
|
||||||
shader_version: Option<ShaderVersion>,
|
shader_version: Option<ShaderVersion>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let painter = crate::Painter::new(gl, None, "", shader_version)
|
let painter = crate::Painter::new(gl, "", shader_version)
|
||||||
.map_err(|error| {
|
.map_err(|error| {
|
||||||
tracing::error!("error occurred in initializing painter:\n{}", error);
|
tracing::error!("error occurred in initializing painter:\n{}", error);
|
||||||
})
|
})
|
||||||
|
|
|
@ -4,6 +4,7 @@ All notable changes to the epaint crate will be documented in this file.
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
* ⚠️ BREAKING: Fix text being too small ([#2069](https://github.com/emilk/egui/pull/2069)).
|
* ⚠️ BREAKING: Fix text being too small ([#2069](https://github.com/emilk/egui/pull/2069)).
|
||||||
|
* ⚠️ BREAKING: epaint now expects integrations to do all color blending in gamma space ([#2071](https://github.com/emilk/egui/pull/2071)).
|
||||||
|
|
||||||
|
|
||||||
## 0.19.0 - 2022-08-20
|
## 0.19.0 - 2022-08-20
|
||||||
|
|
|
@ -326,7 +326,7 @@ impl Rgba {
|
||||||
/// Transparent white
|
/// Transparent white
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn from_white_alpha(a: f32) -> Self {
|
pub fn from_white_alpha(a: f32) -> Self {
|
||||||
crate::epaint_assert!(0.0 <= a && a <= 1.0);
|
crate::epaint_assert!(0.0 <= a && a <= 1.0, "a: {}", a);
|
||||||
Self([a, a, a, a])
|
Self([a, a, a, a])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -195,17 +195,19 @@ impl FontImage {
|
||||||
|
|
||||||
/// Returns the textures as `sRGBA` premultiplied pixels, row by row, top to bottom.
|
/// Returns the textures as `sRGBA` premultiplied pixels, row by row, top to bottom.
|
||||||
///
|
///
|
||||||
/// `gamma` should normally be set to 1.0.
|
/// `gamma` should normally be set to `None`.
|
||||||
/// If you are having problems with text looking skinny and pixelated, try
|
///
|
||||||
/// setting a lower gamma, e.g. `0.5`.
|
/// If you are having problems with text looking skinny and pixelated, try using a low gamma, e.g. `0.4`.
|
||||||
pub fn srgba_pixels(&'_ self, gamma: f32) -> impl ExactSizeIterator<Item = Color32> + '_ {
|
pub fn srgba_pixels(
|
||||||
|
&'_ self,
|
||||||
|
gamma: Option<f32>,
|
||||||
|
) -> impl ExactSizeIterator<Item = Color32> + '_ {
|
||||||
|
let gamma = gamma.unwrap_or(0.55); // TODO(emilk): this default coverage gamma is a magic constant, chosen by eye. I don't even know why we need it.
|
||||||
self.pixels.iter().map(move |coverage| {
|
self.pixels.iter().map(move |coverage| {
|
||||||
// This is arbitrarily chosen to make text look as good as possible.
|
let alpha = coverage.powf(gamma);
|
||||||
// In particular, it looks good with gamma=1 and the default eframe backend,
|
// We want to multiply with `vec4(alpha)` in the fragment shader:
|
||||||
// which uses linear blending.
|
let a = fast_round(alpha * 255.0);
|
||||||
// See https://github.com/emilk/egui/issues/1410
|
Color32::from_rgba_premultiplied(a, a, a, a)
|
||||||
let a = fast_round(coverage.powf(gamma / 2.2) * 255.0);
|
|
||||||
Color32::from_rgba_premultiplied(a, a, a, a) // this makes no sense, but works
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ pub struct Shadow {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Shadow {
|
impl Shadow {
|
||||||
/// Tooltips, menus, …
|
/// Tooltips, menus, …, for dark mode.
|
||||||
pub fn small_dark() -> Self {
|
pub fn small_dark() -> Self {
|
||||||
Self {
|
Self {
|
||||||
extrusion: 16.0,
|
extrusion: 16.0,
|
||||||
|
@ -22,15 +22,15 @@ impl Shadow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tooltips, menus, …
|
/// Tooltips, menus, …, for light mode.
|
||||||
pub fn small_light() -> Self {
|
pub fn small_light() -> Self {
|
||||||
Self {
|
Self {
|
||||||
extrusion: 16.0,
|
extrusion: 16.0,
|
||||||
color: Color32::from_black_alpha(32),
|
color: Color32::from_black_alpha(20),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Subtle and nice on dark backgrounds
|
/// Used for widnows in dark mode.
|
||||||
pub fn big_dark() -> Self {
|
pub fn big_dark() -> Self {
|
||||||
Self {
|
Self {
|
||||||
extrusion: 32.0,
|
extrusion: 32.0,
|
||||||
|
@ -38,11 +38,11 @@ impl Shadow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Subtle and nice on white backgrounds
|
/// Used for widnows in light mode.
|
||||||
pub fn big_light() -> Self {
|
pub fn big_light() -> Self {
|
||||||
Self {
|
Self {
|
||||||
extrusion: 32.0,
|
extrusion: 32.0,
|
||||||
color: Color32::from_black_alpha(40),
|
color: Color32::from_black_alpha(16),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ fn main() {
|
||||||
|
|
||||||
struct MyApp {
|
struct MyApp {
|
||||||
image: RetainedImage,
|
image: RetainedImage,
|
||||||
|
tint: egui::Color32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for MyApp {
|
impl Default for MyApp {
|
||||||
|
@ -28,6 +29,7 @@ impl Default for MyApp {
|
||||||
include_bytes!("rust-logo-256x256.png"),
|
include_bytes!("rust-logo-256x256.png"),
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
tint: egui::Color32::from_rgb(255, 0, 255),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,12 +40,22 @@ impl eframe::App for MyApp {
|
||||||
ui.heading("This is an image:");
|
ui.heading("This is an image:");
|
||||||
self.image.show(ui);
|
self.image.show(ui);
|
||||||
|
|
||||||
ui.heading("This is a rotated image:");
|
ui.heading("This is a rotated image with a tint:");
|
||||||
ui.add(
|
ui.add(
|
||||||
egui::Image::new(self.image.texture_id(ctx), self.image.size_vec2())
|
egui::Image::new(self.image.texture_id(ctx), self.image.size_vec2())
|
||||||
.rotate(45.0_f32.to_radians(), egui::Vec2::splat(0.5)),
|
.rotate(45.0_f32.to_radians(), egui::Vec2::splat(0.5))
|
||||||
|
.tint(self.tint),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Tint:");
|
||||||
|
egui::color_picker::color_edit_button_srgba(
|
||||||
|
ui,
|
||||||
|
&mut self.tint,
|
||||||
|
egui::color_picker::Alpha::BlendOrAdditive,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
ui.heading("This is an image you can click:");
|
ui.heading("This is an image you can click:");
|
||||||
ui.add(egui::ImageButton::new(
|
ui.add(egui::ImageButton::new(
|
||||||
self.image.texture_id(ctx),
|
self.image.texture_id(ctx),
|
||||||
|
|
Loading…
Reference in a new issue