1
0
mirror of https://github.com/emilk/egui.git synced 2026-09-01 06:10:06 -04:00

Move all crates into a crates directory (#1940)

This commit is contained in:
Emil Ernerfeldt
2022-08-20 10:41:49 +02:00
committed by GitHub
parent 5c63648c02
commit 041f2e64ba
260 changed files with 59 additions and 57 deletions

View File

@@ -0,0 +1,103 @@
//! [`egui`] bindings for [`glium`](https://github.com/glium/glium).
//!
//! The main type you want to use is [`EguiGlium`].
//!
//! If you are writing an app, you may want to look at [`eframe`](https://docs.rs/eframe) instead.
//!
//! ## Feature flags
#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
//!
#![allow(clippy::float_cmp)]
#![allow(clippy::manual_range_contains)]
mod painter;
pub use painter::Painter;
pub use egui_winit;
use egui_winit::winit::event_loop::EventLoopWindowTarget;
// ----------------------------------------------------------------------------
/// Convenience wrapper for using [`egui`] from a [`glium`] app.
pub struct EguiGlium {
pub egui_ctx: egui::Context,
pub egui_winit: egui_winit::State,
pub painter: crate::Painter,
shapes: Vec<egui::epaint::ClippedShape>,
textures_delta: egui::TexturesDelta,
}
impl EguiGlium {
pub fn new<E>(display: &glium::Display, event_loop: &EventLoopWindowTarget<E>) -> Self {
let painter = crate::Painter::new(display);
let mut egui_winit = egui_winit::State::new(event_loop);
egui_winit.set_max_texture_side(painter.max_texture_side());
let pixels_per_point = display.gl_window().window().scale_factor() as f32;
egui_winit.set_pixels_per_point(pixels_per_point);
Self {
egui_ctx: Default::default(),
egui_winit,
painter,
shapes: Default::default(),
textures_delta: Default::default(),
}
}
/// Returns `true` if egui wants exclusive use of this event
/// (e.g. a mouse click on an egui window, or entering text into a text field).
/// For instance, if you use egui for a game, you want to first call this
/// and only when this returns `false` pass on the events to your game.
///
/// Note that egui uses `tab` to move focus between elements, so this will always return `true` for tabs.
pub fn on_event(&mut self, event: &glium::glutin::event::WindowEvent<'_>) -> bool {
self.egui_winit.on_event(&self.egui_ctx, event)
}
/// Returns `true` if egui requests a repaint.
///
/// Call [`Self::paint`] later to paint.
pub fn run(
&mut self,
display: &glium::Display,
run_ui: impl FnMut(&egui::Context),
) -> std::time::Duration {
let raw_input = self
.egui_winit
.take_egui_input(display.gl_window().window());
let egui::FullOutput {
platform_output,
repaint_after,
textures_delta,
shapes,
} = self.egui_ctx.run(raw_input, run_ui);
self.egui_winit.handle_platform_output(
display.gl_window().window(),
&self.egui_ctx,
platform_output,
);
self.shapes = shapes;
self.textures_delta.append(textures_delta);
repaint_after
}
/// Paint the results of the last call to [`Self::run`].
pub fn paint<T: glium::Surface>(&mut self, display: &glium::Display, target: &mut T) {
let shapes = std::mem::take(&mut self.shapes);
let textures_delta = std::mem::take(&mut self.textures_delta);
let clipped_primitives = self.egui_ctx.tessellate(shapes);
self.painter.paint_and_update_textures(
display,
target,
self.egui_ctx.pixels_per_point(),
&clipped_primitives,
&textures_delta,
);
}
}

View File

@@ -0,0 +1,290 @@
#![allow(deprecated)] // legacy implement_vertex macro
#![allow(semicolon_in_expressions_from_macros)] // glium::program! macro
use egui::epaint::Primitive;
use {
egui::{emath::Rect, epaint::Mesh},
glium::{
implement_vertex,
index::PrimitiveType,
program,
texture::{self, srgb_texture2d::SrgbTexture2d},
uniform,
uniforms::{MagnifySamplerFilter, SamplerWrapFunction},
},
std::rc::Rc,
};
pub struct Painter {
max_texture_side: usize,
program: glium::Program,
textures: ahash::HashMap<egui::TextureId, Rc<SrgbTexture2d>>,
/// [`egui::TextureId::User`] index
next_native_tex_id: u64,
}
impl Painter {
pub fn new(facade: &dyn glium::backend::Facade) -> Painter {
use glium::CapabilitiesSource as _;
let max_texture_side = facade.get_capabilities().max_texture_size as _;
let program = program! {
facade,
120 => {
vertex: include_str!("shader/vertex_120.glsl"),
fragment: include_str!("shader/fragment_120.glsl"),
},
140 => {
vertex: include_str!("shader/vertex_140.glsl"),
fragment: include_str!("shader/fragment_140.glsl"),
},
100 es => {
vertex: include_str!("shader/vertex_100es.glsl"),
fragment: include_str!("shader/fragment_100es.glsl"),
},
300 es => {
vertex: include_str!("shader/vertex_300es.glsl"),
fragment: include_str!("shader/fragment_300es.glsl"),
},
}
.expect("Failed to compile shader");
Painter {
max_texture_side,
program,
textures: Default::default(),
next_native_tex_id: 0,
}
}
pub fn max_texture_side(&self) -> usize {
self.max_texture_side
}
pub fn paint_and_update_textures<T: glium::Surface>(
&mut self,
display: &glium::Display,
target: &mut T,
pixels_per_point: f32,
clipped_primitives: &[egui::ClippedPrimitive],
textures_delta: &egui::TexturesDelta,
) {
for (id, image_delta) in &textures_delta.set {
self.set_texture(display, *id, image_delta);
}
self.paint_primitives(display, target, pixels_per_point, clipped_primitives);
for &id in &textures_delta.free {
self.free_texture(id);
}
}
/// Main entry-point for painting a frame.
/// You should call `target.clear_color(..)` before
/// and `target.finish()` after this.
pub fn paint_primitives<T: glium::Surface>(
&mut self,
display: &glium::Display,
target: &mut T,
pixels_per_point: f32,
clipped_primitives: &[egui::ClippedPrimitive],
) {
for egui::ClippedPrimitive {
clip_rect,
primitive,
} in clipped_primitives
{
match primitive {
Primitive::Mesh(mesh) => {
self.paint_mesh(target, display, pixels_per_point, clip_rect, mesh);
}
Primitive::Callback(_) => {
panic!("Custom rendering callbacks are not implemented in egui_glium");
}
}
}
}
#[inline(never)] // Easier profiling
fn paint_mesh<T: glium::Surface>(
&mut self,
target: &mut T,
display: &glium::Display,
pixels_per_point: f32,
clip_rect: &Rect,
mesh: &Mesh,
) {
debug_assert!(mesh.is_valid());
let vertex_buffer = {
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct Vertex {
a_pos: [f32; 2],
a_tc: [f32; 2],
a_srgba: [u8; 4],
}
implement_vertex!(Vertex, a_pos, a_tc, a_srgba);
let vertices: &[Vertex] = bytemuck::cast_slice(&mesh.vertices);
// TODO(emilk): we should probably reuse the [`VertexBuffer`] instead of allocating a new one each frame.
glium::VertexBuffer::new(display, vertices).unwrap()
};
// TODO(emilk): we should probably reuse the [`IndexBuffer`] instead of allocating a new one each frame.
let index_buffer =
glium::IndexBuffer::new(display, PrimitiveType::TrianglesList, &mesh.indices).unwrap();
let (width_in_pixels, height_in_pixels) = display.get_framebuffer_dimensions();
let width_in_points = width_in_pixels as f32 / pixels_per_point;
let height_in_points = height_in_pixels as f32 / pixels_per_point;
if let Some(texture) = self.texture(mesh.texture_id) {
// The texture coordinates for text are so that both nearest and linear should work with the egui font texture.
// For user textures linear sampling is more likely to be the right choice.
let filter = MagnifySamplerFilter::Linear;
let uniforms = uniform! {
u_screen_size: [width_in_points, height_in_points],
u_sampler: texture.sampled().magnify_filter(filter).wrap_function(SamplerWrapFunction::Clamp),
};
// egui outputs colors with premultiplied alpha:
let color_blend_func = glium::BlendingFunction::Addition {
source: glium::LinearBlendingFactor::One,
destination: glium::LinearBlendingFactor::OneMinusSourceAlpha,
};
// Less important, but this is technically the correct alpha blend function
// when you want to make use of the framebuffer alpha (for screenshots, compositing, etc).
let alpha_blend_func = glium::BlendingFunction::Addition {
source: glium::LinearBlendingFactor::OneMinusDestinationAlpha,
destination: glium::LinearBlendingFactor::One,
};
let blend = glium::Blend {
color: color_blend_func,
alpha: alpha_blend_func,
..Default::default()
};
// egui outputs mesh in both winding orders:
let backface_culling = glium::BackfaceCullingMode::CullingDisabled;
// Transform clip rect to physical pixels:
let clip_min_x = pixels_per_point * clip_rect.min.x;
let clip_min_y = pixels_per_point * clip_rect.min.y;
let clip_max_x = pixels_per_point * clip_rect.max.x;
let clip_max_y = pixels_per_point * clip_rect.max.y;
// Make sure clip rect can fit within a `u32`:
let clip_min_x = clip_min_x.clamp(0.0, width_in_pixels as f32);
let clip_min_y = clip_min_y.clamp(0.0, height_in_pixels as f32);
let clip_max_x = clip_max_x.clamp(clip_min_x, width_in_pixels as f32);
let clip_max_y = clip_max_y.clamp(clip_min_y, height_in_pixels as f32);
let clip_min_x = clip_min_x.round() as u32;
let clip_min_y = clip_min_y.round() as u32;
let clip_max_x = clip_max_x.round() as u32;
let clip_max_y = clip_max_y.round() as u32;
let params = glium::DrawParameters {
blend,
backface_culling,
scissor: Some(glium::Rect {
left: clip_min_x,
bottom: height_in_pixels - clip_max_y,
width: clip_max_x - clip_min_x,
height: clip_max_y - clip_min_y,
}),
..Default::default()
};
target
.draw(
&vertex_buffer,
&index_buffer,
&self.program,
&uniforms,
&params,
)
.unwrap();
}
}
// ------------------------------------------------------------------------
pub fn set_texture(
&mut self,
facade: &dyn glium::backend::Facade,
tex_id: egui::TextureId,
delta: &egui::epaint::ImageDelta,
) {
let pixels: Vec<(u8, u8, u8, u8)> = match &delta.image {
egui::ImageData::Color(image) => {
assert_eq!(
image.width() * image.height(),
image.pixels.len(),
"Mismatch between texture size and texel count"
);
image.pixels.iter().map(|color| color.to_tuple()).collect()
}
egui::ImageData::Font(image) => {
let gamma = 1.0;
image
.srgba_pixels(gamma)
.map(|color| color.to_tuple())
.collect()
}
};
let glium_image = glium::texture::RawImage2d {
data: std::borrow::Cow::Owned(pixels),
width: delta.image.width() as _,
height: delta.image.height() as _,
format: glium::texture::ClientFormat::U8U8U8U8,
};
let format = texture::SrgbFormat::U8U8U8U8;
let mipmaps = texture::MipmapsOption::NoMipmap;
if let Some(pos) = delta.pos {
// update a sub-region
if let Some(gl_texture) = self.textures.get(&tex_id) {
let rect = glium::Rect {
left: pos[0] as _,
bottom: pos[1] as _,
width: glium_image.width,
height: glium_image.height,
};
gl_texture.main_level().write(rect, glium_image);
}
} else {
let gl_texture =
SrgbTexture2d::with_format(facade, glium_image, format, mipmaps).unwrap();
self.textures.insert(tex_id, gl_texture.into());
}
}
pub fn free_texture(&mut self, tex_id: egui::TextureId) {
self.textures.remove(&tex_id);
}
fn texture(&self, texture_id: egui::TextureId) -> Option<&SrgbTexture2d> {
self.textures.get(&texture_id).map(|rc| rc.as_ref())
}
pub fn register_native_texture(&mut self, native: Rc<SrgbTexture2d>) -> egui::TextureId {
let id = egui::TextureId::User(self.next_native_tex_id);
self.next_native_tex_id += 1;
self.textures.insert(id, native);
id
}
pub fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Rc<SrgbTexture2d>) {
self.textures.insert(id, replacing);
}
}

