//! This demo shows how to embed 3D rendering using [`glow`](https://github.com/grovesNL/glow) in `eframe`. //! //! 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 the `custom_3d_three-d.rs` example. //! //! If you are content of having egui sit on top of a 3D background, 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 #![allow(unsafe_code)] use eframe::egui; use egui::mutex::Mutex; use std::sync::Arc; fn main() { let options = eframe::NativeOptions { initial_window_size: Some(egui::vec2(350.0, 380.0)), multisampling: 8, ..Default::default() }; eframe::run_native( "Custom 3D painting in eframe using glow", options, Box::new(|cc| Box::new(MyApp::new(cc))), ); } struct MyApp { /// Behind an `Arc>` so we can pass it to [`egui::PaintCallback`] and paint later. rotating_triangle: Arc>, angle: f32, } impl MyApp { fn new(cc: &eframe::CreationContext<'_>) -> Self { Self { rotating_triangle: Arc::new(Mutex::new(RotatingTriangle::new(&cc.gl))), angle: 0.0, } } } impl eframe::App for MyApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { 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::Frame::canvas(ui.style()).show(ui, |ui| { self.custom_painting(ui); }); ui.label("Drag to rotate!"); }); } fn on_exit(&mut self, gl: &glow::Context) { self.rotating_triangle.lock().destroy(gl); } } impl MyApp { fn custom_painting(&mut self, ui: &mut egui::Ui) { let (rect, response) = ui.allocate_exact_size(egui::Vec2::splat(300.0), egui::Sense::drag()); self.angle += response.drag_delta().x * 0.01; // Clone locals so we can move them into the paint callback: let angle = self.angle; let rotating_triangle = self.rotating_triangle.clone(); let callback = egui::PaintCallback { rect, callback: std::sync::Arc::new(move |_info, 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, } } } 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); } } }