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

Merge branch 'main' into common-panels

This commit is contained in:
Emil Ernerfeldt
2025-11-16 11:31:23 +01:00
575 changed files with 22977 additions and 10608 deletions

View File

@@ -6,6 +6,35 @@ Changes since the last release can be found at <https://github.com/emilk/egui/co
## 0.33.2 - 2025-11-13
Nothing new
## 0.33.0 - 2025-10-09
* Update MSRV from 1.86 to 1.88 [#7579](https://github.com/emilk/egui/pull/7579) by [@Wumpf](https://github.com/Wumpf)
## 0.32.3 - 2025-09-12
Nothing new
## 0.32.2 - 2025-09-04
Nothing new
## 0.32.1 - 2025-08-15
Nothing new
## 0.32.0 - 2025-07-10
* Fix text distortion on mobile devices/browsers with `glow` backend [#6893](https://github.com/emilk/egui/pull/6893) by [@wareya](https://github.com/wareya)
* Improve texture filtering by doing it in gamma space [#7311](https://github.com/emilk/egui/pull/7311) by [@emilk](https://github.com/emilk)
## 0.31.1 - 2025-03-05
Nothing new
## 0.31.0 - 2025-02-04
Nothing new

View File

@@ -5,19 +5,13 @@ authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
description = "Bindings for using egui natively using the glow library"
edition.workspace = true
rust-version.workspace = true
homepage = "https://github.com/emilk/egui/tree/master/crates/egui_glow"
homepage = "https://github.com/emilk/egui/tree/main/crates/egui_glow"
license.workspace = true
readme = "README.md"
repository = "https://github.com/emilk/egui/tree/master/crates/egui_glow"
repository = "https://github.com/emilk/egui/tree/main/crates/egui_glow"
categories = ["gui", "game-development"]
keywords = ["glow", "egui", "gui", "gamedev"]
include = [
"../LICENSE-APACHE",
"../LICENSE-MIT",
"**/*.rs",
"Cargo.toml",
"src/shader/*.glsl",
]
include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml", "src/shader/*.glsl"]
[lints]
workspace = true
@@ -53,11 +47,10 @@ x11 = ["winit?/x11"]
egui = { workspace = true, default-features = false, features = ["bytemuck"] }
egui-winit = { workspace = true, optional = true, default-features = false }
ahash.workspace = true
bytemuck.workspace = true
glow.workspace = true
log.workspace = true
memoffset = "0.9"
memoffset.workspace = true
profiling.workspace = true
#! ### Optional dependencies
@@ -74,7 +67,7 @@ wasm-bindgen.workspace = true
[dev-dependencies]
glutin = { workspace = true, default-features = true } # examples/pure_glow
glutin = { workspace = true, default-features = true } # examples/pure_glow
glutin-winit = { workspace = true, default-features = true }
[[example]]

View File

@@ -9,7 +9,7 @@ This crates provides bindings between [`egui`](https://github.com/emilk/egui) an
* Render egui using glow on both native and web.
* Write cross platform native egui apps (with the `winit` feature).
To write web apps using `glow` you can use [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe) (which uses `egui_glow` for rendering).
To write web apps using `glow` you can use [`eframe`](https://github.com/emilk/egui/tree/main/crates/eframe) (which uses `egui_glow` for rendering).
To use on Linux, first run:
@@ -17,7 +17,7 @@ To use on Linux, first run:
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev
```
This crate optionally depends on [`egui-winit`](https://github.com/emilk/egui/tree/master/crates/egui-winit).
This crate optionally depends on [`egui-winit`](https://github.com/emilk/egui/tree/main/crates/egui-winit).
Test the example with:

View File

@@ -9,7 +9,7 @@ use std::num::NonZeroU32;
use std::sync::Arc;
use egui_winit::winit;
use winit::raw_window_handle::HasWindowHandle;
use winit::raw_window_handle::HasWindowHandle as _;
/// The majority of `GlutinWindowContext` is taken from `eframe`
struct GlutinWindowContext {
@@ -22,12 +22,12 @@ struct GlutinWindowContext {
impl GlutinWindowContext {
// refactor this function to use `glutin-winit` crate eventually.
// preferably add android support at the same time.
#[allow(unsafe_code)]
#[expect(unsafe_code)]
unsafe fn new(event_loop: &winit::event_loop::ActiveEventLoop) -> Self {
use glutin::context::NotCurrentGlContext;
use glutin::display::GetGlDisplay;
use glutin::display::GlDisplay;
use glutin::prelude::GlSurface;
use glutin::context::NotCurrentGlContext as _;
use glutin::display::GetGlDisplay as _;
use glutin::display::GlDisplay as _;
use glutin::prelude::GlSurface as _;
let winit_window_builder = winit::window::WindowAttributes::default()
.with_resizable(true)
.with_inner_size(winit::dpi::LogicalSize {
@@ -66,7 +66,7 @@ impl GlutinWindowContext {
.expect("failed to get window handle")
.as_raw()
});
log::debug!("raw window handle: {:?}", raw_window_handle);
log::debug!("raw window handle: {raw_window_handle:?}");
let context_attributes =
glutin::context::ContextAttributesBuilder::new().build(raw_window_handle);
// by default, glutin will try to create a core opengl context. but, if it is not available, try to create a gl-es context using this fallback attributes
@@ -138,7 +138,7 @@ impl GlutinWindowContext {
}
fn resize(&self, physical_size: winit::dpi::PhysicalSize<u32>) {
use glutin::surface::GlSurface;
use glutin::surface::GlSurface as _;
self.gl_surface.resize(
&self.gl_context,
physical_size.width.try_into().unwrap(),
@@ -147,12 +147,12 @@ impl GlutinWindowContext {
}
fn swap_buffers(&self) -> glutin::error::Result<()> {
use glutin::surface::GlSurface;
use glutin::surface::GlSurface as _;
self.gl_surface.swap_buffers(&self.gl_context)
}
fn get_proc_address(&self, addr: &std::ffi::CStr) -> *const std::ffi::c_void {
use glutin::display::GlDisplay;
use glutin::display::GlDisplay as _;
self.gl_display.get_proc_address(addr)
}
}

View File

@@ -62,18 +62,14 @@ macro_rules! check_for_gl_error {
/// ```
#[macro_export]
macro_rules! check_for_gl_error_even_in_release {
($gl: expr) => {{
$crate::check_for_gl_error_impl($gl, file!(), line!(), "")
}};
($gl: expr, $context: literal) => {{
$crate::check_for_gl_error_impl($gl, file!(), line!(), $context)
}};
($gl: expr) => {{ $crate::check_for_gl_error_impl($gl, file!(), line!(), "") }};
($gl: expr, $context: literal) => {{ $crate::check_for_gl_error_impl($gl, file!(), line!(), $context) }};
}
#[doc(hidden)]
pub fn check_for_gl_error_impl(gl: &glow::Context, file: &str, line: u32, context: &str) {
use glow::HasContext as _;
#[allow(unsafe_code)]
#[expect(unsafe_code)]
let error_code = unsafe { gl.get_error() };
if error_code != glow::NO_ERROR {
let error_str = match error_code {
@@ -92,20 +88,11 @@ pub fn check_for_gl_error_impl(gl: &glow::Context, file: &str, line: u32, contex
if context.is_empty() {
log::error!(
"GL error, at {}:{}: {} (0x{:X}). Please file a bug at https://github.com/emilk/egui/issues",
file,
line,
error_str,
error_code,
"GL error, at {file}:{line}: {error_str} (0x{error_code:X}). Please file a bug at https://github.com/emilk/egui/issues"
);
} else {
log::error!(
"GL error, at {}:{} ({}): {} (0x{:X}). Please file a bug at https://github.com/emilk/egui/issues",
file,
line,
context,
error_str,
error_code,
"GL error, at {file}:{line} ({context}): {error_str} (0x{error_code:X}). Please file a bug at https://github.com/emilk/egui/issues"
);
}
}

View File

@@ -113,7 +113,7 @@ pub struct Painter {
///
/// # Example
///
/// See the [`custom3d_glow`](https://github.com/emilk/egui/blob/master/crates/egui_demo_app/src/apps/custom3d_wgpu.rs) demo source for a detailed usage example.
/// See the [`custom3d_glow`](https://github.com/emilk/egui/blob/main/crates/egui_demo_app/src/apps/custom3d_wgpu.rs) demo source for a detailed usage example.
pub struct CallbackFn {
f: Box<dyn Fn(PaintCallbackInfo, &Painter) + Sync + Send>,
}
@@ -168,23 +168,18 @@ impl Painter {
let shader_version = shader_version.unwrap_or_else(|| ShaderVersion::get(&gl));
let is_webgl_1 = shader_version == ShaderVersion::Es100;
let shader_version_declaration = shader_version.version_declaration();
log::debug!("Shader header: {:?}.", shader_version_declaration);
log::debug!("Shader header: {shader_version_declaration:?}.");
let supported_extensions = gl.supported_extensions();
log::trace!("OpenGL extensions: {supported_extensions:?}");
let srgb_textures = shader_version == ShaderVersion::Es300 // WebGL2 always support sRGB
|| supported_extensions.iter().any(|extension| {
// EXT_sRGB, GL_ARB_framebuffer_sRGB, GL_EXT_sRGB, GL_EXT_texture_sRGB_decode, …
extension.contains("sRGB")
});
log::debug!("SRGB texture Support: {:?}", srgb_textures);
let srgb_textures = false; // egui wants normal sRGB-unaware textures
let supports_srgb_framebuffer = !cfg!(target_arch = "wasm32")
&& supported_extensions.iter().any(|extension| {
// {GL,GLX,WGL}_ARB_framebuffer_sRGB, …
extension.ends_with("ARB_framebuffer_sRGB")
});
log::debug!("SRGB framebuffer Support: {:?}", supports_srgb_framebuffer);
log::debug!("SRGB framebuffer Support: {supports_srgb_framebuffer}");
unsafe {
let vert = compile_shader(
@@ -202,11 +197,10 @@ impl Painter {
&gl,
glow::FRAGMENT_SHADER,
&format!(
"{}\n#define NEW_SHADER_INTERFACE {}\n#define DITHERING {}\n#define SRGB_TEXTURES {}\n{}\n{}",
"{}\n#define NEW_SHADER_INTERFACE {}\n#define DITHERING {}\n{}\n{}",
shader_version_declaration,
shader_version.is_new_shader_interface() as i32,
dithering as i32,
srgb_textures as i32,
shader_prefix,
FRAG_SRC
),
@@ -296,7 +290,7 @@ impl Painter {
/// So if in a [`egui::Shape::Callback`] you need to use an offscreen FBO, you should
/// then restore to this afterwards with
/// `gl.bind_framebuffer(glow::FRAMEBUFFER, painter.intermediate_fbo());`
#[allow(clippy::unused_self)]
#[expect(clippy::unused_self)]
pub fn intermediate_fbo(&self) -> Option<glow::Framebuffer> {
// 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.
@@ -445,7 +439,9 @@ impl Painter {
if let Some(callback) = callback.callback.downcast_ref::<CallbackFn>() {
(callback.f)(info, self);
} else {
log::warn!("Warning: Unsupported render callback. Expected egui_glow::CallbackFn");
log::warn!(
"Warning: Unsupported render callback. Expected egui_glow::CallbackFn"
);
}
check_for_gl_error!(&self.gl, "callback");
@@ -469,7 +465,7 @@ impl Painter {
#[inline(never)] // Easier profiling
fn paint_mesh(&mut self, mesh: &Mesh) {
debug_assert!(mesh.is_valid());
debug_assert!(mesh.is_valid(), "Mesh is not valid");
if let Some(texture) = self.texture(mesh.texture_id) {
unsafe {
self.gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vbo));
@@ -532,24 +528,7 @@ impl Painter {
self.upload_texture_srgb(delta.pos, image.size, delta.options, data);
}
egui::ImageData::Font(image) => {
assert_eq!(
image.width() * image.height(),
image.pixels.len(),
"Mismatch between texture size and texel count"
);
let data: Vec<u8> = {
profiling::scope!("font -> sRGBA");
image
.srgba_pixels(None)
.flat_map(|a| a.to_array())
.collect()
};
self.upload_texture_srgb(delta.pos, image.size, delta.options, &data);
}
};
}
}
fn upload_texture_srgb(
@@ -560,7 +539,12 @@ impl Painter {
data: &[u8],
) {
profiling::function_scope!();
assert_eq!(data.len(), w * h * 4);
assert_eq!(
data.len(),
w * h * 4,
"Mismatch between texture size and texel count, by {}",
data.len() % (w * h * 4)
);
assert!(
w <= self.max_texture_side && h <= self.max_texture_side,
"Got a texture image of size {}x{}, but the maximum supported texture side is only {}",
@@ -658,7 +642,6 @@ impl Painter {
self.textures.get(&texture_id).copied()
}
#[allow(clippy::needless_pass_by_value)] // False positive
pub fn register_native_texture(&mut self, native: glow::Texture) -> egui::TextureId {
self.assert_not_destroyed();
let id = egui::TextureId::User(self.next_native_tex_id);
@@ -667,7 +650,6 @@ impl Painter {
id
}
#[allow(clippy::needless_pass_by_value)] // False positive
pub fn replace_native_texture(&mut self, id: egui::TextureId, replacing: glow::Texture) {
if let Some(old_tex) = self.textures.insert(id, replacing) {
self.textures_to_destroy.push(old_tex);
@@ -693,10 +675,7 @@ impl Painter {
for row in pixels.chunks_exact((w * 4) as usize).rev() {
flipped.extend_from_slice(bytemuck::cast_slice(row));
}
egui::ColorImage {
size: [w as usize, h as usize],
pixels: flipped,
}
egui::ColorImage::new([w as usize, h as usize], flipped)
}
pub fn read_screen_rgb(&self, [w, h]: [u32; 2]) -> Vec<u8> {
@@ -719,6 +698,7 @@ impl Painter {
unsafe fn destroy_gl(&self) {
unsafe {
self.gl.delete_program(self.program);
#[expect(clippy::iter_over_hash_type)]
for tex in self.textures.values() {
self.gl.delete_texture(*tex);
}

View File

@@ -1,5 +1,11 @@
#ifdef GL_ES
precision mediump float;
// To avoid weird distortion issues when rendering text etc, we want highp if possible.
// But apparently some devices don't support it, so we have to check first.
#if defined(GL_FRAGMENT_PRECISION_HIGH) && GL_FRAGMENT_PRECISION_HIGH == 1
precision highp float;
#else
precision mediump float;
#endif
#endif
uniform sampler2D u_sampler;
@@ -37,25 +43,8 @@ vec3 dither_interleaved(vec3 rgb, float levels) {
return rgb + noise / (levels - 1.0);
}
// 0-1 sRGB gamma from 0-1 linear
vec3 srgb_gamma_from_linear(vec3 rgb) {
bvec3 cutoff = lessThan(rgb, vec3(0.0031308));
vec3 lower = rgb * vec3(12.92);
vec3 higher = vec3(1.055) * pow(rgb, vec3(1.0 / 2.4)) - vec3(0.055);
return mix(higher, lower, vec3(cutoff));
}
// 0-1 sRGBA gamma from 0-1 linear
vec4 srgba_gamma_from_linear(vec4 rgba) {
return vec4(srgb_gamma_from_linear(rgba.rgb), rgba.a);
}
void main() {
#if SRGB_TEXTURES
vec4 texture_in_gamma = srgba_gamma_from_linear(texture2D(u_sampler, v_tc));
#else
vec4 texture_in_gamma = texture2D(u_sampler, v_tc);
#endif
// We multiply the colors in gamma space, because that's the only way to get text to look right.
vec4 frag_color_gamma = v_rgba_in_gamma * texture_in_gamma;

View File

@@ -9,7 +9,13 @@
#endif
#ifdef GL_ES
precision mediump float;
// To avoid weird distortion issues when rendering text etc, we want highp if possible.
// But apparently some devices don't support it, so we have to check first.
#if defined(GL_FRAGMENT_PRECISION_HIGH) && GL_FRAGMENT_PRECISION_HIGH == 1
precision highp float;
#else
precision mediump float;
#endif
#endif
uniform vec2 u_screen_size;

View File

@@ -1,11 +1,10 @@
#![allow(unsafe_code)]
#![allow(clippy::undocumented_unsafe_blocks)]
use std::convert::TryInto;
use std::convert::TryInto as _;
/// Helper for parsing and interpreting the OpenGL shader version.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[allow(dead_code)]
pub enum ShaderVersion {
Gl120,
@@ -25,11 +24,7 @@ impl ShaderVersion {
let shading_lang_string =
unsafe { gl.get_parameter_string(glow::SHADING_LANGUAGE_VERSION) };
let shader_version = Self::parse(&shading_lang_string);
log::debug!(
"Shader version: {:?} ({:?}).",
shader_version,
shading_lang_string
);
log::debug!("Shader version: {shader_version:?} ({shading_lang_string:?}).");
shader_version
}
@@ -48,11 +43,7 @@ impl ShaderVersion {
.try_into()
.unwrap();
if es {
if maj >= 3 {
Self::Es300
} else {
Self::Es100
}
if maj >= 3 { Self::Es300 } else { Self::Es100 }
} else if maj > 1 || (maj == 1 && min >= 40) {
Self::Gl140
} else {

View File

@@ -27,7 +27,6 @@ pub(crate) struct VertexArrayObject {
}
impl VertexArrayObject {
#[allow(clippy::needless_pass_by_value)] // false positive
pub(crate) unsafe fn new(
gl: &glow::Context,
vbo: glow::Buffer,
@@ -119,7 +118,7 @@ fn supports_vao(gl: &glow::Context) -> bool {
const OPENGL_ES_PREFIX: &str = "OpenGL ES ";
let version_string = unsafe { gl.get_parameter_string(glow::VERSION) };
log::debug!("GL version: {:?}.", version_string);
log::debug!("GL version: {version_string:?}.");
// Examples:
// * "WebGL 2.0 (OpenGL ES 3.0 Chromium)"
@@ -130,7 +129,7 @@ fn supports_vao(gl: &glow::Context) -> bool {
if version_str.contains("1.0") {
// need to test OES_vertex_array_object .
let supported_extensions = gl.supported_extensions();
log::debug!("Supported OpenGL extensions: {:?}", supported_extensions);
log::debug!("Supported OpenGL extensions: {supported_extensions:?}");
supported_extensions.contains("OES_vertex_array_object")
|| supported_extensions.contains("GL_OES_vertex_array_object")
} else {
@@ -141,7 +140,7 @@ fn supports_vao(gl: &glow::Context) -> bool {
if version_string.contains("2.0") {
// need to test OES_vertex_array_object .
let supported_extensions = gl.supported_extensions();
log::debug!("Supported OpenGL extensions: {:?}", supported_extensions);
log::debug!("Supported OpenGL extensions: {supported_extensions:?}");
supported_extensions.contains("OES_vertex_array_object")
|| supported_extensions.contains("GL_OES_vertex_array_object")
} else {
@@ -153,7 +152,7 @@ fn supports_vao(gl: &glow::Context) -> bool {
// I found APPLE_vertex_array_object , GL_ATI_vertex_array_object ,ARB_vertex_array_object
// but APPLE's and ATI's very old extension.
let supported_extensions = gl.supported_extensions();
log::debug!("Supported OpenGL extensions: {:?}", supported_extensions);
log::debug!("Supported OpenGL extensions: {supported_extensions:?}");
supported_extensions.contains("ARB_vertex_array_object")
|| supported_extensions.contains("GL_ARB_vertex_array_object")
} else {

View File

@@ -1,8 +1,7 @@
use ahash::HashSet;
pub use egui_winit::{self, EventResponse};
use egui::{ViewportId, ViewportOutput};
pub use egui_winit;
use egui_winit::winit;
pub use egui_winit::EventResponse;
use crate::shader_version::ShaderVersion;
@@ -81,7 +80,7 @@ impl EguiGlow {
log::warn!("Multiple viewports not yet supported by EguiGlow");
}
for (_, ViewportOutput { commands, .. }) in viewport_output {
let mut actions_requested: HashSet<egui_winit::ActionRequested> = Default::default();
let mut actions_requested = Default::default();
egui_winit::process_viewport_commands(
&self.egui_ctx,
&mut self.viewport_info,
@@ -90,7 +89,7 @@ impl EguiGlow {
&mut actions_requested,
);
for action in actions_requested {
log::warn!("{:?} not yet supported by EguiGlow", action);
log::warn!("{action:?} not yet supported by EguiGlow");
}
}