View File

@@ -0,0 +1,45 @@
#version 100
precision mediump float;
uniform sampler2D u_sampler;
varying vec4 v_rgba;
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));
}
vec4 srgba_from_linear(vec4 rgba) {
return vec4(srgb_from_linear(rgba.rgb), 255.0 * 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() {
// We must decode the colors, since WebGL doesn't come with sRGBA textures:
vec4 texture_rgba = linear_from_srgba(texture2D(u_sampler, v_tc) * 255.0);
/// Multiply vertex color with texture color (in linear space).
gl_FragColor = v_rgba * texture_rgba;
// 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
}

View File

@@ -0,0 +1,11 @@
#version 120
uniform sampler2D u_sampler;
varying vec4 v_rgba;
varying vec2 v_tc;
void main() {
// The texture sampler is sRGB aware, and glium already expects linear rgba output
// so no need for any sRGB conversions here:
gl_FragColor = v_rgba * texture2D(u_sampler, v_tc);
}

View File

@@ -0,0 +1,12 @@
#version 140
uniform sampler2D u_sampler;
in vec4 v_rgba;
in vec2 v_tc;
out vec4 f_color;
void main() {
// The texture sampler is sRGB aware, and glium already expects linear rgba output
// so no need for any sRGB conversions here:
f_color = v_rgba * texture(u_sampler, v_tc);
}

View File

@@ -0,0 +1,33 @@
#version 300 es
precision mediump float;
uniform sampler2D u_sampler;
varying vec4 v_rgba;
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));
}
vec4 srgba_from_linear(vec4 rgba) {
return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a);
}
void main() {
// The texture is set up with `SRGB8_ALPHA8`, so no need to decode here!
vec4 texture_rgba = texture2D(u_sampler, v_tc);
/// Multiply vertex color with texture color (in linear space).
gl_FragColor = v_rgba * texture_rgba;
// 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
}

