diff --git a/Cargo.lock b/Cargo.lock index 626250a5..15c9bbb6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1041,10 +1041,12 @@ dependencies = [ "criterion", "egui", "egui_extras", + "egui_glow", "ehttp", "enum-map", "epi", "image", + "parking_lot 0.12.0", "poll-promise", "serde", "syntect", @@ -1083,6 +1085,7 @@ dependencies = [ "bytemuck", "egui", "egui-winit", + "epi", "glow", "glutin", "memoffset", diff --git a/egui_demo_lib/Cargo.toml b/egui_demo_lib/Cargo.toml index a41f3059..6235f9bc 100644 --- a/egui_demo_lib/Cargo.toml +++ b/egui_demo_lib/Cargo.toml @@ -36,10 +36,12 @@ syntax_highlighting = ["syntect"] [dependencies] egui = { version = "0.17.0", path = "../egui", default-features = false } epi = { version = "0.17.0", path = "../epi" } +egui_glow = { version = "0.17.0", path = "../egui_glow" } # for custom_3d app chrono = { version = "0.4", optional = true, features = ["js-sys", "wasmbind"] } enum-map = { version = "2", features = ["serde"] } tracing = "0.1" +parking_lot = "0.12" unicode_names2 = { version = "0.5.0", default-features = false } # feature "http": diff --git a/egui_demo_lib/src/apps/custom_3d.rs b/egui_demo_lib/src/apps/custom_3d.rs new file mode 100644 index 00000000..6bdfbe1d --- /dev/null +++ b/egui_demo_lib/src/apps/custom_3d.rs @@ -0,0 +1,190 @@ +//! This demo shows how to embed 3D rendering using [`glow`](https://github.com/grovesNL/glow) in `epi`. +//! +//! This is very advanced usage, and you need to be careful. +//! +//! If you want an easier way to show 3D graphics with egui, take a look at: +//! * [`bevy_egui`](https://github.com/mvlabat/bevy_egui) +//! * [`three-d`](https://github.com/asny/three-d) + +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release + +use epi::{glow, Renderer as _}; +use parking_lot::Mutex; +use std::sync::Arc; + +pub struct Custom3dApp { + /// Behind an `Arc>` so we can pass it to [`egui::PaintCallback`] and paint later. + rotating_triangle: Arc>, + angle: f32, +} + +impl Custom3dApp { + pub fn new(cc: &epi::CreationContext<'_>) -> Self { + Self { + rotating_triangle: Arc::new(Mutex::new(RotatingTriangle::new(&cc.gl))), + angle: 0.0, + } + } +} + +impl epi::App for Custom3dApp { + fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) { + egui::CentralPanel::default().show(ctx, |ui| { + ui.vertical_centered(|ui| { + ui.add(crate::__egui_github_link_file!()); + }); + + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 0.0; + ui.label("The triangle is being painted using "); + ui.hyperlink_to("glow", "https://github.com/grovesNL/glow"); + ui.label(" (OpenGL)."); + }); + + egui::ScrollArea::both().show(ui, |ui| { + egui::Frame::canvas(ui.style()).show(ui, |ui| { + self.custom_painting(ui); + }); + ui.label("Drag to rotate!"); + }); + }); + } +} + +impl Custom3dApp { + fn custom_painting(&mut self, ui: &mut egui::Ui) { + let (rect, response) = + ui.allocate_exact_size(egui::Vec2::splat(256.0), egui::Sense::drag()); + + self.angle += response.drag_delta().x * 0.01; + + let angle = self.angle; + let rotating_triangle = self.rotating_triangle.clone(); + + let callback = egui::epaint::PaintCallback { + rect, + callback: std::sync::Arc::new(move |render_ctx| { + if let Some(painter) = render_ctx.downcast_ref::() { + rotating_triangle.lock().paint(painter.gl(), angle); + } else { + eprintln!("Can't do custom painting because we are not using a glow context"); + } + }), + }; + ui.painter().add(callback); + } +} + +struct RotatingTriangle { + program: glow::Program, + vertex_array: glow::VertexArray, +} + +impl RotatingTriangle { + fn new(gl: &glow::Context) -> Self { + use glow::HasContext as _; + + let shader_version = if cfg!(target_arch = "wasm32") { + "#version 300 es" + } else { + "#version 410" + }; + + unsafe { + let program = gl.create_program().expect("Cannot create program"); + + let (vertex_shader_source, fragment_shader_source) = ( + r#" + const vec2 verts[3] = vec2[3]( + vec2(0.0, 1.0), + vec2(-1.0, -1.0), + vec2(1.0, -1.0) + ); + const vec4 colors[3] = vec4[3]( + vec4(1.0, 0.0, 0.0, 1.0), + vec4(0.0, 1.0, 0.0, 1.0), + vec4(0.0, 0.0, 1.0, 1.0) + ); + out vec4 v_color; + uniform float u_angle; + void main() { + v_color = colors[gl_VertexID]; + gl_Position = vec4(verts[gl_VertexID], 0.0, 1.0); + gl_Position.x *= cos(u_angle); + } + "#, + r#" + precision mediump float; + in vec4 v_color; + out vec4 out_color; + void main() { + out_color = v_color; + } + "#, + ); + + let shader_sources = [ + (glow::VERTEX_SHADER, vertex_shader_source), + (glow::FRAGMENT_SHADER, fragment_shader_source), + ]; + + let shaders: Vec<_> = shader_sources + .iter() + .map(|(shader_type, shader_source)| { + let shader = gl + .create_shader(*shader_type) + .expect("Cannot create shader"); + gl.shader_source(shader, &format!("{}\n{}", shader_version, shader_source)); + gl.compile_shader(shader); + if !gl.get_shader_compile_status(shader) { + panic!("{}", gl.get_shader_info_log(shader)); + } + gl.attach_shader(program, shader); + shader + }) + .collect(); + + gl.link_program(program); + if !gl.get_program_link_status(program) { + panic!("{}", gl.get_program_info_log(program)); + } + + for shader in shaders { + gl.detach_shader(program, shader); + gl.delete_shader(shader); + } + + let vertex_array = gl + .create_vertex_array() + .expect("Cannot create vertex array"); + + Self { + program, + vertex_array, + } + } + } + + // TODO: figure out how to call this in a nice way + #[allow(unused)] + fn destroy(self, gl: &glow::Context) { + use glow::HasContext as _; + unsafe { + gl.delete_program(self.program); + gl.delete_vertex_array(self.vertex_array); + } + } + + fn paint(&self, gl: &glow::Context, angle: f32) { + use glow::HasContext as _; + unsafe { + gl.use_program(Some(self.program)); + gl.uniform_1_f32( + gl.get_uniform_location(self.program, "u_angle").as_ref(), + angle, + ); + gl.bind_vertex_array(Some(self.vertex_array)); + gl.draw_arrays(glow::TRIANGLES, 0, 3); + } + } +} diff --git a/egui_demo_lib/src/apps/mod.rs b/egui_demo_lib/src/apps/mod.rs index f801529f..e1180f1a 100644 --- a/egui_demo_lib/src/apps/mod.rs +++ b/egui_demo_lib/src/apps/mod.rs @@ -1,10 +1,12 @@ mod color_test; +mod custom_3d; mod demo; mod fractal_clock; #[cfg(feature = "http")] mod http_app; pub use color_test::ColorTest; +pub use custom_3d::Custom3dApp; pub use demo::DemoApp; pub use fractal_clock::FractalClock; #[cfg(feature = "http")] diff --git a/egui_demo_lib/src/wrap_app.rs b/egui_demo_lib/src/wrap_app.rs index e80240e4..3386f8bf 100644 --- a/egui_demo_lib/src/wrap_app.rs +++ b/egui_demo_lib/src/wrap_app.rs @@ -1,7 +1,5 @@ /// All the different demo apps. -#[derive(Default)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr(feature = "serde", serde(default))] pub struct Apps { demo: crate::apps::DemoApp, easy_mark_editor: crate::easy_mark::EasyMarkEditor, @@ -9,9 +7,22 @@ pub struct Apps { http: crate::apps::HttpApp, clock: crate::apps::FractalClock, color_test: crate::apps::ColorTest, + custom_3d: crate::apps::Custom3dApp, } impl Apps { + fn new(cc: &epi::CreationContext<'_>) -> Self { + Self { + demo: Default::default(), + easy_mark_editor: Default::default(), + #[cfg(feature = "http")] + http: Default::default(), + clock: Default::default(), + color_test: Default::default(), + custom_3d: crate::apps::Custom3dApp::new(cc), + } + } + fn iter_mut(&mut self) -> impl Iterator { vec![ ("✨ Demos", "demo", &mut self.demo as &mut dyn epi::App), @@ -32,6 +43,11 @@ impl Apps { "colors", &mut self.color_test as &mut dyn epi::App, ), + ( + "🔺 3D painting", + "custom_3d", + &mut self.custom_3d as &mut dyn epi::App, + ), ] .into_iter() } diff --git a/egui_glium/examples/native_texture.rs b/egui_glium/examples/native_texture.rs index abaafd46..740f88e4 100644 --- a/egui_glium/examples/native_texture.rs +++ b/egui_glium/examples/native_texture.rs @@ -1,4 +1,4 @@ -//! Example how to use [`epi::NativeTexture`] with glium. +//! Example how to use native textures with glium painter. #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release diff --git a/egui_glow/Cargo.toml b/egui_glow/Cargo.toml index f9edcbb0..c1b0c7e2 100644 --- a/egui_glow/Cargo.toml +++ b/egui_glow/Cargo.toml @@ -52,6 +52,7 @@ winit = ["egui-winit", "glutin"] egui = { version = "0.17.0", path = "../egui", default-features = false, features = [ "convert_bytemuck", ] } +epi = { version = "0.17.0", path = "../epi" } bytemuck = "1.7" glow = "0.11" diff --git a/egui_glow/src/painter.rs b/egui_glow/src/painter.rs index 804bc19e..02bededf 100644 --- a/egui_glow/src/painter.rs +++ b/egui_glow/src/painter.rs @@ -6,6 +6,7 @@ use egui::{ emath::Rect, epaint::{Color32, Mesh, Primitive, Vertex}, }; +use epi::Renderer as _; use glow::HasContext; use memoffset::offset_of; @@ -19,6 +20,31 @@ pub use glow::Context; const VERT_SRC: &str = include_str!("shader/vertex.glsl"); const FRAG_SRC: &str = include_str!("shader/fragment.glsl"); +// ---------------------------------------------------------------------------- + +#[derive(Copy, Clone)] +pub enum TextureFilter { + Linear, + Nearest, +} + +impl Default for TextureFilter { + fn default() -> Self { + TextureFilter::Linear + } +} + +impl TextureFilter { + pub(crate) fn glow_code(&self) -> u32 { + match self { + TextureFilter::Linear => glow::LINEAR, + TextureFilter::Nearest => glow::NEAREST, + } + } +} + +// ---------------------------------------------------------------------------- + /// An OpenGL painter using [`glow`]. /// /// This is responsible for painting egui and managing egui textures. @@ -46,7 +72,6 @@ pub struct Painter { textures: HashMap, - #[cfg(feature = "epi")] next_native_tex_id: u64, // TODO: 128-bit texture space? /// Stores outdated OpenGL textures that are yet to be deleted @@ -56,23 +81,28 @@ pub struct Painter { destroyed: bool, } -#[derive(Copy, Clone)] -pub enum TextureFilter { - Linear, - Nearest, -} - -impl Default for TextureFilter { - fn default() -> Self { - TextureFilter::Linear +impl epi::Renderer for Painter { + /// Access the shared glow context. + fn gl(&self) -> &std::rc::Rc { + &self.gl } -} -impl TextureFilter { - pub(crate) fn glow_code(&self) -> u32 { - match self { - TextureFilter::Linear => glow::LINEAR, - TextureFilter::Nearest => glow::NEAREST, + /// Get the [`glow::Texture`] bound to a [`egui::TextureId`]. + fn texture(&self, texture_id: egui::TextureId) -> Option { + self.textures.get(&texture_id).copied() + } + + 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); + self.next_native_tex_id += 1; + self.textures.insert(id, native); + id + } + + 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); } } } @@ -226,7 +256,6 @@ impl Painter { vertex_buffer, element_array_buffer, textures: Default::default(), - #[cfg(feature = "epi")] next_native_tex_id: 1 << 32, textures_to_destroy: Vec::new(), destroyed: false, @@ -234,11 +263,6 @@ impl Painter { } } - /// Access the shared glow context. - pub fn gl(&self) -> &std::rc::Rc { - &self.gl - } - pub fn max_texture_side(&self) -> usize { self.max_texture_side } @@ -403,7 +427,7 @@ impl Painter { #[inline(never)] // Easier profiling fn paint_mesh(&mut self, mesh: &Mesh) { debug_assert!(mesh.is_valid()); - if let Some(texture) = self.get_texture(mesh.texture_id) { + if let Some(texture) = self.texture(mesh.texture_id) { unsafe { self.gl .bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer)); @@ -579,11 +603,6 @@ impl Painter { } } - /// Get the [`glow::Texture`] bound to a [`egui::TextureId`]. - pub fn get_texture(&self, texture_id: egui::TextureId) -> Option { - self.textures.get(&texture_id).copied() - } - unsafe fn destroy_gl(&self) { self.gl.delete_program(self.program); for tex in self.textures.values() { @@ -642,25 +661,6 @@ impl Drop for Painter { } } -#[cfg(feature = "epi")] -impl epi::NativeTexture for Painter { - type Texture = glow::Texture; - - fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId { - self.assert_not_destroyed(); - let id = egui::TextureId::User(self.next_native_tex_id); - self.next_native_tex_id += 1; - self.textures.insert(id, native); - id - } - - fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture) { - if let Some(old_tex) = self.textures.insert(id, replacing) { - self.textures_to_destroy.push(old_tex); - } - } -} - fn set_clip_rect( gl: &glow::Context, size_in_pixels: (u32, u32), diff --git a/egui_web/src/backend.rs b/egui_web/src/backend.rs index 0ea9c8c0..216688e0 100644 --- a/egui_web/src/backend.rs +++ b/egui_web/src/backend.rs @@ -1,7 +1,6 @@ use crate::{glow_wrapping::WrappedGlowPainter, *}; - use egui::TexturesDelta; -pub use egui::{pos2, Color32}; +use epi::Renderer as _; // ---------------------------------------------------------------------------- diff --git a/egui_web/src/glow_wrapping.rs b/egui_web/src/glow_wrapping.rs index a66cf5d4..c97f711a 100644 --- a/egui_web/src/glow_wrapping.rs +++ b/egui_web/src/glow_wrapping.rs @@ -1,5 +1,6 @@ use egui::{ClippedPrimitive, Rgba}; use egui_glow::glow; +use epi::Renderer as _; use wasm_bindgen::JsCast; use wasm_bindgen::JsValue; use web_sys::HtmlCanvasElement; diff --git a/epi/src/lib.rs b/epi/src/lib.rs index 9359ccca..853f8c37 100644 --- a/epi/src/lib.rs +++ b/epi/src/lib.rs @@ -374,16 +374,22 @@ pub struct IntegrationInfo { pub native_pixels_per_point: Option, } -/// Abstraction for platform dependent texture reference -pub trait NativeTexture { - /// The native texture type. - type Texture; +/// The rendering backend of `eframe`. +/// +/// This is a wrapper around [`glow`](https://github.com/grovesNL/glow) +/// that also handles texture allocations. +pub trait Renderer { + /// Access the shared glow context. + fn gl(&self) -> &std::rc::Rc; + + /// Get the [`glow::Texture`] bound to a [`egui::TextureId`]. + fn texture(&self, texture_id: egui::TextureId) -> Option; /// Bind native texture to an egui texture id. - fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId; + fn register_native_texture(&mut self, native: glow::Texture) -> egui::TextureId; /// Change what texture the given id refers to. - fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture); + fn replace_native_texture(&mut self, id: egui::TextureId, replacing: glow::Texture); } // ----------------------------------------------------------------------------