View File

@@ -0,0 +1,32 @@
#version 100
precision mediump float;
uniform vec2 u_screen_size;
attribute vec2 a_pos;
attribute vec2 a_tc;
attribute vec4 a_srgba;
varying vec4 v_rgba;
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() {
gl_Position = vec4(
2.0 * a_pos.x / u_screen_size.x - 1.0,
1.0 - 2.0 * a_pos.y / u_screen_size.y,
0.0,
1.0);
// egui encodes vertex colors in gamma spaces, so we must decode the colors here:
v_rgba = linear_from_srgba(a_srgba);
v_tc = a_tc;
}

View File

@@ -0,0 +1,31 @@
#version 120
uniform vec2 u_screen_size;
attribute vec2 a_pos;
attribute vec4 a_srgba; // 0-255 sRGB
attribute vec2 a_tc;
varying vec4 v_rgba;
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() {
gl_Position = vec4(
2.0 * a_pos.x / u_screen_size.x - 1.0,
1.0 - 2.0 * a_pos.y / u_screen_size.y,
0.0,
1.0);
// egui encodes vertex colors in gamma spaces, so we must decode the colors here:
v_rgba = linear_from_srgba(a_srgba);
v_tc = a_tc;
}

View File

@@ -0,0 +1,31 @@
#version 140
uniform vec2 u_screen_size;
in vec2 a_pos;
in vec4 a_srgba; // 0-255 sRGB
in vec2 a_tc;
out vec4 v_rgba;
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() {
gl_Position = vec4(
2.0 * a_pos.x / u_screen_size.x - 1.0,
1.0 - 2.0 * a_pos.y / u_screen_size.y,
0.0,
1.0);
// egui encodes vertex colors in gamma spaces, so we must decode the colors here:
v_rgba = linear_from_srgba(a_srgba);
v_tc = a_tc;
}

View File

@@ -0,0 +1,32 @@
#version 300 es
precision mediump float;
uniform vec2 u_screen_size;
attribute vec2 a_pos;
attribute vec2 a_tc;
attribute vec4 a_srgba;
varying vec4 v_rgba;
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() {
gl_Position = vec4(
2.0 * a_pos.x / u_screen_size.x - 1.0,
1.0 - 2.0 * a_pos.y / u_screen_size.y,
0.0,
1.0);
// egui encodes vertex colors in gamma spaces, so we must decode the colors here:
v_rgba = linear_from_srgba(a_srgba);
v_tc = a_tc;
}